IfcPatch now allows passing in IFC objects instead of filepath strings

This commit is contained in:
Dion Moult
2022-09-19 13:31:33 +10:00
parent ea1d797605
commit 5c5bb03652
21 changed files with 104 additions and 123 deletions
+1 -3
View File
@@ -57,10 +57,8 @@ Here is a minimal example of how to use IfcDiff as a library:
import ifcpatch import ifcpatch
ifcpatch.execute({ ifcpatch.execute({
"input": "input.ifc", "input": ifcopenshell.open("input.ifc"),
"output": "output.ifc",
"recipe": "ExtractElements", "recipe": "ExtractElements",
"log": "ifcpatch.log",
"arguments": [".IfcWall"], "arguments": [".IfcWall"],
}) })
+10 -27
View File
@@ -18,8 +18,6 @@
# 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/>.
# This can be packaged into one executable with ./make.py
import ifcopenshell import ifcopenshell
import logging import logging
import os import os
@@ -29,39 +27,24 @@ import collections
import importlib import importlib
def execute(args, is_library=None): def execute(args):
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG) if "log" in args:
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
logger = logging.getLogger("IFCPatch") logger = logging.getLogger("IFCPatch")
print("# Loading IFC file ...") ifc_file = args["input"]
ifc_file = ifcopenshell.open(args["input"])
print("# Loading patch recipe ...")
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"])
if recipe.Patcher.__init__.__doc__ is not None: if recipe.Patcher.__init__.__doc__ is not None:
patcher = recipe.Patcher(args["input"], ifc_file, logger, *args["arguments"]) patcher = recipe.Patcher(args["input"], ifc_file, logger, *args["arguments"])
else: else:
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"]) patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
print("# Patching ...")
patcher.patch() patcher.patch()
ifc_file = getattr(patcher, "file_patched", patcher.file) return getattr(patcher, "file_patched", patcher.file)
if is_library is True:
return ifc_file
print("# Writing patched file ...")
if not args["output"]:
args["output"] = args["input"]
if isinstance(ifc_file, str):
with open(args["output"], "w") as text_file:
text_file.write(ifc_file)
else:
ifc_file.write(args["output"])
print("# All tasks are complete :-)")
def extract_docs( def extract_docs(
submodule_name: str, submodule_name: str, cls_name: str, method_name: str = "__init__", boilerplate_args: typing.Iterable[str] = None
cls_name: str, ):
method_name: str="__init__",
boilerplate_args : typing.Iterable[str]=None):
"""Extract class docstrings and method arguments """Extract class docstrings and method arguments
:param submodule_name: Submodule from which to extract the class :param submodule_name: Submodule from which to extract the class
@@ -70,8 +53,8 @@ def extract_docs(
:param boilerplate_args: String iterable containing arguments that shall not be parsed :param boilerplate_args: String iterable containing arguments that shall not be parsed
""" """
spec = importlib.util.spec_from_file_location( spec = importlib.util.spec_from_file_location(
submodule_name, submodule_name, f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py"
f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py") )
submodule = importlib.util.module_from_spec(spec) submodule = importlib.util.module_from_spec(spec)
try: try:
spec.loader.exec_module(submodule) spec.loader.exec_module(submodule)
@@ -101,7 +84,7 @@ def _extract_docs(cls, method_name, boilerplate_args):
type_hints = typing.get_type_hints(method) type_hints = typing.get_type_hints(method)
for input_name in inputs.keys(): for input_name in inputs.keys():
type_hint = type_hints.get(input_name, None) type_hint = type_hints.get(input_name, None)
if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??) if type_hint is None: # The argument is not type-hinted. (Or hinted to None ??)
continue continue
if isinstance(type_hint, typing._UnionGenericAlias): if isinstance(type_hint, typing._UnionGenericAlias):
inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)] inputs[input_name]["type"] = [t.__name__ for t in typing.get_args(type_hint)]
+19 -2
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021, 2022 Dion Moult <dion@thinkmoult.com>
# #
# This file is part of IfcPatch. # This file is part of IfcPatch.
# #
@@ -20,6 +20,7 @@
import argparse import argparse
import ifcpatch import ifcpatch
import ifcopenshell
parser = argparse.ArgumentParser(description="Patches IFC files to fix badly formatted data") parser = argparse.ArgumentParser(description="Patches IFC files to fix badly formatted data")
parser.add_argument("-i", "--input", type=str, required=True, help="The IFC file to patch") parser.add_argument("-i", "--input", type=str, required=True, help="The IFC file to patch")
@@ -28,4 +29,20 @@ parser.add_argument("-r", "--recipe", type=str, required=True, help="Name of the
parser.add_argument("-l", "--log", type=str, help="Specify a log file", default="ifcpatch.log") parser.add_argument("-l", "--log", type=str, help="Specify a log file", default="ifcpatch.log")
parser.add_argument("-a", "--arguments", nargs="+", help="Specify custom arguments to the patch recipe") parser.add_argument("-a", "--arguments", nargs="+", help="Specify custom arguments to the patch recipe")
args = vars(parser.parse_args()) args = vars(parser.parse_args())
ifcpatch.execute(args)
print("# Loading IFC file ...")
args["input"] = ifcopenshell.open(args["input"])
print("# Patching ...")
ifc_file = ifcpatch.execute(args)
print("# Writing patched file ...")
if not args["output"]:
args["output"] = args["input"]
if isinstance(ifc_file, str):
with open(args["output"], "w") as text_file:
text_file.write(ifc_file)
else:
ifc_file.write(args["output"])
print("# All tasks are complete :-)")
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -37,13 +36,13 @@ class Patcher:
if self.file.schema == "IFC2X3": if self.file.schema == "IFC2X3":
user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser) user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
old_get_user = ifcopenshell.api.owner.settings.get_user old_get_user = ifcopenshell.api.owner.settings.get_user
ifcopenshell.api.owner.settings.get_user = lambda ifc : user ifcopenshell.api.owner.settings.get_user = lambda ifc: user
project = ifcopenshell.api.run("root.create_entity", self.file_patched, ifc_class="IfcProject") project = ifcopenshell.api.run("root.create_entity", self.file_patched, ifc_class="IfcProject")
unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit}) unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit})
# Is there a better way? # Is there a better way?
for element in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False): for element in self.file.by_type("IfcGeometricRepresentationContext", include_subtypes=False):
element.Precision = 1E-8 element.Precision = 1e-8
# If we don't add openings first, they don't get converted # If we don't add openings first, they don't get converted
for element in self.file.by_type("IfcOpeningElement"): for element in self.file.by_type("IfcOpeningElement"):
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -44,13 +44,13 @@ class Patcher:
bm = bmesh.new() bm = bmesh.new()
bm.from_mesh(data) bm.from_mesh(data)
bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01) bmesh.ops.remove_doubles(bm, verts=bm.verts, dist=0.01)
bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method='BEAUTY', ngon_method='BEAUTY') bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method="BEAUTY", ngon_method="BEAUTY")
bm.faces.ensure_lookup_table() bm.faces.ensure_lookup_table()
for polygon in bm.faces: for polygon in bm.faces:
v1, v2, v3 = [v.co.to_2d() for v in polygon.verts] v1, v2, v3 = [v.co.to_2d() for v in polygon.verts]
d1 = degrees((v2-v1).angle(v3-v1)) d1 = degrees((v2 - v1).angle(v3 - v1))
d2 = degrees((v3-v2).angle(v1-v2)) d2 = degrees((v3 - v2).angle(v1 - v2))
d3 = degrees((v1-v3).angle(v2-v3)) d3 = degrees((v1 - v3).angle(v2 - v3))
if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold: if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold:
bm.faces.remove(polygon) bm.faces.remove(polygon)
bm.to_mesh(data) bm.to_mesh(data)
@@ -45,7 +45,7 @@ class Patcher:
# #
# See bug https://github.com/Autodesk/revit-ifc/issues/187 # See bug https://github.com/Autodesk/revit-ifc/issues/187
#for context in self.file.by_type("IfcGeometricRepresentationSubContext"): # for context in self.file.by_type("IfcGeometricRepresentationSubContext"):
# if context.ContextIdentifier == "FootPrint": # if context.ContextIdentifier == "FootPrint":
# context.ContextIdentifier = None # context.ContextIdentifier = None
# context.ContextType = "Annotation" # context.ContextType = "Annotation"
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -20,6 +19,7 @@
import ifcopenshell import ifcopenshell
import ifcopenshell.util.element import ifcopenshell.util.element
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -29,9 +29,9 @@ class Patcher:
def patch(self): def patch(self):
source = ifcopenshell.open(self.args[0]) source = ifcopenshell.open(self.args[0])
original_project = self.file.by_type('IfcProject')[0] original_project = self.file.by_type("IfcProject")[0]
merged_project = self.file.add(source.by_type('IfcProject')[0]) merged_project = self.file.add(source.by_type("IfcProject")[0])
for element in source.by_type('IfcRoot'): for element in source.by_type("IfcRoot"):
self.file.add(element) self.file.add(element)
for inverse in self.file.get_inverse(merged_project): for inverse in self.file.get_inverse(merged_project):
ifcopenshell.util.element.replace_attribute(inverse, merged_project, original_project) ifcopenshell.util.element.replace_attribute(inverse, merged_project, original_project)
-1
View File
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -25,11 +25,11 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
project = self.file.by_type('IfcProject')[0] project = self.file.by_type("IfcProject")[0]
storeys = self.find_decomposed_ifc_class(project, 'IfcBuildingStorey') storeys = self.find_decomposed_ifc_class(project, "IfcBuildingStorey")
for storey in storeys: for storey in storeys:
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
storey.ObjectPlacement.RelativePlacement.Location.Coordinates = (co[0], co[1], co[2]+float(self.args[0])) storey.ObjectPlacement.RelativePlacement.Location.Coordinates = (co[0], co[1], co[2] + float(self.args[0]))
co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates co = storey.ObjectPlacement.RelativePlacement.Location.Coordinates
# NOTE If the geometric data is provided (ObjectPlacement is # NOTE If the geometric data is provided (ObjectPlacement is
# specified), the Elevation value shall either not be included, or # specified), the Elevation value shall either not be included, or
+1 -10
View File
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -22,7 +21,6 @@ import ifcopenshell.util.element
from toposort import toposort_flatten as toposort from toposort import toposort_flatten as toposort
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -31,7 +29,6 @@ class Patcher:
self.args = args self.args = args
self.optimized_file = ifcopenshell.file(schema=self.file.schema) self.optimized_file = ifcopenshell.file(schema=self.file.schema)
def patch(self): def patch(self):
def generate_instances_and_references(): def generate_instances_and_references():
""" """
@@ -43,7 +40,6 @@ class Patcher:
instance_mapping = {} instance_mapping = {}
def map_value(v): def map_value(v):
""" """
Recursive function which replicates an entity instance, with Recursive function which replicates an entity instance, with
@@ -60,7 +56,6 @@ class Patcher:
# express simple types are not part of the toposort and just copied # express simple types are not part of the toposort and just copied
return self.optimized_file.create_entity(v.is_a(), v[0]) return self.optimized_file.create_entity(v.is_a(), v[0])
return instance_mapping[v] return instance_mapping[v]
else: else:
# a plain python value can just be returned # a plain python value can just be returned
@@ -76,10 +71,6 @@ class Patcher:
else: else:
info_to_id[info] = id info_to_id[info] = id
instance_mapping[inst] = self.optimized_file.create_entity( instance_mapping[inst] = self.optimized_file.create_entity(inst.is_a(), *map(map_value, inst))
inst.is_a(),
*map(map_value, inst)
)
self.file = self.optimized_file self.file = self.optimized_file
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -20,6 +19,7 @@
from collections import deque from collections import deque
import ifcopenshell.util.element import ifcopenshell.util.element
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -31,7 +31,7 @@ class Patcher:
deleted = [] deleted = []
hashes = {} hashes = {}
for element in self.file: for element in self.file:
if element.is_a('IfcRoot'): if element.is_a("IfcRoot"):
continue continue
h = hash(tuple(element)) h = hash(tuple(element))
if h in hashes: if h in hashes:
@@ -42,13 +42,13 @@ class Patcher:
hashes[h] = element hashes[h] = element
deleted.sort() deleted.sort()
deleted_q = deque(deleted) deleted_q = deque(deleted)
new = '' new = ""
for line in self.file.wrapped_data.to_string().split('\n'): for line in self.file.wrapped_data.to_string().split("\n"):
try: try:
if int(line.split('=')[0][1:]) != deleted_q[0]: if int(line.split("=")[0][1:]) != deleted_q[0]:
new += (line + '\n') new += line + "\n"
else: else:
deleted_q.popleft() deleted_q.popleft()
except: except:
new += (line + '\n') new += line + "\n"
self.file = new self.file = new
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -25,8 +25,8 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
project = self.file.by_type('IfcProject')[0] project = self.file.by_type("IfcProject")[0]
sites = self.find_decomposed_ifc_class(project, 'IfcSite') sites = self.find_decomposed_ifc_class(project, "IfcSite")
for site in sites: for site in sites:
site.Representation = None site.Representation = None
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -26,8 +26,8 @@ class Patcher:
def patch(self): def patch(self):
placement_coord_ids = set() placement_coord_ids = set()
for placement in self.file.by_type('IfcObjectPlacement'): for placement in self.file.by_type("IfcObjectPlacement"):
[placement_coord_ids.add(e.id()) for e in self.file.traverse(placement) if e.is_a('IfcCartesianPoint')] [placement_coord_ids.add(e.id()) for e in self.file.traverse(placement) if e.is_a("IfcCartesianPoint")]
# Arbitrary threshold based on experience # Arbitrary threshold based on experience
self.threshold = 1000000 self.threshold = 1000000
@@ -42,9 +42,9 @@ class Patcher:
# the case, but is very fast to run, and works for most cases. # the case, but is very fast to run, and works for most cases.
offset_point = None offset_point = None
if self.args and len(self.args) >= 3: if self.args and len(self.args) >= 3:
offset_point = (float(self.args[0]),float(self.args[1]),float(self.args[2])) offset_point = (float(self.args[0]), float(self.args[1]), float(self.args[2]))
try: try:
point_lists = self.file.by_type('IfcCartesianPointList3D') point_lists = self.file.by_type("IfcCartesianPointList3D")
except: except:
# IFC2X3 does not have IfcCartesianPointList3D # IFC2X3 does not have IfcCartesianPointList3D
point_lists = [] point_lists = []
@@ -56,33 +56,29 @@ class Patcher:
continue continue
if not offset_point: if not offset_point:
offset_point = (-point[0], -point[1], -point[2]) offset_point = (-point[0], -point[1], -point[2])
self.logger.info(f'Resetting absolute coordinates by {point}') self.logger.info(f"Resetting absolute coordinates by {point}")
point = ( point = (point[0] + offset_point[0], point[1] + offset_point[1], point[2] + offset_point[2])
point[0] + offset_point[0],
point[1] + offset_point[1],
point[2] + offset_point[2]
)
coord_list[i] = point coord_list[i] = point
point_list.CoordList = coord_list point_list.CoordList = coord_list
for point in self.file.by_type('IfcCartesianPoint'): for point in self.file.by_type("IfcCartesianPoint"):
if len(point.Coordinates) == 2 or not self.is_point_far_away(point): if len(point.Coordinates) == 2 or not self.is_point_far_away(point):
continue continue
if point.id() in placement_coord_ids: if point.id() in placement_coord_ids:
continue continue
if not offset_point: if not offset_point:
offset_point = (-point.Coordinates[0], -point.Coordinates[1], -point.Coordinates[2]) offset_point = (-point.Coordinates[0], -point.Coordinates[1], -point.Coordinates[2])
self.logger.info(f'Resetting absolute coordinates by {point}') self.logger.info(f"Resetting absolute coordinates by {point}")
point.Coordinates = ( point.Coordinates = (
point.Coordinates[0] + offset_point[0], point.Coordinates[0] + offset_point[0],
point.Coordinates[1] + offset_point[1], point.Coordinates[1] + offset_point[1],
point.Coordinates[2] + offset_point[2] point.Coordinates[2] + offset_point[2],
) )
def is_point_far_away(self, point): def is_point_far_away(self, point):
if hasattr(point, 'Coordinates'): if hasattr(point, "Coordinates"):
return abs(point.Coordinates[0]) > self.threshold \ return (
or abs(point.Coordinates[1]) > self.threshold \ abs(point.Coordinates[0]) > self.threshold
or abs(point.Coordinates[1]) > self.threshold
or abs(point.Coordinates[2]) > self.threshold or abs(point.Coordinates[2]) > self.threshold
return abs(point[0]) > self.threshold \ )
or abs(point[1]) > self.threshold \ return abs(point[0]) > self.threshold or abs(point[1]) > self.threshold or abs(point[2]) > self.threshold
or abs(point[2]) > self.threshold
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -25,7 +25,7 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
project = self.file.by_type('IfcProject')[0] project = self.file.by_type("IfcProject")[0]
spatial_elements = self.find_decomposed_ifc_class(project, self.args[0]) spatial_elements = self.find_decomposed_ifc_class(project, self.args[0])
for spatial_element in spatial_elements: for spatial_element in spatial_elements:
self.patch_placement_to_origin(spatial_element) self.patch_placement_to_origin(spatial_element)
@@ -43,8 +43,8 @@ class Patcher:
return results return results
def patch_placement_to_origin(self, element): def patch_placement_to_origin(self, element):
element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0., 0., 0.) element.ObjectPlacement.RelativePlacement.Location.Coordinates = (0.0, 0.0, 0.0)
if element.ObjectPlacement.RelativePlacement.Axis: if element.ObjectPlacement.RelativePlacement.Axis:
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.) element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0)
if element.ObjectPlacement.RelativePlacement.RefDirection: if element.ObjectPlacement.RelativePlacement.RefDirection:
element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1., 0., 0.) element.ObjectPlacement.RelativePlacement.RefDirection.DirectionRatios = (1.0, 0.0, 0.0)
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -25,8 +25,8 @@ class Patcher:
self.args = args self.args = args
def patch(self): def patch(self):
project = self.file.by_type('IfcProject')[0] project = self.file.by_type("IfcProject")[0]
sites = self.find_decomposed_ifc_class(project, 'IfcSite') sites = self.find_decomposed_ifc_class(project, "IfcSite")
for site in sites: for site in sites:
site.RefElevation = float(self.args[0]) site.RefElevation = float(self.args[0])
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -17,6 +16,7 @@
# 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/>.
class Patcher: class Patcher:
def __init__(self, src, file, logger, args=None): def __init__(self, src, file, logger, args=None):
self.src = src self.src = src
@@ -27,35 +27,39 @@ class Patcher:
def patch(self): def patch(self):
import ifcopenshell import ifcopenshell
from shutil import copyfile from shutil import copyfile
storeys = self.file.by_type('IfcBuildingStorey')
storeys = self.file.by_type("IfcBuildingStorey")
for i, storey in enumerate(storeys): for i, storey in enumerate(storeys):
dest = '{}-{}.ifc'.format(i, storey.Name) dest = "{}-{}.ifc".format(i, storey.Name)
copyfile(self.src, dest) copyfile(self.src, dest)
old_ifc = ifcopenshell.open(dest) old_ifc = ifcopenshell.open(dest)
new_ifc = ifcopenshell.file(schema=self.file.schema) new_ifc = ifcopenshell.file(schema=self.file.schema)
if self.file.schema == 'IFC2X3': if self.file.schema == "IFC2X3":
elements = old_ifc.by_type('IfcProject') + old_ifc.by_type('IfcProduct') elements = old_ifc.by_type("IfcProject") + old_ifc.by_type("IfcProduct")
else: else:
elements = old_ifc.by_type('IfcContext') + old_ifc.by_type('IfcProduct') elements = old_ifc.by_type("IfcContext") + old_ifc.by_type("IfcProduct")
inverse_elements = [] inverse_elements = []
for element in elements: for element in elements:
if element.is_a('IfcElement') \ if element.is_a("IfcElement") and not self.is_in_storey(element, storey):
and not self.is_in_storey(element, storey):
element.Representation = None element.Representation = None
continue continue
if element.is_a('IfcElement'): if element.is_a("IfcElement"):
styled_rep_items = [i for i in old_ifc.traverse(element) if i.is_a('IfcRepresentationItem') and i.StyledByItem] styled_rep_items = [
i for i in old_ifc.traverse(element) if i.is_a("IfcRepresentationItem") and i.StyledByItem
]
[new_ifc.add(i.StyledByItem[0]) for i in styled_rep_items] [new_ifc.add(i.StyledByItem[0]) for i in styled_rep_items]
new_ifc.add(element) new_ifc.add(element)
inverse_elements.extend(old_ifc.get_inverse(element)) inverse_elements.extend(old_ifc.get_inverse(element))
for inverse_element in inverse_elements: for inverse_element in inverse_elements:
new_ifc.add(inverse_element) new_ifc.add(inverse_element)
for element in new_ifc.by_type('IfcElement'): for element in new_ifc.by_type("IfcElement"):
if not self.is_in_storey(element, storey): if not self.is_in_storey(element, storey):
new_ifc.remove(element) new_ifc.remove(element)
new_ifc.write(dest) new_ifc.write(dest)
def is_in_storey(self, element, storey): def is_in_storey(self, element, storey):
return element.ContainedInStructure \ return (
and element.ContainedInStructure[0].RelatingStructure.is_a('IfcBuildingStorey') \ element.ContainedInStructure
and element.ContainedInStructure[0].RelatingStructure.is_a("IfcBuildingStorey")
and element.ContainedInStructure[0].RelatingStructure.GlobalId == storey.GlobalId and element.ContainedInStructure[0].RelatingStructure.GlobalId == storey.GlobalId
)
@@ -1,4 +1,3 @@
# IfcPatch - IFC patching utiliy # IfcPatch - IFC patching utiliy
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com> # Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
# #
@@ -16,4 +15,3 @@
# #
# 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/>.
-1
View File
@@ -23,4 +23,3 @@ import subprocess
cmd = f'pyinstaller ./bootstrap.py --name ifcpatch --onefile --clean --add-data "ifcpatch{os.pathsep}ifcpatch"' cmd = f'pyinstaller ./bootstrap.py --name ifcpatch --onefile --clean --add-data "ifcpatch{os.pathsep}ifcpatch"'
subprocess.check_output(cmd, shell=True) subprocess.check_output(cmd, shell=True)