ifcpatch - input and file arguments #2751

documented when input is currently required
This commit is contained in:
Andrej730
2024-12-06 16:41:09 +05:00
parent 2da5f2d61e
commit c306014190
4 changed files with 63 additions and 16 deletions
+35 -8
View File
@@ -27,13 +27,22 @@ import inspect
import collections import collections
import importlib import importlib
import importlib.util import importlib.util
from typing import Union, Iterable, Optional, Any from typing import Union, Iterable, Optional, Any, TypedDict, Literal
from typing_extensions import NotRequired
__version__ = version = "0.0.0" __version__ = version = "0.0.0"
def execute(args: dict) -> Union[ifcopenshell.file, str]: class ArgumentsDict(TypedDict):
recipe: str
file: NotRequired[ifcopenshell.file]
input: NotRequired[str]
log: NotRequired[str]
arguments: NotRequired[list]
def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
"""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
@@ -43,10 +52,13 @@ def execute(args: dict) -> Union[ifcopenshell.file, str]:
:param args: A dictionary of arguments, corresponding to the parameters :param args: A dictionary of arguments, corresponding to the parameters
listed subsequent to this in this docstring. listed subsequent to this in this docstring.
:type args: dict :type args: dict
:param input: A filepath to the incoming IFC file.
:type input: str
:param file: An IFC model to apply the patch recipe to. :param file: An IFC model to apply the patch recipe to.
:type file: ifcopenshell.file.file Required for most recipes except the ones that require `input`.
:type file: ifcopenshell.file, optional
:param input: A filepath to the incoming IFC file.
Required/supported only for some recipes, see specific recipes descriptions,
in other cases will be ignored.
:type input: str, optional
:param recipe: The name of the recipe. This is the same as the filename of :param recipe: The name of the recipe. This is the same as the filename of
the recipe. E.g. "ExtractElements". the recipe. E.g. "ExtractElements".
:type recipe: str :type recipe: str
@@ -58,7 +70,6 @@ def execute(args: dict) -> Union[ifcopenshell.file, str]:
:type arguments: list :type arguments: list
:return: The result of the patch. This is typically a patched model, either :return: The result of the patch. This is typically a patched model, either
as an object or as a string. as an object or as a string.
:rtype: ifcopenshell.file.file,str
Example: Example:
@@ -77,16 +88,32 @@ def execute(args: dict) -> Union[ifcopenshell.file, str]:
logger = logging.getLogger("IFCPatch") logger = logging.getLogger("IFCPatch")
recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes") recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes")
recipe = getattr(recipes, args["recipe"]) recipe = getattr(recipes, args["recipe"])
# Ensure file or input is provided.
input_argument = get_patch_input_argument_use(args["recipe"])
if input_argument == "REQUIRED":
if not args.get("input"):
raise ValueError(f"Recipe {args['recipe']} is requiring 'input' argument to be provided.")
elif "file" not in args: # SUPPORTED, IGNORED.
raise ValueError(f"Recipe {args['recipe']} is requiring 'file' argument to be provided.")
arguments = args.get("arguments", []) arguments = args.get("arguments", [])
if recipe.Patcher.__init__.__doc__ is not None: if recipe.Patcher.__init__.__doc__ is not None:
patcher = recipe.Patcher(args.get("input"), args["file"], logger, *arguments) patcher = recipe.Patcher(args.get("input"), args.get("file"), logger, *arguments)
else: else:
patcher = recipe.Patcher(args.get("input"), args["file"], logger, arguments) patcher = recipe.Patcher(args.get("input"), args.get("file"), logger, arguments)
patcher.patch() patcher.patch()
output = getattr(patcher, "file_patched", patcher.file) output = getattr(patcher, "file_patched", patcher.file)
return output return output
def get_patch_input_argument_use(recipe: str) -> Literal["REQUIRED", "SUPPORTED", "IGNORED"]:
import importlib
recipe_module = importlib.import_module(f"ifcpatch.recipes.{recipe}")
return getattr(recipe_module.Patcher, "input_argument", "IGNORED")
def write(output: Union[ifcopenshell.file, str], filepath: str) -> None: def write(output: Union[ifcopenshell.file, str], filepath: str) -> None:
"""Write the output of an IFC patch to a file """Write the output of an IFC patch to a file
@@ -16,9 +16,14 @@
# 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 IfcPatch. If not, see <http://www.gnu.org/licenses/>. # along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import logging
import ifcopenshell
class Patcher: class Patcher:
def __init__(self, src, file, logger): input_argument = "REQUIRED"
def __init__(self, src: str, file: None, logger: logging.Logger):
"""Allow ArchiCAD IFC spaces to open as Revit rooms """Allow ArchiCAD IFC spaces to open as Revit rooms
The underlying problem is that Revit does not bring in IFC spaces as The underlying problem is that Revit does not bring in IFC spaces as
@@ -46,17 +51,18 @@ class Patcher:
requires you to run it using Blender, as the geometric modification requires you to run it using Blender, as the geometric modification
uses the Blender geometry engine. uses the Blender geometry engine.
`input` argument is required for this recipe, `file` argument is ignored.
Example: Example:
.. code:: python .. code:: python
ifcpatch.execute({"input": "input.ifc", "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
""" """
self.src = src self.src = src
self.file = file self.file = file
self.logger = logger self.logger = logger
def patch(self): def patch(self) -> None:
import bpy import bpy
import bonsai.tool as tool import bonsai.tool as tool
import ifcopenshell import ifcopenshell
@@ -70,10 +76,11 @@ class Patcher:
bpy.ops.bim.load_project(filepath=self.src) bpy.ops.bim.load_project(filepath=self.src)
def recalculate_origin(wall): def recalculate_origin(wall: bpy.types.Object) -> None:
new_origin = wall.matrix_world @ Vector(wall.bound_box[0]) new_origin = wall.matrix_world @ Vector(wall.bound_box[0])
if (wall.matrix_world.translation - new_origin).length < 0.001: if (wall.matrix_world.translation - new_origin).length < 0.001:
return return
assert isinstance(wall.data, bpy.types.Mesh)
wall.data.transform( wall.data.transform(
Matrix.Translation( Matrix.Translation(
(wall.matrix_world.inverted().to_quaternion() @ (wall.matrix_world.translation - new_origin)) (wall.matrix_world.inverted().to_quaternion() @ (wall.matrix_world.translation - new_origin))
+11 -3
View File
@@ -17,8 +17,14 @@
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>. # along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
import logging
class Patcher: class Patcher:
def __init__(self, src, file, logger, is_solid=True): input_argument = "REQUIRED"
def __init__(self, src: str, file: None, logger: logging.Logger, is_solid: bool = True):
"""Fix missing or spot-coordinate bugged TINs loading in Revit """Fix missing or spot-coordinate bugged TINs loading in Revit
TINs exported from 12D or Civil 3D may contain dense or highly obtuse TINs exported from 12D or Civil 3D may contain dense or highly obtuse
@@ -56,18 +62,20 @@ class Patcher:
from civil software. It also requires you to run it using Blender, as from civil software. It also requires you to run it using Blender, as
the geometric modification uses the Blender geometry engine. the geometric modification uses the Blender geometry engine.
`input` argument is required for this recipe, `file` argument is ignored.
Example: Example:
.. code:: python .. code:: python
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixRevitTINs", "arguments": []}) ifcpatch.execute({"input": "input.ifc", "recipe": "FixRevitTINs", "arguments": []})
""" """
self.src = src self.src = src
self.file = file self.file = file
self.logger = logger self.logger = logger
self.is_solid = is_solid self.is_solid = is_solid
def patch(self): def patch(self) -> None:
import bpy import bpy
import bmesh import bmesh
import bonsai.tool as tool import bonsai.tool as tool
@@ -24,6 +24,8 @@ from typing import Union
class Patcher: class Patcher:
input_argument = "SUPPORTED"
def __init__(self, src: str, file: ifcopenshell.file, logger: logging.Logger, output_dir: Union[str, None] = None): def __init__(self, src: str, file: ifcopenshell.file, logger: logging.Logger, output_dir: Union[str, None] = None):
"""Split an IFC model into multiple models based on building storey """Split an IFC model into multiple models based on building storey
@@ -31,6 +33,9 @@ class Patcher:
format of {i}-{name}.ifc, where {i} is an ascending number starting from format of {i}-{name}.ifc, where {i} is an ascending number starting from
0 and {name} is the name of the storey. 0 and {name} is the name of the storey.
`input` argument might be provided to ifcpatch - it will be used load file from disk
(otherwise `file` will be saved to a temporary file).
:param output_dir: Specifies an output directory where the new IFC models will be saved. :param output_dir: Specifies an output directory where the new IFC models will be saved.
Example: Example: