mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-11 18:16:40 +00:00
Rename ifcapi to ifcedit, README and black
This commit is contained in:
@@ -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>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (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
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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"
|
||||
@@ -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>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (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
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -24,8 +24,8 @@ import sys
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
from ifcapi.discover import function_docs, list_functions, list_modules
|
||||
from ifcapi.run import run_api
|
||||
from ifcedit.discover import function_docs, list_functions, list_modules
|
||||
from ifcedit.run import run_api
|
||||
|
||||
|
||||
def format_output(data, fmt: str) -> str:
|
||||
@@ -138,7 +138,7 @@ def _parse_extra_args(extra: list[str]) -> dict[str, str]:
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
prog="ifcapi",
|
||||
prog="ifcedit",
|
||||
description="CLI wrapper for ifcopenshell.api IFC model mutation functions",
|
||||
)
|
||||
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>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (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
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -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>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (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
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -55,12 +55,14 @@ def list_modules() -> list[dict]:
|
||||
description = ""
|
||||
if mod.__doc__:
|
||||
description = mod.__doc__.strip().split("\n")[0]
|
||||
modules.append({
|
||||
"module": child.name,
|
||||
"description": description,
|
||||
"functions": list(all_names),
|
||||
"count": len(all_names),
|
||||
})
|
||||
modules.append(
|
||||
{
|
||||
"module": child.name,
|
||||
"description": description,
|
||||
"functions": list(all_names),
|
||||
"count": len(all_names),
|
||||
}
|
||||
)
|
||||
return modules
|
||||
|
||||
|
||||
@@ -80,11 +82,13 @@ def list_functions(module: str) -> list[dict]:
|
||||
if fn.__doc__:
|
||||
description = fn.__doc__.strip().split("\n")[0]
|
||||
params = _extract_params(fn)
|
||||
functions.append({
|
||||
"name": name,
|
||||
"description": description,
|
||||
"params": params,
|
||||
})
|
||||
functions.append(
|
||||
{
|
||||
"name": name,
|
||||
"description": description,
|
||||
"params": params,
|
||||
}
|
||||
)
|
||||
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>
|
||||
#
|
||||
# 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
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (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
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# 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
|
||||
|
||||
@@ -24,7 +24,7 @@ import typing
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
from ifcapi.coerce import coerce_value
|
||||
from ifcedit.coerce import coerce_value
|
||||
|
||||
|
||||
def run_api(
|
||||
@@ -3,7 +3,7 @@ requires = ["setuptools>=61.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "ifcapi"
|
||||
name = "ifcedit"
|
||||
version = "0.0.0"
|
||||
authors = [
|
||||
{ name="Bruno Postle", email="bruno@postle.net" },
|
||||
@@ -23,7 +23,7 @@ Documentation = "https://docs.ifcopenshell.org"
|
||||
Issues = "https://github.com/IfcOpenShell/IfcOpenShell/issues"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
include = ["ifcapi*"]
|
||||
include = ["ifcedit*"]
|
||||
exclude = ["test*"]
|
||||
|
||||
[tool.ruff]
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
import ifcopenshell
|
||||
|
||||
from ifcapi.coerce import coerce_value
|
||||
from ifcedit.coerce import coerce_value
|
||||
|
||||
|
||||
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:
|
||||
@@ -5,10 +5,10 @@ import sys
|
||||
import pytest
|
||||
|
||||
|
||||
def run_ifcapi(*args):
|
||||
"""Run ifcapi as a subprocess and return (stdout, stderr, returncode)."""
|
||||
def run_ifcedit(*args):
|
||||
"""Run ifcedit as a subprocess and return (stdout, stderr, returncode)."""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "ifcapi", *args],
|
||||
[sys.executable, "-m", "ifcedit", *args],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
@@ -17,7 +17,7 @@ def run_ifcapi(*args):
|
||||
|
||||
class TestListCommand:
|
||||
def test_list_all_modules(self):
|
||||
stdout, stderr, rc = run_ifcapi("list")
|
||||
stdout, stderr, rc = run_ifcedit("list")
|
||||
assert rc == 0
|
||||
data = json.loads(stdout)
|
||||
assert isinstance(data, list)
|
||||
@@ -26,7 +26,7 @@ class TestListCommand:
|
||||
assert "spatial" in module_names
|
||||
|
||||
def test_list_module_functions(self):
|
||||
stdout, stderr, rc = run_ifcapi("list", "root")
|
||||
stdout, stderr, rc = run_ifcedit("list", "root")
|
||||
assert rc == 0
|
||||
data = json.loads(stdout)
|
||||
assert isinstance(data, list)
|
||||
@@ -34,14 +34,14 @@ class TestListCommand:
|
||||
assert "create_entity" in names
|
||||
|
||||
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 "root" in stdout
|
||||
|
||||
|
||||
class TestDocsCommand:
|
||||
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
|
||||
data = json.loads(stdout)
|
||||
assert data["module"] == "root"
|
||||
@@ -49,18 +49,18 @@ class TestDocsCommand:
|
||||
assert "params" in data
|
||||
|
||||
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 "module.function" in stderr
|
||||
|
||||
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
|
||||
|
||||
|
||||
class TestRunCommand:
|
||||
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"
|
||||
)
|
||||
assert rc == 0, f"stderr: {stderr}"
|
||||
@@ -70,9 +70,7 @@ class TestRunCommand:
|
||||
assert data["result"]["name"] == "CLIWall"
|
||||
|
||||
def test_dry_run(self, model_file):
|
||||
stdout, stderr, rc = run_ifcapi(
|
||||
"run", model_file, "root.create_entity", "--dry-run", "--ifc_class", "IfcWall"
|
||||
)
|
||||
stdout, stderr, rc = run_ifcedit("run", model_file, "root.create_entity", "--dry-run", "--ifc_class", "IfcWall")
|
||||
assert rc == 0
|
||||
data = json.loads(stdout)
|
||||
assert data["ok"] is True
|
||||
@@ -80,9 +78,7 @@ class TestRunCommand:
|
||||
|
||||
def test_output_to_different_file(self, model_file, tmp_path):
|
||||
output = str(tmp_path / "output.ifc")
|
||||
stdout, stderr, rc = run_ifcapi(
|
||||
"run", model_file, "root.create_entity", "-o", output, "--ifc_class", "IfcSlab"
|
||||
)
|
||||
stdout, stderr, rc = run_ifcedit("run", model_file, "root.create_entity", "-o", output, "--ifc_class", "IfcSlab")
|
||||
assert rc == 0, f"stderr: {stderr}"
|
||||
data = json.loads(stdout)
|
||||
assert data["ok"] is True
|
||||
@@ -92,10 +88,10 @@ class TestRunCommand:
|
||||
assert os.path.exists(output)
|
||||
|
||||
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
|
||||
|
||||
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 "module.function" in stderr
|
||||
@@ -1,7 +1,7 @@
|
||||
import ifcopenshell.api.pset
|
||||
import ifcopenshell.api.root
|
||||
|
||||
from ifcapi.run import run_api, serialize_result
|
||||
from ifcedit.run import run_api, serialize_result
|
||||
|
||||
|
||||
class TestRunApi:
|
||||
@@ -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.
|
||||
@@ -62,9 +62,7 @@ def _get_scope_elements(
|
||||
return elements, "all"
|
||||
|
||||
|
||||
def _build_tree(
|
||||
model: ifcopenshell.file, elements: set[ifcopenshell.entity_instance]
|
||||
) -> ifcopenshell.geom.tree | None:
|
||||
def _build_tree(model: ifcopenshell.file, elements: set[ifcopenshell.entity_instance]) -> ifcopenshell.geom.tree | None:
|
||||
"""Build geometry tree for given elements using iterator.
|
||||
|
||||
Returns None if iterator fails to initialize (no geometry available).
|
||||
@@ -72,9 +70,7 @@ def _build_tree(
|
||||
geom_settings = ifcopenshell.geom.settings()
|
||||
geom_settings.set("use-world-coords", True)
|
||||
geom_tree = ifcopenshell.geom.tree()
|
||||
iterator = ifcopenshell.geom.iterator(
|
||||
geom_settings, model, multiprocessing.cpu_count(), include=list(elements)
|
||||
)
|
||||
iterator = ifcopenshell.geom.iterator(geom_settings, model, multiprocessing.cpu_count(), include=list(elements))
|
||||
if not iterator.initialize():
|
||||
return None
|
||||
while True:
|
||||
@@ -84,9 +80,7 @@ def _build_tree(
|
||||
return geom_tree
|
||||
|
||||
|
||||
def _format_clash(
|
||||
clash_result, geom_tree: ifcopenshell.geom.tree, model: ifcopenshell.file
|
||||
) -> dict[str, Any]:
|
||||
def _format_clash(clash_result, geom_tree: ifcopenshell.geom.tree, model: ifcopenshell.file) -> dict[str, Any]:
|
||||
"""Format a single clash result to dict."""
|
||||
# clash result .a/.b are C++ wrapper entity_instances without .Name;
|
||||
# look up the Python entity from the model by id for proper serialization
|
||||
@@ -124,9 +118,7 @@ def clash(
|
||||
|
||||
if not scope_elements:
|
||||
result["pass"] = True
|
||||
result["checks"] = {
|
||||
"intersection": {"pass": True, "tolerance": tolerance, "clashes": []}
|
||||
}
|
||||
result["checks"] = {"intersection": {"pass": True, "tolerance": tolerance, "clashes": []}}
|
||||
if clearance is not None:
|
||||
result["checks"]["clearance"] = {"pass": True, "clearance": clearance, "clashes": []}
|
||||
return result
|
||||
|
||||
@@ -142,9 +142,7 @@ class TestNoClashes:
|
||||
# Create a model with a single wall
|
||||
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_application = lambda ifc: (
|
||||
ifc.by_type("IfcApplication") or [None]
|
||||
)[0]
|
||||
ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0]
|
||||
project = ifcopenshell.api.root.create_entity(f, ifc_class="IfcProject", name="P")
|
||||
ifcopenshell.api.unit.assign_unit(f)
|
||||
site = ifcopenshell.api.root.create_entity(f, ifc_class="IfcSite", name="S")
|
||||
|
||||
Reference in New Issue
Block a user