119 lines
3 KiB
Markdown
119 lines
3 KiB
Markdown
# Space extraction and object modifications
|
||
|
||
## Extracting a space
|
||
|
||
The `space` command extracts an `IfcSpace` and all elements contained within it
|
||
(furniture, walls, etc.) using the ifcopenshell `location` filter.
|
||
|
||
```bash
|
||
# by Name (room code)
|
||
uv run ifccommit.py space input.ifc out.ifc A102
|
||
|
||
# by LongName (human label)
|
||
uv run ifccommit.py space input.ifc out.ifc "Living Room" --by longname
|
||
```
|
||
|
||
Internally the query is:
|
||
|
||
```
|
||
IfcElement, location = "A102"
|
||
```
|
||
|
||
The `location` filter follows `IfcRelContainedInSpatialStructure` links — it
|
||
returns the `IfcSpace` itself plus every element directly contained in it.
|
||
|
||
### Known limitation — aggregated spaces
|
||
|
||
Some IFC files place spaces via `IfcRelAggregates` instead of
|
||
`IfcRelContainedInSpatialStructure`. The `location` filter only follows the
|
||
latter, so those spaces appear empty. This is a data modelling choice in the
|
||
source file, not a bug in ifcbus.
|
||
|
||
### Multi-match with `--by longname`
|
||
|
||
When a `LongName` matches several spaces (e.g. "Bathroom 1" → A104 and B104),
|
||
the command writes one output file per space:
|
||
|
||
```
|
||
dist/out_A104.ifc
|
||
dist/out_B104.ifc
|
||
```
|
||
|
||
The stem of the user-provided output path is reused; the space `Name` is
|
||
appended as a suffix.
|
||
|
||
---
|
||
|
||
## Moving objects
|
||
|
||
IFC element positions are stored in a 4×4 transformation matrix inside
|
||
`IfcLocalPlacement → IfcAxis2Placement3D`. The last column holds the
|
||
translation in the file's length unit (metres for duplex.ifc).
|
||
|
||
```
|
||
[[ r00 r01 r02 X ],
|
||
[ r10 r11 r12 Y ],
|
||
[ r20 r21 r22 Z ],
|
||
[ 0 0 0 1 ]]
|
||
```
|
||
|
||
### Reading and writing placement
|
||
|
||
```python
|
||
import ifcopenshell
|
||
import ifcopenshell.util.placement
|
||
import ifcopenshell.api
|
||
|
||
model = ifcopenshell.open("input.ifc")
|
||
el = model.by_id(17902) # entity id (not GlobalId)
|
||
|
||
matrix = ifcopenshell.util.placement.get_local_placement(el.ObjectPlacement)
|
||
|
||
# translate +2 m on world X axis ("right")
|
||
matrix[0, 3] += 2.0
|
||
|
||
ifcopenshell.api.run(
|
||
"geometry.edit_object_placement",
|
||
model,
|
||
product=el,
|
||
matrix=matrix,
|
||
)
|
||
|
||
model.write("output.ifc")
|
||
```
|
||
|
||
### Finding an element by name
|
||
|
||
Entity ids are file-specific and unstable. Prefer searching by name:
|
||
|
||
```python
|
||
results = [
|
||
e for e in model.by_type("IfcFurnishingElement")
|
||
if "168381" in (e.Name or "")
|
||
]
|
||
el = results[0]
|
||
```
|
||
|
||
Or use `ifcopenshell.util.selector.filter_elements` for richer queries.
|
||
|
||
### Axis convention
|
||
|
||
"Right" is ambiguous without a viewport. In duplex.ifc the world X axis
|
||
is used as the reference. Verify with the placement matrix before committing
|
||
to a direction — a 180° rotated element has its local X pointing in the
|
||
opposite direction to world X.
|
||
|
||
### `move` command
|
||
|
||
```bash
|
||
uv run ifccommit.py move input.ifc output.ifc <entity_id> --x 2 --y 0 --z 0
|
||
```
|
||
|
||
Offsets are in the file's length unit (metres for duplex.ifc). Example:
|
||
|
||
```
|
||
uv run ifccommit.py move samples/duplex.ifc dist/duplex_moved_table.ifc 17902 --x 2
|
||
# Element : #17902 M_Table-Coffee:0915 x 1830 x 0457mm:...
|
||
# Before : X=2.6192 Y=-15.3432 Z=0.0000
|
||
# After : X=4.6192 Y=-15.3432 Z=0.0000
|
||
```
|