Metadata-Version: 2.4
Name: ifc-spf
Version: 0.1.0
Summary: Read IFC-SPF (STEP) files with the standard library — instances, property sets, quantities and units, no geometry kernel.
Author: Nguyen Thu Thuy
License: MIT
Project-URL: Homepage, https://github.com/sophie-nguyenthuthuy/ifc-spf
Project-URL: Issues, https://github.com/sophie-nguyenthuthuy/ifc-spf/issues
Keywords: ifc,bim,step,iso-10303,buildingsmart,aec
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: File Formats
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# ifc-spf

**Read IFC files with the standard library.** Instances, property sets,
quantities, units and the spatial chain — no geometry kernel, no compiled
dependency, no schema download.

```python
from ifcspf import Model, Index

model = Model.open("building.ifc")
index = Index(model)

for door in model.of_type("IfcDoor"):            # subtypes included
    print(model.attr(door, "Name"),              # attributes by name, not position
          index.flat(door),                      # {"Pset_DoorCommon.FireExit": True, …}
          index.spatial(door)["storey"])         # where it is
```

Extracted from two tools that had each written it once already — a quantity
takeoff and a building-code checker — because the interesting part of both was
never the parser.

## What it does

| layer | what you get |
|---|---|
| `spf` | ISO 10303-21 tokeniser and parser: strings and their `\X2\` escapes, references, enumerations, typed values, complex instances, comments, gzip, the header |
| `schema` | attributes addressed by **name** (`attr(door, "OverallWidth")`), subtype expansion (`IfcWall` finds `IfcWallStandardCase`), IFC4 / IFC2X3 differences — all as data tables |
| `units` | the project unit assignment resolved to SI factors, including conversion-based units (feet, inches), plus output conversion to `mm`, `ft2`, `cy`, … |
| `psets` | property sets, quantity sets and the spatial chain, **converted to SI**, with type-object inheritance and per-property unit overrides |

## The three things it gets right that hand-rolled readers usually don't

**Type-level property sets are inherited.** A door's fire rating often lives on
its `IfcDoorType`, not the occurrence. `Index` collects type sets first and lets
instance sets override them — the inheritance IFC intends — and marks which is
which (`Value.from_type`).

**Per-property units override the project unit.** `IfcPropertySingleValue` and
every `IfcQuantity*` may carry their own `Unit`. A file with millimetre lengths
and one property in metres is not exotic; it is a Tuesday.

**Every measure comes out in SI, and says what it was.** `Value` carries the
converted number, the raw number, the measure type (`IFCLENGTHMEASURE`) and the
kind (`length`), so a consumer converts once, deliberately, at its own edge.

```python
value = index.get(door, "Pset_PlancheckDoor", "ClearWidth")
value.value      # 0.88          — metres, whatever the file used
value.raw        # 880.0         — as written
value.measure    # 'IFCLENGTHMEASURE'
value.kind       # 'length'
value.from_type  # False
```

## Install

```bash
pip install ifc-spf
```

Python 3.11+. No dependencies.

## API

```python
Model.open(path)  /  Model.from_text(text)      # .ifc and .ifc.gz
model.of_type("IfcWall", subtypes=True) -> [Entity]
model.attr(entity, "Name") / model.ref_attr(entity, "RelatingStructure")
model.get(ref) / model.resolve_all(refs) / model.referencing(entity, of_type=None)
model.schema        # 'IFC4' | 'IFC2X3' | …
model.scale         # UnitScale(length=0.001, area=1.0, …)
model.type_counts()

index = Index(model, inherit_type=True)
index.values(entity)   # {set name: {property name: Value}}
index.flat(entity)     # {"Pset.Name": value}
index.get(entity, "Pset_DoorCommon", "FireExit")   # one Value, case-insensitive
index.find(entity, "FireExit")                     # by name, any set
index.spatial(entity)  # {"space": …, "storey": …, "building": …, "site": …}
index.storey_elevation(entity)                     # metres

from ifcspf import loads, load, unwrap, convert, expand_types
convert(0.88, "mm")    # 880.0   — SI base out to a declared unit
```

## What it deliberately does not do

- **No geometry.** No swept solids, no BRep, no placement maths, no clash
  detection. Property-level tooling — takeoffs, code checks, audits, exports —
  does not need it, and pretending otherwise is how a reader becomes a kernel.
- **No writing.** Read-only, on purpose.
- **No schema validation.** It reads what the file states about itself. A file
  that lies about its own schema will be read as it is written, not as it should
  have been.

If you need geometry or validation, use ifcopenshell — it is excellent, and this
package is not trying to replace it. This is for the large class of jobs where
pulling in a compiled IFC toolkit is the heaviest thing in the project.

## Used by

- [qto](https://github.com/sophie-nguyenthuthuy/qto) — quantity takeoff with
  pluggable cost classification
- [plancheck](https://github.com/sophie-nguyenthuthuy/plancheck) — building code
  as machine-readable rules

MIT licensed.
