mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-09 21:53:40 +00:00
ifcpatch - a static way to use ifcpatch
This commit is contained in:
@@ -57,6 +57,19 @@ example, we'll extract out all `IfcWall` elements.
|
|||||||
ifcpatch -i input.ifc -o output.ifc -r ExtractElements -a "IfcWall"
|
ifcpatch -i input.ifc -o output.ifc -r ExtractElements -a "IfcWall"
|
||||||
cat output.ifc
|
cat output.ifc
|
||||||
|
|
||||||
|
You can also alias it to a command:
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
alias ifcpatch='python -m ifcpatch'
|
||||||
|
|
||||||
|
Alternatively, you can package it as an executable.
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
python make.py
|
||||||
|
./dist/ifcpatch
|
||||||
|
|
||||||
Here is a minimal example of how to use IfcPatch as a library:
|
Here is a minimal example of how to use IfcPatch as a library:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@@ -71,18 +84,22 @@ Here is a minimal example of how to use IfcPatch as a library:
|
|||||||
})
|
})
|
||||||
ifcpatch.write(output, "output.ifc")
|
ifcpatch.write(output, "output.ifc")
|
||||||
|
|
||||||
You can also alias it to a command:
|
|
||||||
|
|
||||||
.. code-block:: bash
|
Alternatively, there is a less dynamic way to use IfcPatch
|
||||||
|
that allows seeing available arguments, their descriptions, types, default values, etc.
|
||||||
|
|
||||||
alias ifcpatch='python -m ifcpatch'
|
..code-block:: python
|
||||||
|
|
||||||
Alternatively, you can package it as an executable.
|
import ifcopenshell
|
||||||
|
import ifcpatch
|
||||||
|
from ifcpatch.recipes import ExtractElements
|
||||||
|
|
||||||
.. code-block:: bash
|
patcher = ExtractElements.Patcher(
|
||||||
|
ifcopenshell.open("input.ifc"),
|
||||||
python make.py
|
query="IfcWall",
|
||||||
./dist/ifcpatch
|
)
|
||||||
|
patcher.patch()
|
||||||
|
ifcpatch.write(patcher.get_output(), "output.ifc")
|
||||||
|
|
||||||
Patch recipes
|
Patch recipes
|
||||||
-------------
|
-------------
|
||||||
|
|||||||
@@ -45,7 +45,27 @@ class ArgumentsDict(TypedDict):
|
|||||||
arguments: NotRequired[Sequence[Any]]
|
arguments: NotRequired[Sequence[Any]]
|
||||||
|
|
||||||
|
|
||||||
def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
|
class BasePatcher:
|
||||||
|
def __init__(self, file: ifcopenshell.file, logger: Union[logging.Logger, None]):
|
||||||
|
self.file = file
|
||||||
|
self.logger = ensure_logger(logger)
|
||||||
|
|
||||||
|
def patch(self) -> None:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def get_output(self) -> Union[ifcopenshell.file, str, None]:
|
||||||
|
if hasattr(self, "file_patched"):
|
||||||
|
return self.file_patched # pyright: ignore[reportAttributeAccessIssue]
|
||||||
|
return self.file
|
||||||
|
|
||||||
|
|
||||||
|
def ensure_logger(logger: Union[logging.Logger, None] = None) -> logging.Logger:
|
||||||
|
if logger is not None:
|
||||||
|
return logger
|
||||||
|
return logging.getLogger("IFCPatch")
|
||||||
|
|
||||||
|
|
||||||
|
def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str, None]:
|
||||||
"""Execute a patch recipe
|
"""Execute a patch recipe
|
||||||
|
|
||||||
The details of how the patch recipe is executed depends on the definition of
|
The details of how the patch recipe is executed depends on the definition of
|
||||||
@@ -88,7 +108,7 @@ def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
|
|||||||
"""
|
"""
|
||||||
if "log" in args:
|
if "log" in args:
|
||||||
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
||||||
logger = logging.getLogger("IFCPatch")
|
logger = ensure_logger()
|
||||||
if recipe_dir := os.environ.get("IFCPATCH_RECIPE_DIR"):
|
if recipe_dir := os.environ.get("IFCPATCH_RECIPE_DIR"):
|
||||||
spec = importlib.util.spec_from_file_location(args["recipe"], os.path.join(recipe_dir, args["recipe"] + ".py"))
|
spec = importlib.util.spec_from_file_location(args["recipe"], os.path.join(recipe_dir, args["recipe"] + ".py"))
|
||||||
recipe = importlib.util.module_from_spec(spec)
|
recipe = importlib.util.module_from_spec(spec)
|
||||||
@@ -103,17 +123,17 @@ def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
|
|||||||
else:
|
else:
|
||||||
patcher = recipe.Patcher(args.get("file"), logger, arguments)
|
patcher = recipe.Patcher(args.get("file"), logger, arguments)
|
||||||
patcher.patch()
|
patcher.patch()
|
||||||
output = getattr(patcher, "file_patched", patcher.file)
|
output = BasePatcher.get_output(patcher)
|
||||||
return output
|
return output
|
||||||
|
|
||||||
|
|
||||||
def write(output: Union[ifcopenshell.file, str], filepath: Union[Path, str]) -> None:
|
def write(output: Union[ifcopenshell.file, str, None], filepath: Union[Path, str]) -> None:
|
||||||
"""Write the output of an IFC patch to a file
|
"""Write the output of an IFC patch to a file
|
||||||
|
|
||||||
Typically a patch output would be a patched IFC model file object, or as a
|
Typically a patch output would be a patched IFC model file object, or as a
|
||||||
string. This function lets you agnostically write that output to a filepath.
|
string. This function lets you agnostically write that output to a filepath.
|
||||||
|
|
||||||
:param output: The results from ifcpatch.execute()
|
:param output: The results from ``ifcpatch.execute()`` / ``Patcher.get_output()``
|
||||||
:param filepath: A filepath to where the results of the patched model should
|
:param filepath: A filepath to where the results of the patched model should
|
||||||
be written to.
|
be written to.
|
||||||
:return: None
|
:return: None
|
||||||
|
|||||||
@@ -22,9 +22,11 @@ import ifcopenshell.api.owner.settings
|
|||||||
import ifcopenshell.util.pset
|
import ifcopenshell.util.pset
|
||||||
import ifcopenshell.util.element
|
import ifcopenshell.util.element
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
|
import ifcpatch
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
import typing
|
import typing
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
LengthUnit = typing.Literal[
|
LengthUnit = typing.Literal[
|
||||||
"ATTOMETER",
|
"ATTOMETER",
|
||||||
@@ -50,11 +52,11 @@ LengthUnit = typing.Literal[
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher(ifcpatch.BasePatcher):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
logger: Logger,
|
logger: Union[Logger, None] = None,
|
||||||
unit: LengthUnit = "METER",
|
unit: LengthUnit = "METER",
|
||||||
):
|
):
|
||||||
"""Converts the length unit of a model to the specified unit
|
"""Converts the length unit of a model to the specified unit
|
||||||
@@ -74,8 +76,7 @@ class Patcher:
|
|||||||
# Convert to feet
|
# Convert to feet
|
||||||
model = ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertLengthUnit", "arguments": ["FOOT"]})
|
model = ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertLengthUnit", "arguments": ["FOOT"]})
|
||||||
"""
|
"""
|
||||||
self.file = file
|
super().__init__(file, logger)
|
||||||
self.logger = logger
|
|
||||||
self.unit = unit
|
self.unit = unit
|
||||||
self.file_patched: ifcopenshell.file
|
self.file_patched: ifcopenshell.file
|
||||||
|
|
||||||
|
|||||||
@@ -21,15 +21,16 @@ import ifcopenshell.api
|
|||||||
import ifcopenshell.api.project
|
import ifcopenshell.api.project
|
||||||
import ifcopenshell.guid
|
import ifcopenshell.guid
|
||||||
import ifcopenshell.util.selector
|
import ifcopenshell.util.selector
|
||||||
|
import ifcpatch
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher(ifcpatch.BasePatcher):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
logger: Logger,
|
logger: Union[Logger, None] = None,
|
||||||
query: str = "IfcWall",
|
query: str = "IfcWall",
|
||||||
assume_asset_uniqueness_by_name: bool = True,
|
assume_asset_uniqueness_by_name: bool = True,
|
||||||
):
|
):
|
||||||
@@ -62,8 +63,7 @@ class Patcher:
|
|||||||
# Extract all walls and slabs
|
# Extract all walls and slabs
|
||||||
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ExtractElements", "arguments": ["IfcWall, IfcSlab"]})
|
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ExtractElements", "arguments": ["IfcWall, IfcSlab"]})
|
||||||
"""
|
"""
|
||||||
self.file = file
|
super().__init__(file, logger)
|
||||||
self.logger = logger
|
|
||||||
self.query = query
|
self.query = query
|
||||||
self.assume_asset_uniqueness_by_name = assume_asset_uniqueness_by_name
|
self.assume_asset_uniqueness_by_name = assume_asset_uniqueness_by_name
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import ifcopenshell.util.placement
|
|||||||
import ifcopenshell.util.schema
|
import ifcopenshell.util.schema
|
||||||
import ifcopenshell.util.shape
|
import ifcopenshell.util.shape
|
||||||
import ifcopenshell.util.unit
|
import ifcopenshell.util.unit
|
||||||
|
import ifcpatch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, TYPE_CHECKING, Literal, Union
|
from typing import Any, TYPE_CHECKING, Literal, Union
|
||||||
from typing_extensions import assert_never
|
from typing_extensions import assert_never
|
||||||
@@ -61,11 +62,11 @@ else:
|
|||||||
DEFAULT_DATABASE_NAME = "database"
|
DEFAULT_DATABASE_NAME = "database"
|
||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher(ifcpatch.BasePatcher):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
logger: logging.Logger,
|
logger: Union[logging.Logger, None] = None,
|
||||||
sql_type: SQLTypes = "SQLite",
|
sql_type: SQLTypes = "SQLite",
|
||||||
host: str = "localhost",
|
host: str = "localhost",
|
||||||
username: str = "root",
|
username: str = "root",
|
||||||
@@ -116,7 +117,7 @@ class Patcher:
|
|||||||
{"input": "input.ifc", "file": model, "recipe": "Ifc2Sql", "arguments": ["sqlite"]}
|
{"input": "input.ifc", "file": model, "recipe": "Ifc2Sql", "arguments": ["sqlite"]}
|
||||||
)
|
)
|
||||||
"""
|
"""
|
||||||
self.file = file
|
super().__init__(file, logger)
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
self.sql_type: Literal["sqlite", "mysql"] = sql_type.lower()
|
self.sql_type: Literal["sqlite", "mysql"] = sql_type.lower()
|
||||||
self.host = host
|
self.host = host
|
||||||
|
|||||||
@@ -18,15 +18,17 @@
|
|||||||
|
|
||||||
import ifcopenshell
|
import ifcopenshell
|
||||||
import ifcopenshell.util.schema
|
import ifcopenshell.util.schema
|
||||||
|
import ifcpatch
|
||||||
import typing
|
import typing
|
||||||
|
from typing import Union
|
||||||
from logging import Logger
|
from logging import Logger
|
||||||
|
|
||||||
|
|
||||||
class Patcher:
|
class Patcher(ifcpatch.BasePatcher):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
file: ifcopenshell.file,
|
file: ifcopenshell.file,
|
||||||
logger: Logger,
|
logger: Union[Logger, None] = None,
|
||||||
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
||||||
):
|
):
|
||||||
"""Migrate from one IFC version to another
|
"""Migrate from one IFC version to another
|
||||||
@@ -43,8 +45,7 @@ class Patcher:
|
|||||||
# Upgrade an IFC2X3 model to IFC4
|
# Upgrade an IFC2X3 model to IFC4
|
||||||
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Migrate", "arguments": ["IFC4"]})
|
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Migrate", "arguments": ["IFC4"]})
|
||||||
"""
|
"""
|
||||||
self.file = file
|
super().__init__(file, logger)
|
||||||
self.logger = logger
|
|
||||||
self.schema = schema
|
self.schema = schema
|
||||||
|
|
||||||
def patch(self):
|
def patch(self):
|
||||||
|
|||||||
@@ -16,6 +16,8 @@
|
|||||||
# 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 IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
import ifcopenshell.api.root
|
||||||
import ifcpatch
|
import ifcpatch
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
@@ -32,3 +34,26 @@ class Test:
|
|||||||
expected_keys = ("class_", "description", "output", "inputs")
|
expected_keys = ("class_", "description", "output", "inputs")
|
||||||
for key in expected_keys:
|
for key in expected_keys:
|
||||||
assert key in docs
|
assert key in docs
|
||||||
|
|
||||||
|
def test_static_ifcpatch_execution(self):
|
||||||
|
from ifcpatch.recipes import ExtractElements
|
||||||
|
|
||||||
|
ifc_file = ifcopenshell.file()
|
||||||
|
project = ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject")
|
||||||
|
wall = ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcWall")
|
||||||
|
|
||||||
|
patcher = ExtractElements.Patcher(ifc_file, query="IfcWall")
|
||||||
|
patcher.patch()
|
||||||
|
|
||||||
|
output = patcher.get_output()
|
||||||
|
assert isinstance(output, ifcopenshell.file)
|
||||||
|
assert output.by_type("IfcProject")[0].GlobalId == project.GlobalId
|
||||||
|
assert output.by_type("IfcWall")[0].GlobalId == wall.GlobalId
|
||||||
|
|
||||||
|
output_path = Path(tempfile.mktemp())
|
||||||
|
try:
|
||||||
|
assert not output_path.exists()
|
||||||
|
ifcpatch.write(patcher.get_output(), output_path)
|
||||||
|
assert output_path.exists()
|
||||||
|
finally:
|
||||||
|
output_path.unlink()
|
||||||
|
|||||||
Reference in New Issue
Block a user