Rename ifcapi to ifcedit, README and black

This commit is contained in:
Bruno Postle
2026-02-09 22:59:11 +00:00
parent 12ed2d1ec3
commit b1f1de954d
16 changed files with 528 additions and 78 deletions
+208
View File
@@ -0,0 +1,208 @@
# ifcedit
A CLI wrapper that exposes all 350+ `ifcopenshell.api` mutation functions as
shell commands. Functions are auto-discovered at runtime via introspection --
no hardcoded list to maintain.
## Installation
```bash
pip install ifcedit
```
Requires `ifcopenshell`.
## Usage
```
ifcedit <command> [options] [--format json|text]
```
Three subcommands: `list` to discover functions, `docs` to read their
documentation, and `run` to execute them.
## Subcommands
### list
Discover available API modules and their functions.
**List all modules:**
```bash
ifcedit list
```
```json
[
{
"module": "root",
"description": "Functions for creating project-level entities",
"functions": ["create_entity", "remove_product", "copy_class"],
"count": 3
},
{
"module": "spatial",
"description": "Functions for managing spatial relationships",
"functions": ["assign_container", "unassign_container"],
"count": 2
}
]
```
**List functions in a module:**
```bash
ifcedit list root
```
```json
[
{
"name": "create_entity",
"description": "Create an IFC entity with optional initial attributes",
"params": [
{"name": "ifc_class", "type": "str", "required": true},
{"name": "name", "type": "Optional[str]"}
]
}
]
```
### docs
Show full documentation for a specific function, including parameter
descriptions from docstrings and return type.
```bash
ifcedit docs root.create_entity
```
```json
{
"module": "root",
"function": "create_entity",
"description": "Create an IFC entity with optional initial attributes",
"long_description": "This function creates a new entity instance...",
"params": [
{
"name": "ifc_class",
"type": "str",
"required": true,
"description": "The IFC class name (e.g. 'IfcWall', 'IfcProject')"
},
{
"name": "name",
"type": "Optional[str]",
"description": "Optional name attribute"
}
],
"return_type": "ifcopenshell.entity_instance",
"return_description": "The newly created entity instance"
}
```
### run
Execute an API function against an IFC file. Parameters are passed as
`--key value` pairs after the function name.
```bash
ifcedit run model.ifc root.create_entity --ifc_class IfcWall --name "My Wall"
```
```json
{
"ok": true,
"result": {"id": 42, "type": "IfcWall", "name": "My Wall"}
}
```
**Options:**
- `-o, --output <path>` -- write to a different file instead of overwriting the input
- `--dry-run` -- validate parameters without executing or saving
```bash
# Save to a new file
ifcedit run model.ifc root.create_entity -o out.ifc --ifc_class IfcWall
# Validate without executing
ifcedit run model.ifc root.create_entity --dry-run --ifc_class IfcWall
```
Dry-run output shows the resolved parameters:
```json
{
"ok": true,
"dry_run": true,
"module": "root",
"function": "create_entity",
"args": {"ifc_class": "IfcWall", "name": "My Wall"}
}
```
## Parameter type coercion
CLI strings are automatically converted to the types expected by each API
function, using the function's type annotations:
| Type | CLI input | Python value |
|------|-----------|--------------|
| `str` | `"hello"` | `"hello"` |
| `int` | `"42"` or `"#42"` | `42` |
| `float` | `"3.14"` | `3.14` |
| `bool` | `"true"`, `"1"`, `"yes"` | `True` |
| `Optional[X]` | `"none"` | `None` |
| `entity_instance` | `"42"` or `"#42"` | resolved from model by step ID |
| `list[entity_instance]` | `"5,6,7"` or `"[5, 6, 7]"` | list of resolved entities |
| `dict` | `'{"key": "val"}'` | parsed JSON object |
| `Literal["A", "B"]` | `"A"` | validated against allowed values |
## Examples
```bash
# Create a project
ifcedit run model.ifc root.create_entity --ifc_class IfcProject --name "My Project"
# Assign an element to a storey
ifcedit run model.ifc spatial.assign_container --products 10 --relating_structure 4
# Assign multiple elements at once
ifcedit run model.ifc aggregate.assign_object --products "5,6,7" --relating_object 1
# Add a property set
ifcedit run model.ifc pset.add_pset --product 10 --name "Pset_WallCommon"
# Edit properties
ifcedit run model.ifc pset.edit_pset --pset 15 \
--properties '{"IsExternal": true, "FireRating": "2HR"}'
```
## Error handling
Errors are reported in the JSON response:
```json
{
"ok": false,
"error": "Entity #999 not found in model"
}
```
Exit code is 0 on success, 1 on error.
## Relationship to ifcquery
`ifcedit` and `ifcquery` are complementary tools:
- **ifcquery** reads and inspects IFC models (summary, tree, info, select, relations, clash)
- **ifcedit** modifies IFC models by wrapping `ifcopenshell.api` functions
A typical workflow: inspect with `ifcquery`, look up the right API function
with `ifcedit docs`, then apply changes with `ifcedit run`.
## License
LGPLv3+ -- see the IfcOpenShell project license.
@@ -1,19 +1,19 @@
# IfcApi - CLI wrapper for ifcopenshell.api mutation functions # IfcEdit - CLI wrapper for ifcopenshell.api mutation functions
# Copyright (C) 2025 Bruno Postle <bruno@postle.net> # Copyright (C) 2025 Bruno Postle <bruno@postle.net>
# #
# This file is part of IfcApi. # This file is part of IfcEdit.
# #
# IfcApi is free software: you can redistribute it and/or modify # IfcEdit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# IfcApi is distributed in the hope that it will be useful, # IfcEdit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcApi. If not, see <http://www.gnu.org/licenses/>. # along with IfcEdit. If not, see <http://www.gnu.org/licenses/>.
__version__ = version = "0.0.0" __version__ = version = "0.0.0"
@@ -1,20 +1,20 @@
# IfcApi - CLI wrapper for ifcopenshell.api mutation functions # IfcEdit - CLI wrapper for ifcopenshell.api mutation functions
# Copyright (C) 2025 Bruno Postle <bruno@postle.net> # Copyright (C) 2025 Bruno Postle <bruno@postle.net>
# #
# This file is part of IfcApi. # This file is part of IfcEdit.
# #
# IfcApi is free software: you can redistribute it and/or modify # IfcEdit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# IfcApi is distributed in the hope that it will be useful, # IfcEdit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcApi. If not, see <http://www.gnu.org/licenses/>. # along with IfcEdit. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
@@ -24,8 +24,8 @@ import sys
import ifcopenshell import ifcopenshell
from ifcapi.discover import function_docs, list_functions, list_modules from ifcedit.discover import function_docs, list_functions, list_modules
from ifcapi.run import run_api from ifcedit.run import run_api
def format_output(data, fmt: str) -> str: def format_output(data, fmt: str) -> str:
@@ -138,7 +138,7 @@ def _parse_extra_args(extra: list[str]) -> dict[str, str]:
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
prog="ifcapi", prog="ifcedit",
description="CLI wrapper for ifcopenshell.api IFC model mutation functions", description="CLI wrapper for ifcopenshell.api IFC model mutation functions",
) )
parser.add_argument( parser.add_argument(
@@ -1,20 +1,20 @@
# IfcApi - CLI wrapper for ifcopenshell.api mutation functions # IfcEdit - CLI wrapper for ifcopenshell.api mutation functions
# Copyright (C) 2025 Bruno Postle <bruno@postle.net> # Copyright (C) 2025 Bruno Postle <bruno@postle.net>
# #
# This file is part of IfcApi. # This file is part of IfcEdit.
# #
# IfcApi is free software: you can redistribute it and/or modify # IfcEdit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# IfcApi is distributed in the hope that it will be useful, # IfcEdit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcApi. If not, see <http://www.gnu.org/licenses/>. # along with IfcEdit. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
@@ -1,20 +1,20 @@
# IfcApi - CLI wrapper for ifcopenshell.api mutation functions # IfcEdit - CLI wrapper for ifcopenshell.api mutation functions
# Copyright (C) 2025 Bruno Postle <bruno@postle.net> # Copyright (C) 2025 Bruno Postle <bruno@postle.net>
# #
# This file is part of IfcApi. # This file is part of IfcEdit.
# #
# IfcApi is free software: you can redistribute it and/or modify # IfcEdit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# IfcApi is distributed in the hope that it will be useful, # IfcEdit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcApi. If not, see <http://www.gnu.org/licenses/>. # along with IfcEdit. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
@@ -55,12 +55,14 @@ def list_modules() -> list[dict]:
description = "" description = ""
if mod.__doc__: if mod.__doc__:
description = mod.__doc__.strip().split("\n")[0] description = mod.__doc__.strip().split("\n")[0]
modules.append({ modules.append(
"module": child.name, {
"description": description, "module": child.name,
"functions": list(all_names), "description": description,
"count": len(all_names), "functions": list(all_names),
}) "count": len(all_names),
}
)
return modules return modules
@@ -80,11 +82,13 @@ def list_functions(module: str) -> list[dict]:
if fn.__doc__: if fn.__doc__:
description = fn.__doc__.strip().split("\n")[0] description = fn.__doc__.strip().split("\n")[0]
params = _extract_params(fn) params = _extract_params(fn)
functions.append({ functions.append(
"name": name, {
"description": description, "name": name,
"params": params, "description": description,
}) "params": params,
}
)
return functions return functions
@@ -1,20 +1,20 @@
# IfcApi - CLI wrapper for ifcopenshell.api mutation functions # IfcEdit - CLI wrapper for ifcopenshell.api mutation functions
# Copyright (C) 2025 Bruno Postle <bruno@postle.net> # Copyright (C) 2025 Bruno Postle <bruno@postle.net>
# #
# This file is part of IfcApi. # This file is part of IfcEdit.
# #
# IfcApi is free software: you can redistribute it and/or modify # IfcEdit is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by # it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or # the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version. # (at your option) any later version.
# #
# IfcApi is distributed in the hope that it will be useful, # IfcEdit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details. # GNU Lesser General Public License for more details.
# #
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with IfcApi. If not, see <http://www.gnu.org/licenses/>. # along with IfcEdit. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations from __future__ import annotations
@@ -24,7 +24,7 @@ import typing
import ifcopenshell import ifcopenshell
from ifcapi.coerce import coerce_value from ifcedit.coerce import coerce_value
def run_api( def run_api(
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "ifcapi" name = "ifcedit"
version = "0.0.0" version = "0.0.0"
authors = [ authors = [
{ name="Bruno Postle", email="bruno@postle.net" }, { name="Bruno Postle", email="bruno@postle.net" },
@@ -23,7 +23,7 @@ Documentation = "https://docs.ifcopenshell.org"
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues" Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["ifcapi*"] include = ["ifcedit*"]
exclude = ["test*"] exclude = ["test*"]
[tool.ruff] [tool.ruff]
@@ -5,7 +5,7 @@ import pytest
import ifcopenshell import ifcopenshell
from ifcapi.coerce import coerce_value from ifcedit.coerce import coerce_value
class TestStringCoercion: class TestStringCoercion:
@@ -1,4 +1,4 @@
from ifcapi.discover import function_docs, list_functions, list_modules from ifcedit.discover import function_docs, list_functions, list_modules
class TestListModules: class TestListModules:
@@ -5,10 +5,10 @@ import sys
import pytest import pytest
def run_ifcapi(*args): def run_ifcedit(*args):
"""Run ifcapi as a subprocess and return (stdout, stderr, returncode).""" """Run ifcedit as a subprocess and return (stdout, stderr, returncode)."""
result = subprocess.run( result = subprocess.run(
[sys.executable, "-m", "ifcapi", *args], [sys.executable, "-m", "ifcedit", *args],
capture_output=True, capture_output=True,
text=True, text=True,
) )
@@ -17,7 +17,7 @@ def run_ifcapi(*args):
class TestListCommand: class TestListCommand:
def test_list_all_modules(self): def test_list_all_modules(self):
stdout, stderr, rc = run_ifcapi("list") stdout, stderr, rc = run_ifcedit("list")
assert rc == 0 assert rc == 0
data = json.loads(stdout) data = json.loads(stdout)
assert isinstance(data, list) assert isinstance(data, list)
@@ -26,7 +26,7 @@ class TestListCommand:
assert "spatial" in module_names assert "spatial" in module_names
def test_list_module_functions(self): def test_list_module_functions(self):
stdout, stderr, rc = run_ifcapi("list", "root") stdout, stderr, rc = run_ifcedit("list", "root")
assert rc == 0 assert rc == 0
data = json.loads(stdout) data = json.loads(stdout)
assert isinstance(data, list) assert isinstance(data, list)
@@ -34,14 +34,14 @@ class TestListCommand:
assert "create_entity" in names assert "create_entity" in names
def test_list_text_format(self): def test_list_text_format(self):
stdout, stderr, rc = run_ifcapi("--format", "text", "list") stdout, stderr, rc = run_ifcedit("--format", "text", "list")
assert rc == 0 assert rc == 0
assert "root" in stdout assert "root" in stdout
class TestDocsCommand: class TestDocsCommand:
def test_docs_create_entity(self): def test_docs_create_entity(self):
stdout, stderr, rc = run_ifcapi("docs", "root.create_entity") stdout, stderr, rc = run_ifcedit("docs", "root.create_entity")
assert rc == 0 assert rc == 0
data = json.loads(stdout) data = json.loads(stdout)
assert data["module"] == "root" assert data["module"] == "root"
@@ -49,18 +49,18 @@ class TestDocsCommand:
assert "params" in data assert "params" in data
def test_docs_invalid_path(self): def test_docs_invalid_path(self):
stdout, stderr, rc = run_ifcapi("docs", "invalid_path") stdout, stderr, rc = run_ifcedit("docs", "invalid_path")
assert rc != 0 assert rc != 0
assert "module.function" in stderr assert "module.function" in stderr
def test_docs_unknown_function(self): def test_docs_unknown_function(self):
stdout, stderr, rc = run_ifcapi("docs", "root.nonexistent") stdout, stderr, rc = run_ifcedit("docs", "root.nonexistent")
assert rc != 0 assert rc != 0
class TestRunCommand: class TestRunCommand:
def test_create_entity(self, model_file): def test_create_entity(self, model_file):
stdout, stderr, rc = run_ifcapi( stdout, stderr, rc = run_ifcedit(
"run", model_file, "root.create_entity", "--ifc_class", "IfcWall", "--name", "CLIWall" "run", model_file, "root.create_entity", "--ifc_class", "IfcWall", "--name", "CLIWall"
) )
assert rc == 0, f"stderr: {stderr}" assert rc == 0, f"stderr: {stderr}"
@@ -70,9 +70,7 @@ class TestRunCommand:
assert data["result"]["name"] == "CLIWall" assert data["result"]["name"] == "CLIWall"
def test_dry_run(self, model_file): def test_dry_run(self, model_file):
stdout, stderr, rc = run_ifcapi( stdout, stderr, rc = run_ifcedit("run", model_file, "root.create_entity", "--dry-run", "--ifc_class", "IfcWall")
"run", model_file, "root.create_entity", "--dry-run", "--ifc_class", "IfcWall"
)
assert rc == 0 assert rc == 0
data = json.loads(stdout) data = json.loads(stdout)
assert data["ok"] is True assert data["ok"] is True
@@ -80,9 +78,7 @@ class TestRunCommand:
def test_output_to_different_file(self, model_file, tmp_path): def test_output_to_different_file(self, model_file, tmp_path):
output = str(tmp_path / "output.ifc") output = str(tmp_path / "output.ifc")
stdout, stderr, rc = run_ifcapi( stdout, stderr, rc = run_ifcedit("run", model_file, "root.create_entity", "-o", output, "--ifc_class", "IfcSlab")
"run", model_file, "root.create_entity", "-o", output, "--ifc_class", "IfcSlab"
)
assert rc == 0, f"stderr: {stderr}" assert rc == 0, f"stderr: {stderr}"
data = json.loads(stdout) data = json.loads(stdout)
assert data["ok"] is True assert data["ok"] is True
@@ -92,10 +88,10 @@ class TestRunCommand:
assert os.path.exists(output) assert os.path.exists(output)
def test_run_error_bad_function(self, model_file): def test_run_error_bad_function(self, model_file):
stdout, stderr, rc = run_ifcapi("run", model_file, "root.nonexistent") stdout, stderr, rc = run_ifcedit("run", model_file, "root.nonexistent")
assert rc != 0 assert rc != 0
def test_run_invalid_function_path(self, model_file): def test_run_invalid_function_path(self, model_file):
stdout, stderr, rc = run_ifcapi("run", model_file, "invalid_path") stdout, stderr, rc = run_ifcedit("run", model_file, "invalid_path")
assert rc != 0 assert rc != 0
assert "module.function" in stderr assert "module.function" in stderr
@@ -1,7 +1,7 @@
import ifcopenshell.api.pset import ifcopenshell.api.pset
import ifcopenshell.api.root import ifcopenshell.api.root
from ifcapi.run import run_api, serialize_result from ifcedit.run import run_api, serialize_result
class TestRunApi: class TestRunApi:
+252
View File
@@ -0,0 +1,252 @@
# ifcquery
A CLI tool for querying and inspecting IFC building models. All output is
structured JSON (or human-readable text), making it easy to pipe into other
tools or scripts.
## Installation
```bash
pip install ifcquery
```
Requires `ifcopenshell`. The `clash` subcommand additionally requires the
IfcOpenShell C++ geometry bindings (`ifcopenshell.geom`).
## Usage
```
ifcquery <ifc_file> <command> [options] [--format json|text]
```
The `--format` flag controls output. Default is `json`; use `text` for
indented human-readable output.
## Subcommands
### summary
Get a model overview: schema version, entity counts, and project info.
```bash
ifcquery model.ifc summary
```
```json
{
"schema": "IFC4",
"total_entities": 1847,
"project": {
"id": 1,
"name": "Office Building",
"description": null
},
"types": {
"IfcWall": 42,
"IfcSlab": 12,
"IfcWindow": 36
}
}
```
### tree
Display the spatial hierarchy from IfcProject down through sites, buildings,
storeys, and their contained elements.
```bash
ifcquery model.ifc tree
```
```json
{
"id": 1,
"type": "IfcProject",
"name": "Office Building",
"children": [
{
"id": 2,
"type": "IfcSite",
"name": "Default Site",
"children": [
{
"id": 3,
"type": "IfcBuilding",
"name": "Main Building",
"children": [
{
"id": 4,
"type": "IfcBuildingStorey",
"name": "Ground Floor",
"elements": [
{"id": 10, "type": "IfcWall", "name": "Wall001"},
{"id": 11, "type": "IfcSlab", "name": "Floor001"}
]
}
]
}
]
}
]
}
```
### info
Get detailed information about a specific element by step ID.
```bash
ifcquery model.ifc info 10
ifcquery model.ifc info '#10'
```
Returns attributes, property sets, type relationship, material assignment,
spatial container, and placement matrix.
```json
{
"id": 10,
"type": "IfcWall",
"attributes": {
"Name": "Wall001",
"Description": null,
"ObjectType": "LOADBEARING"
},
"property_sets": {
"Pset_WallCommon": {
"IsExternal": true,
"FireRating": "2HR"
}
},
"element_type": {"id": 50, "type": "IfcWallType", "name": "Standard"},
"material": {"id": 60, "type": "IfcMaterial", "name": "Concrete"},
"container": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"},
"placement": [
[1.0, 0.0, 0.0, 5.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0]
]
}
```
### select
Filter elements using the ifcopenshell selector syntax.
```bash
ifcquery model.ifc select 'IfcWall'
ifcquery model.ifc select 'IfcWall, IfcSlab'
```
```json
[
{"id": 10, "type": "IfcWall", "name": "Wall001"},
{"id": 11, "type": "IfcWall", "name": "Wall002"},
{"id": 20, "type": "IfcSlab", "name": "Floor001"}
]
```
Results are sorted by ID.
### relations
Show all relationships for an element, organized by category: hierarchy,
children, type relationships, groups, systems, material, and connections.
```bash
ifcquery model.ifc relations 10
```
```json
{
"id": 10,
"type": "IfcWall",
"name": "Wall001",
"hierarchy": {
"parent": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"},
"container": {"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"}
},
"children": {
"openings": [{"id": 30, "type": "IfcOpeningElement", "name": "Opening01"}]
},
"type_relationship": {
"type_of": {"id": 50, "type": "IfcWallType", "name": "Standard"}
},
"material": {"id": 60, "type": "IfcMaterial", "name": "Concrete"}
}
```
Empty categories are omitted from output.
Use `--traverse up` to walk the spatial hierarchy from the element up to
IfcProject:
```bash
ifcquery model.ifc relations 10 --traverse up
```
```json
[
{"id": 10, "type": "IfcWall", "name": "Wall001"},
{"id": 4, "type": "IfcBuildingStorey", "name": "Ground Floor"},
{"id": 3, "type": "IfcBuilding", "name": "Main Building"},
{"id": 2, "type": "IfcSite", "name": "Default Site"},
{"id": 1, "type": "IfcProject", "name": "Office Building"}
]
```
### clash
Check a single element for geometric intersections and clearance violations
against other elements.
```bash
ifcquery model.ifc clash 10
ifcquery model.ifc clash 10 --clearance 0.5
ifcquery model.ifc clash 10 --scope all --tolerance 0.001
```
Options:
- `--clearance <meters>` -- minimum clearance distance to check
- `--tolerance <meters>` -- intersection tolerance (default: 0.002)
- `--scope {storey,all}` -- check against same-storey elements or all elements (default: storey)
```json
{
"element": {"id": 10, "type": "IfcWall", "name": "Wall001"},
"scope": "storey",
"pass": false,
"checks": {
"intersection": {
"pass": false,
"tolerance": 0.002,
"clashes": [
{
"element": {"id": 11, "type": "IfcWall", "name": "Wall002"},
"type": "intersection",
"distance": 0.0,
"p1": [2.5, 2.5, 1.5],
"p2": [2.5, 2.5, 1.5]
}
]
},
"clearance": {
"pass": true,
"clearance": 0.5,
"clashes": []
}
}
}
```
Requires the IfcOpenShell C++ geometry bindings.
## Error handling
Errors are written to stderr. Exit code is 0 on success, 1 on error.
## License
LGPLv3+ -- see the IfcOpenShell project license.
+4 -12
View File
@@ -62,9 +62,7 @@ def _get_scope_elements(
return elements, "all" return elements, "all"
def _build_tree( def _build_tree(model: ifcopenshell.file, elements: set[ifcopenshell.entity_instance]) -> ifcopenshell.geom.tree | None:
model: ifcopenshell.file, elements: set[ifcopenshell.entity_instance]
) -> ifcopenshell.geom.tree | None:
"""Build geometry tree for given elements using iterator. """Build geometry tree for given elements using iterator.
Returns None if iterator fails to initialize (no geometry available). Returns None if iterator fails to initialize (no geometry available).
@@ -72,9 +70,7 @@ def _build_tree(
geom_settings = ifcopenshell.geom.settings() geom_settings = ifcopenshell.geom.settings()
geom_settings.set("use-world-coords", True) geom_settings.set("use-world-coords", True)
geom_tree = ifcopenshell.geom.tree() geom_tree = ifcopenshell.geom.tree()
iterator = ifcopenshell.geom.iterator( iterator = ifcopenshell.geom.iterator(geom_settings, model, multiprocessing.cpu_count(), include=list(elements))
geom_settings, model, multiprocessing.cpu_count(), include=list(elements)
)
if not iterator.initialize(): if not iterator.initialize():
return None return None
while True: while True:
@@ -84,9 +80,7 @@ def _build_tree(
return geom_tree return geom_tree
def _format_clash( def _format_clash(clash_result, geom_tree: ifcopenshell.geom.tree, model: ifcopenshell.file) -> dict[str, Any]:
clash_result, geom_tree: ifcopenshell.geom.tree, model: ifcopenshell.file
) -> dict[str, Any]:
"""Format a single clash result to dict.""" """Format a single clash result to dict."""
# clash result .a/.b are C++ wrapper entity_instances without .Name; # clash result .a/.b are C++ wrapper entity_instances without .Name;
# look up the Python entity from the model by id for proper serialization # look up the Python entity from the model by id for proper serialization
@@ -124,9 +118,7 @@ def clash(
if not scope_elements: if not scope_elements:
result["pass"] = True result["pass"] = True
result["checks"] = { result["checks"] = {"intersection": {"pass": True, "tolerance": tolerance, "clashes": []}}
"intersection": {"pass": True, "tolerance": tolerance, "clashes": []}
}
if clearance is not None: if clearance is not None:
result["checks"]["clearance"] = {"pass": True, "clearance": clearance, "clashes": []} result["checks"]["clearance"] = {"pass": True, "clearance": clearance, "clashes": []}
return result return result
+1 -3
View File
@@ -142,9 +142,7 @@ class TestNoClashes:
# Create a model with a single wall # Create a model with a single wall
f = ifcopenshell.api.project.create_file() f = ifcopenshell.api.project.create_file()
ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0]
ifcopenshell.api.owner.settings.get_application = lambda ifc: ( ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0]
ifc.by_type("IfcApplication") or [None]
)[0]
project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P") project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P")
ifcopenshell.api.unit.assign_unit(f) ifcopenshell.api.unit.assign_unit(f)
site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="S") site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="S")