mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-12 18:43:26 +00:00
ifcpatch - input and file arguments #2751
documented when input is currently required
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user