mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +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"
|
||||
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:
|
||||
|
||||
.. 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")
|
||||
|
||||
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
|
||||
|
||||
python make.py
|
||||
./dist/ifcpatch
|
||||
patcher = ExtractElements.Patcher(
|
||||
ifcopenshell.open("input.ifc"),
|
||||
query="IfcWall",
|
||||
)
|
||||
patcher.patch()
|
||||
ifcpatch.write(patcher.get_output(), "output.ifc")
|
||||
|
||||
Patch recipes
|
||||
-------------
|
||||
|
||||
@@ -45,7 +45,27 @@ class ArgumentsDict(TypedDict):
|
||||
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
|
||||
|
||||
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:
|
||||
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"):
|
||||
spec = importlib.util.spec_from_file_location(args["recipe"], os.path.join(recipe_dir, args["recipe"] + ".py"))
|
||||
recipe = importlib.util.module_from_spec(spec)
|
||||
@@ -103,17 +123,17 @@ def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
|
||||
else:
|
||||
patcher = recipe.Patcher(args.get("file"), logger, arguments)
|
||||
patcher.patch()
|
||||
output = getattr(patcher, "file_patched", patcher.file)
|
||||
output = BasePatcher.get_output(patcher)
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
: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
|
||||
be written to.
|
||||
:return: None
|
||||
|
||||
@@ -22,9 +22,11 @@ import ifcopenshell.api.owner.settings
|
||||
import ifcopenshell.util.pset
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.unit
|
||||
import ifcpatch
|
||||
from logging import Logger
|
||||
|
||||
import typing
|
||||
from typing import Union
|
||||
|
||||
LengthUnit = typing.Literal[
|
||||
"ATTOMETER",
|
||||
@@ -50,11 +52,11 @@ LengthUnit = typing.Literal[
|
||||
]
|
||||
|
||||
|
||||
class Patcher:
|
||||
class Patcher(ifcpatch.BasePatcher):
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
logger: Logger,
|
||||
logger: Union[Logger, None] = None,
|
||||
unit: LengthUnit = "METER",
|
||||
):
|
||||
"""Converts the length unit of a model to the specified unit
|
||||
@@ -74,8 +76,7 @@ class Patcher:
|
||||
# Convert to feet
|
||||
model = ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertLengthUnit", "arguments": ["FOOT"]})
|
||||
"""
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
super().__init__(file, logger)
|
||||
self.unit = unit
|
||||
self.file_patched: ifcopenshell.file
|
||||
|
||||
|
||||
@@ -21,15 +21,16 @@ import ifcopenshell.api
|
||||
import ifcopenshell.api.project
|
||||
import ifcopenshell.guid
|
||||
import ifcopenshell.util.selector
|
||||
import ifcpatch
|
||||
from typing import Union
|
||||
from logging import Logger
|
||||
|
||||
|
||||
class Patcher:
|
||||
class Patcher(ifcpatch.BasePatcher):
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
logger: Logger,
|
||||
logger: Union[Logger, None] = None,
|
||||
query: str = "IfcWall",
|
||||
assume_asset_uniqueness_by_name: bool = True,
|
||||
):
|
||||
@@ -62,8 +63,7 @@ class Patcher:
|
||||
# Extract all walls and slabs
|
||||
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ExtractElements", "arguments": ["IfcWall, IfcSlab"]})
|
||||
"""
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
super().__init__(file, logger)
|
||||
self.query = query
|
||||
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.shape
|
||||
import ifcopenshell.util.unit
|
||||
import ifcpatch
|
||||
from pathlib import Path
|
||||
from typing import Any, TYPE_CHECKING, Literal, Union
|
||||
from typing_extensions import assert_never
|
||||
@@ -61,11 +62,11 @@ else:
|
||||
DEFAULT_DATABASE_NAME = "database"
|
||||
|
||||
|
||||
class Patcher:
|
||||
class Patcher(ifcpatch.BasePatcher):
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
logger: logging.Logger,
|
||||
logger: Union[logging.Logger, None] = None,
|
||||
sql_type: SQLTypes = "SQLite",
|
||||
host: str = "localhost",
|
||||
username: str = "root",
|
||||
@@ -116,7 +117,7 @@ class Patcher:
|
||||
{"input": "input.ifc", "file": model, "recipe": "Ifc2Sql", "arguments": ["sqlite"]}
|
||||
)
|
||||
"""
|
||||
self.file = file
|
||||
super().__init__(file, logger)
|
||||
self.logger = logger
|
||||
self.sql_type: Literal["sqlite", "mysql"] = sql_type.lower()
|
||||
self.host = host
|
||||
|
||||
@@ -18,15 +18,17 @@
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.schema
|
||||
import ifcpatch
|
||||
import typing
|
||||
from typing import Union
|
||||
from logging import Logger
|
||||
|
||||
|
||||
class Patcher:
|
||||
class Patcher(ifcpatch.BasePatcher):
|
||||
def __init__(
|
||||
self,
|
||||
file: ifcopenshell.file,
|
||||
logger: Logger,
|
||||
logger: Union[Logger, None] = None,
|
||||
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
|
||||
):
|
||||
"""Migrate from one IFC version to another
|
||||
@@ -43,8 +45,7 @@ class Patcher:
|
||||
# Upgrade an IFC2X3 model to IFC4
|
||||
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Migrate", "arguments": ["IFC4"]})
|
||||
"""
|
||||
self.file = file
|
||||
self.logger = logger
|
||||
super().__init__(file, logger)
|
||||
self.schema = schema
|
||||
|
||||
def patch(self):
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import tempfile
|
||||
import ifcopenshell.api.root
|
||||
import ifcpatch
|
||||
from pathlib import Path
|
||||
|
||||
@@ -32,3 +34,26 @@ class Test:
|
||||
expected_keys = ("class_", "description", "output", "inputs")
|
||||
for key in expected_keys:
|
||||
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