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 importlib
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"
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
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
listed subsequent to this in this docstring.
: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.
: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
the recipe. E.g. "ExtractElements".
:type recipe: str
@@ -58,7 +70,6 @@ def execute(args: dict) -> Union[ifcopenshell.file, str]:
:type arguments: list
:return: The result of the patch. This is typically a patched model, either
as an object or as a string.
:rtype: ifcopenshell.file.file,str
Example:
@@ -77,16 +88,32 @@ def execute(args: dict) -> Union[ifcopenshell.file, str]:
logger = logging.getLogger("IFCPatch")
recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes")
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", [])
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:
patcher = recipe.Patcher(args.get("input"), args["file"], logger, arguments)
patcher = recipe.Patcher(args.get("input"), args.get("file"), logger, arguments)
patcher.patch()
output = getattr(patcher, "file_patched", patcher.file)
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:
"""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
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
import logging
import ifcopenshell
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
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
uses the Blender geometry engine.
`input` argument is required for this recipe, `file` argument is ignored.
Example:
.. code:: python
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
ifcpatch.execute({"input": "input.ifc", "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
def patch(self):
def patch(self) -> None:
import bpy
import bonsai.tool as tool
import ifcopenshell
@@ -70,10 +76,11 @@ class Patcher:
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])
if (wall.matrix_world.translation - new_origin).length < 0.001:
return
assert isinstance(wall.data, bpy.types.Mesh)
wall.data.transform(
Matrix.Translation(
(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/>.
import ifcopenshell
import logging
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
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
the geometric modification uses the Blender geometry engine.
`input` argument is required for this recipe, `file` argument is ignored.
Example:
.. code:: python
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixRevitTINs", "arguments": []})
ifcpatch.execute({"input": "input.ifc", "recipe": "FixRevitTINs", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
self.is_solid = is_solid
def patch(self):
def patch(self) -> None:
import bpy
import bmesh
import bonsai.tool as tool
@@ -24,6 +24,8 @@ from typing import Union
class Patcher:
input_argument = "SUPPORTED"
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
@@ -31,6 +33,9 @@ class Patcher:
format of {i}-{name}.ifc, where {i} is an ascending number starting from
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.
Example: