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"],
}) })
+11 -28
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)]
@@ -125,7 +108,7 @@ def _extract_docs(cls, method_name, boilerplate_args):
description += line description += line
elif i > 2: elif i > 2:
description += "\n" + line description += "\n" + line
docs["description"] = description.strip() docs["description"] = description.strip()
docs["inputs"] = inputs docs["inputs"] = inputs
return docs return docs
+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
+7 -16
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,24 +29,22 @@ 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():
""" """
Generator which yields an entity id and Generator which yields an entity id and
the set of all of its references contained in its attributes. the set of all of its references contained in its attributes.
""" """
for inst in self.file: for inst in self.file:
yield inst.id(), set(i.id() for i in self.file.traverse(inst)[1:] if i.id()) yield inst.id(), set(i.id() for i in self.file.traverse(inst)[1:] if i.id())
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
its attributes, mapping references to already registered its attributes, mapping references to already registered
instances. Indeed, because of the toposort we know that instances. Indeed, because of the toposort we know that
forward attribute value instances are mapped before the instances forward attribute value instances are mapped before the instances
that reference them. that reference them.
""" """
@@ -59,15 +55,14 @@ class Patcher:
if v.id() == 0: if v.id() == 0:
# 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
return v return v
info_to_id = {} info_to_id = {}
for id in toposort(dict(generate_instances_and_references())): for id in toposort(dict(generate_instances_and_references())):
inst = self.file[id] inst = self.file[id]
info = inst.get_info(include_identifier=False, recursive=True, return_type=frozenset) info = inst.get_info(include_identifier=False, recursive=True, return_type=frozenset)
@@ -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)