Fix #5939. Fix #2751. IfcPatch no longer needs the (rarely used) input src arg by default.

This commit is contained in:
Dion Moult
2025-01-10 16:59:45 +11:00
parent 83add56e6c
commit cac3d7f699
33 changed files with 58 additions and 119 deletions
@@ -98,12 +98,6 @@ class ExecuteIfcPatch(bpy.types.Operator):
if props.should_load_from_memory and tool.Ifc.get():
args["file"] = tool.Ifc.get()
if ifcpatch.get_patch_input_argument_use(recipe_name) == "REQUIRED":
self.report(
{"ERROR"},
f"The recipe '{recipe_name}' is not currently supported if file is loaded from memory.",
)
return {"CANCELLED"}
else:
args["input"] = cast(str, props.ifc_patch_input)
args["file"] = cast(ifcopenshell.file, ifcopenshell.open(props.ifc_patch_input))
+2 -4
View File
@@ -33,10 +33,8 @@ class Patch(bonsai.core.tool.Patch):
@classmethod
def is_filepath_argument(cls, recipe: str, arg_name: str) -> bool:
# TODO: Temporary hack to identify filepath arguments.
# Should mark them as such in the patches documentation
# and process it later.
return recipe == "SplitByBuildingStorey" and arg_name == "output_dir"
# There is probably a more explicit way to do this
return "filepath" in arg_name
@classmethod
def does_patch_has_output(cls, recipe: str) -> bool:
+2 -16
View File
@@ -95,30 +95,16 @@ def execute(args: ArgumentsDict) -> Union[ifcopenshell.file, str]:
else:
recipe = importlib.import_module(f"ifcpatch.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", None) or []
if recipe.Patcher.__init__.__doc__ is not None:
patcher = recipe.Patcher(args.get("input"), args.get("file"), logger, *arguments)
patcher = recipe.Patcher(args.get("file"), logger, *arguments)
else:
patcher = recipe.Patcher(args.get("input"), args.get("file"), logger, arguments)
patcher = recipe.Patcher(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"]:
# try out of tree and subpackage imports
recipe_module = sys.modules.get(f"ifcpatch.recipes.{recipe}") or sys.modules.get(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
@@ -53,7 +53,6 @@ LengthUnit = typing.Literal[
class Patcher:
def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: Logger,
unit: LengthUnit = "METER",
@@ -76,7 +75,6 @@ class Patcher:
# Convert to feet
model = ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertLengthUnit", "arguments": ["FOOT"]})
"""
self.src = src
self.file = file
self.logger = logger
self.unit = unit
@@ -21,7 +21,7 @@ import ifcopenshell.guid
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Convert nesting relationships to aggregate relationships
Some software like Revit won't load nested children elements because
@@ -39,7 +39,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertNestToAggregate", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -26,7 +26,7 @@ from typing import Union
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, property_name: str, quantity_name: str):
def __init__(self, file: ifcopenshell.file, logger: Logger, property_name: str, quantity_name: str):
"""Converts a property to a standardised quantity
IFC can store arbitrary key value metadata associated with a elements
@@ -59,7 +59,6 @@ class Patcher:
# "NetSideArea", if that standardised quantity exists.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ConvertPropertiesToQuantities", "arguments": ["Area", "NetSideArea"]})
"""
self.src = src
self.file = file
self.logger = logger
self.source_property_name = property_name
@@ -21,7 +21,7 @@ import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Downgrade indexed polycurves to simple polylines
Low quality IFC viewers like Navisworks do not support various IFC4
@@ -39,7 +39,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "DowngradeIndexedPolyCurve", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -25,7 +25,7 @@ from logging import Logger
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, query: str = "IfcWall"):
def __init__(self, file: ifcopenshell.file, logger: Logger, query: str = "IfcWall"):
"""Extract certain elements into a new model
Extract a subset of elements from an existing IFC data set and save it
@@ -48,7 +48,6 @@ class Patcher:
# Extract all walls and slabs
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ExtractElements", "arguments": ["IfcWall, IfcSlab"]})
"""
self.src = src
self.file = file
self.logger = logger
self.query = query
@@ -34,7 +34,6 @@ except:
class Patcher:
def __init__(
self,
src,
file,
logger,
):
@@ -50,7 +49,6 @@ class Patcher:
result = ifcpatch.execute({"input": fn, "file": model, "recipe": "ExtractPropertiesToSQLite"})
ifcpatch.write(result, "output.sqlite")
"""
self.src = src
self.file = file
self.logger = logger
@@ -27,7 +27,7 @@ import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Fix missing door swings in Revit when viewing ArchiCAD IFCs
ArchiCAD has the ability to store 2D data with objects like doors for
@@ -56,7 +56,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixArchiCADToRevitDoorSwings", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -21,9 +21,7 @@ import ifcopenshell
class Patcher:
input_argument = "REQUIRED"
def __init__(self, src: str, file: None, logger: logging.Logger):
def __init__(self, file: None, logger: logging.Logger, filepath: str):
"""Allow ArchiCAD IFC spaces to open as Revit rooms
The underlying problem is that Revit does not bring in IFC spaces as
@@ -51,16 +49,21 @@ 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.
`filepath` argument is required for this recipe, `file` argument is
ignored.
:param filepath: The filepath of the IFC model. This is required to
load into Bonsai.
:filter_glob filepath: *.ifc;*.ifczip;*.ifcxml
Example:
.. code:: python
ifcpatch.execute({"input": "input.ifc", "recipe": "FixArchiCADToRevitSpaces", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
self.filepath = filepath
def patch(self) -> None:
import bpy
@@ -74,7 +77,7 @@ class Patcher:
bpy.data.batch_remove(bpy.data.objects)
bpy.ops.outliner.orphans_purge(do_local_ids=True, do_linked_ids=True, do_recursive=True)
bpy.ops.bim.load_project(filepath=self.src)
bpy.ops.bim.load_project(filepath=self.filepath)
def recalculate_origin(wall: bpy.types.Object) -> None:
new_origin = wall.matrix_world @ Vector(wall.bound_box[0])
@@ -21,7 +21,7 @@ import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Reassigns occurrence classifications to types
Revit has a bug (see https://github.com/Autodesk/revit-ifc/issues/691)
@@ -35,7 +35,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "FixRevitClassificationCodeTypes"})
"""
self.src = src
self.file = file
self.logger = logger
@@ -22,9 +22,7 @@ import logging
class Patcher:
input_argument = "REQUIRED"
def __init__(self, src: str, file: None, logger: logging.Logger, is_solid: bool = True):
def __init__(self, file: None, logger: logging.Logger, filepath: str, 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
@@ -62,7 +60,12 @@ 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.
`filepath` argument is required for this recipe, `file` argument is
ignored.
:param filepath: The filepath of the IFC model. This is required to
load into Bonsai.
:filter_glob filepath: *.ifc;*.ifczip;*.ifcxml
Example:
@@ -70,8 +73,8 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "recipe": "FixRevitTINs", "arguments": []})
"""
self.src = src
self.file = file
self.filepath = filepath
self.logger = logger
self.is_solid = is_solid
@@ -82,7 +85,7 @@ class Patcher:
from math import degrees
bpy.context.scene.BIMProjectProperties.should_use_native_meshes = True
bpy.ops.bim.load_project(filepath=self.src)
bpy.ops.bim.load_project(filepath=self.filepath)
old_history_size = tool.Ifc.get().history_size
old_undo_steps = bpy.context.preferences.edit.undo_steps
-2
View File
@@ -54,7 +54,6 @@ except:
class Patcher:
def __init__(
self,
src,
file,
logger,
sql_type: SQLTypes = "SQLite",
@@ -98,7 +97,6 @@ class Patcher:
{"input": "input.ifc", "file": model, "recipe": "Ifc2Sql", "arguments": ["sqlite"]}
)
"""
self.src = src
self.file = file
self.logger = logger
self.sql_type = sql_type.lower()
@@ -23,7 +23,7 @@ from logging import Logger
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, attribute: str = "Tag"):
def __init__(self, file: ifcopenshell.file, logger: Logger, attribute: str = "Tag"):
"""Merge duplicate element types via the Tag or another attribute
Revit is notorious for creating many duplicate element types. Element
@@ -60,7 +60,6 @@ class Patcher:
# Explicitly say we want to merge based on the Name attribute
ifcpatch.execute({"file": model, "recipe": "MergeDuplicateTypes", "arguments": ["Name"]})
"""
self.src = src
self.file = file
self.logger = logger
self.attribute = attribute
@@ -27,9 +27,7 @@ from logging import Logger
class Patcher:
def __init__(
self, src: str, file: ifcopenshell.file, logger: Logger, filepaths: list[Union[str, ifcopenshell.file]]
):
def __init__(self, file: ifcopenshell.file, logger: Logger, filepaths: list[Union[str, ifcopenshell.file]]):
"""Merge two or more IFC models into one
Note that other than combining the two (or more) IfcProject elements into
@@ -50,7 +48,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "MergeProjects", "arguments": ["/path/to/model2.ifc"]})
"""
self.src = src
self.file = file
self.logger = logger
self.filepaths = filepaths
-2
View File
@@ -25,7 +25,6 @@ from logging import Logger
class Patcher:
def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: Logger,
schema: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4",
@@ -45,7 +44,6 @@ class Patcher:
# Upgrade an IFC2X3 model to IFC4
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Migrate", "arguments": ["IFC4"]})
"""
self.src = src
self.file = file
self.logger = logger
self.schema = schema
@@ -26,7 +26,6 @@ import typing
class Patcher:
def __init__(
self,
src,
file,
logger,
x: typing.Union[str, float] = "0",
@@ -89,7 +88,6 @@ class Patcher:
# Some crazy 3D rotation and offset
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "OffsetObjectPlacements", "arguments": [12.5,5,2,False,90,90,45]})
"""
self.src = src
self.file = file
self.logger = logger
self.x = float(x)
@@ -20,7 +20,7 @@ import typing
class Patcher:
def __init__(self, src, file, logger, z: typing.Union[str, float] = "0"):
def __init__(self, file, logger, z: typing.Union[str, float] = "0"):
"""Offset building storeys by a particular Z value
All objects placed relative to the storeys will also be shifted.
@@ -35,7 +35,6 @@ class Patcher:
# Shift all storeys up by 42 units
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "OffsetStoreyElevations", "arguments": [42]})
"""
self.src = src
self.file = file
self.logger = logger
self.z = float(z)
+1 -2
View File
@@ -21,7 +21,7 @@ import ifcopenshell.util.element
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Optimise the filesize of an IFC model
It is possible to non-losslessly optimise the filesize of an IFC model.
@@ -46,7 +46,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "Optimise", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
self.optimized_file = ifcopenshell.file(schema=self.file.schema)
+1 -2
View File
@@ -22,7 +22,7 @@ from logging import Logger
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger):
def __init__(self, file: ifcopenshell.file, logger: Logger):
"""Purge IFC properties, relationships, and other data
In some rare cases (i.e. "resetting" a model or for security purposes)
@@ -51,7 +51,6 @@ class Patcher:
# Watch the world burn
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "PurgeData", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -22,7 +22,7 @@ import ifcopenshell.util.element
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: logging.Logger):
def __init__(self, file: ifcopenshell.file, logger: logging.Logger):
"""Optimise the filesize of an IFC model by reusing non-rooted elements
It is possible to non-losslessly optimise the filesize of an IFC model.
@@ -46,7 +46,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "RecycleNonRootedElements", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -22,7 +22,7 @@ from logging import Logger
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, only_duplicates=False):
def __init__(self, file: ifcopenshell.file, logger: Logger, only_duplicates=False):
"""Regenerate GlobalIds in an IFC model
All root elements in an IFC model must be identified by a unique Global
@@ -48,7 +48,6 @@ class Patcher:
# Regenerate only duplicate GlobalIds
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "RegenerateGlobalIds", "arguments": [True]})
"""
self.src = src
self.file = file
self.logger = logger
self.only_duplicates = only_duplicates
@@ -18,7 +18,7 @@
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Removes the built-in Revit Uniformat classification.
Revit has a bug (see https://github.com/Autodesk/revit-ifc/issues/486)
@@ -31,7 +31,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "RemoveRevitUniformatClassification"})
"""
self.src = src
self.file = file
self.logger = logger
@@ -18,7 +18,7 @@
class Patcher:
def __init__(self, src, file, logger):
def __init__(self, file, logger):
"""Removes any 3D geometry associated with a site or multiple sites
If no sites or no site geometry is present, nothing happens.
@@ -29,7 +29,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "RemoveSiteRepresentation", "arguments": []})
"""
self.src = src
self.file = file
self.logger = logger
@@ -26,7 +26,6 @@ from typing import Literal, Optional, Union
class Patcher:
def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: logging.Logger,
mode: Literal[
@@ -99,7 +98,6 @@ class Patcher:
# Reset all coordinates with an ordinate larger than 1000 by -500,-200,0
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ResetAbsoluteCoordinates", "arguments": [False, 1000, -500,-200,0]})
"""
self.src = src
self.file = file
self.logger = logger
self.mode = mode.lower()
@@ -18,7 +18,7 @@
class Patcher:
def __init__(self, src, file, logger, ifc_class="IfcSite"):
def __init__(self, file, logger, ifc_class="IfcSite"):
"""Resets the location of a spatial element to 0,0,0
Another more specialised patch to fix incorrect coordinate usage is to
@@ -35,7 +35,6 @@ class Patcher:
# All IfcSites will shift back to 0,0,0.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "ResetSpatialElementLocations", "arguments": ["IfcSite"]})
"""
self.src = src
self.file = file
self.logger = logger
self.ifc_class = ifc_class
@@ -25,7 +25,6 @@ import typing
class Patcher:
def __init__(
self,
src,
file,
logger,
name: str = "EPSG:1234",
@@ -59,7 +58,6 @@ class Patcher:
# Set the current origin 0,0,0 to correlate to map coordinates 1000,1000,0 and a grid north of 15.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "SetFalseOrigin", "arguments": ["EPSG:1234", 0, 0, 0, 1000, 1000, 0, 15, 0]})
"""
self.src = src
self.file = file
self.logger = logger
self.name = name
@@ -73,7 +71,7 @@ class Patcher:
self.rotate_angle = float(rotate_angle)
def patch(self):
SetWorldCoordinateSystem.Patcher(self.src, self.file, self.logger, x=0, y=0, z=0, ax=0, ay=0, az=0).patch()
SetWorldCoordinateSystem.Patcher(self.file, self.logger, x=0, y=0, z=0, ax=0, ay=0, az=0).patch()
coordinate_operation = {
"Eastings": self.e,
"Northings": self.n,
@@ -90,7 +88,6 @@ class Patcher:
self.file, projected_crs={"Name": self.name}, coordinate_operation=coordinate_operation
)
OffsetObjectPlacements.Patcher(
self.src,
self.file,
self.logger,
x=-self.x,
@@ -20,7 +20,7 @@ import typing
class Patcher:
def __init__(self, src, file, logger, elevation: typing.Union[str, float] = "0"):
def __init__(self, file, logger, elevation: typing.Union[str, float] = "0"):
"""Sets the reference elevation of all IfcSites
To completely reference model coordinates, a reference elevation should
@@ -41,7 +41,6 @@ class Patcher:
# All IfcSites will have their reference elevation set to 42.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "SetRefElevation", "arguments": [42]})
"""
self.src = src
self.file = file
self.logger = logger
self.elevation = float(elevation)
@@ -24,7 +24,6 @@ import typing
class Patcher:
def __init__(
self,
src,
file,
logger,
x: typing.Union[str, float] = "0",
@@ -61,7 +60,6 @@ class Patcher:
# Set the world coordinate system back to 0, 0, 0
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "SetWorldCoordinateSystem", "arguments": [0,0,0,0,0,0]})
"""
self.src = src
self.file = file
self.logger = logger
self.x = float(x)
@@ -24,18 +24,13 @@ 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):
def __init__(self, file: ifcopenshell.file, logger: logging.Logger, output_dir: Union[str, None] = None):
"""Split an IFC model into multiple models based on building storey
The new IFC model names will be named after the storey name in the
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:
@@ -44,7 +39,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "SplitByBuildingStorey", "arguments": ["C:/.../output_files"]})
"""
self.src = src
self.file = file
self.logger = logger
self.output_dir = output_dir
@@ -60,17 +54,14 @@ class Patcher:
output_dir = Path(self.output_dir)
output_dir.mkdir(parents=True, exist_ok=True)
temp_file = None
if not self.src:
temp_file = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False)
self.src = temp_file.name
self.file.write(self.src)
temp_file = tempfile.NamedTemporaryFile(suffix=".ifc", delete=False)
self.file.write(temp_file.name)
storeys = self.file.by_type("IfcBuildingStorey")
for i, storey in enumerate(storeys):
filename = f"{i}-{storey.Name}.ifc"
dest = filename if output_dir == None else output_dir / filename
copyfile(self.src, dest)
copyfile(temp_file.name, dest)
old_ifc: ifcopenshell.file = ifcopenshell.open(dest)
new_ifc = ifcopenshell.file(schema=self.file.schema)
@@ -98,9 +89,8 @@ class Patcher:
new_ifc.remove(element)
new_ifc.write(dest)
if temp_file is not None:
temp_file.close()
os.unlink(temp_file.name)
temp_file.close()
os.unlink(temp_file.name)
def is_in_storey(self, element: ifcopenshell.entity_instance, storey: ifcopenshell.entity_instance) -> bool:
return (
@@ -30,7 +30,6 @@ from typing import Union
class Patcher:
def __init__(
self,
src: str,
file: ifcopenshell.file,
logger: Logger,
query: str = "IfcBeam",
@@ -58,7 +57,6 @@ class Patcher:
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "TessellateElements", "arguments": ["IfcBeam", False]})
"""
self.src = src
self.file = file
self.logger = logger
self.query = query
+12 -9
View File
@@ -25,18 +25,22 @@ from logging import Logger
class Patcher:
def __init__(self, src: str, file: ifcopenshell.file, logger: Logger, query: str = ""):
def __init__(self, file: ifcopenshell.file, logger: Logger, query: str = ""):
"""Create independent copies for shared psets in IFC file.
In IFC it's possible that same property set is shared by multiple elements,
so editing it's properties will automatically change their values for all those elements.
In IFC it's possible that same property set is shared by multiple
elements, so editing it's properties will automatically change their
values for all those elements.
Sometimes it's intended but sometimes it's not and it's just the way some other
software exports IFC (e.g. there is a known case when Tekla exports shared psets for all the occurrences).
While it is more optimized way to store data, it may lead to unexpected results when editing properties.
Sometimes it's intended but sometimes it's not and it's just the way
some other software exports IFC (e.g. there is a known case when Tekla
exports shared psets for all the occurrences). While it is more
optimized way to store data, it may lead to unexpected results when
editing properties.
This recipe creates independent copies of all shared psets (may be limited by the query)
and assigns them to the elements, so they can be edited without affecting any other elements.
This recipe creates independent copies of all shared psets (may be
limited by the query) and assigns them to the elements, so they can be
edited without affecting any other elements.
:param query: A query to select the subset of IFC elements, optional.
If not provided, patch will be applied to all shared property sets in the model.
@@ -51,7 +55,6 @@ class Patcher:
# Unshare psets on all IfcWalls.
ifcpatch.execute({"input": "input.ifc", "file": model, "recipe": "UnsharePsets", "arguments": ["IfcWall"]})
"""
self.src = src
self.file = file
self.logger = logger
self.query = query