mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-13 02:47:48 +00:00
IfcPatch now allows passing in IFC objects instead of filepath strings
This commit is contained in:
@@ -57,10 +57,8 @@ Here is a minimal example of how to use IfcDiff as a library:
|
||||
import ifcpatch
|
||||
|
||||
ifcpatch.execute({
|
||||
"input": "input.ifc",
|
||||
"output": "output.ifc",
|
||||
"input": ifcopenshell.open("input.ifc"),
|
||||
"recipe": "ExtractElements",
|
||||
"log": "ifcpatch.log",
|
||||
"arguments": [".IfcWall"],
|
||||
})
|
||||
|
||||
|
||||
@@ -18,8 +18,6 @@
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# This can be packaged into one executable with ./make.py
|
||||
|
||||
import ifcopenshell
|
||||
import logging
|
||||
import os
|
||||
@@ -29,39 +27,24 @@ import collections
|
||||
import importlib
|
||||
|
||||
|
||||
def execute(args, is_library=None):
|
||||
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
||||
def execute(args):
|
||||
if "log" in args:
|
||||
logging.basicConfig(filename=args["log"], filemode="a", level=logging.DEBUG)
|
||||
logger = logging.getLogger("IFCPatch")
|
||||
print("# Loading IFC file ...")
|
||||
ifc_file = ifcopenshell.open(args["input"])
|
||||
print("# Loading patch recipe ...")
|
||||
ifc_file = args["input"]
|
||||
recipes = getattr(__import__("ifcpatch.recipes.{}".format(args["recipe"])), "recipes")
|
||||
recipe = getattr(recipes, args["recipe"])
|
||||
if recipe.Patcher.__init__.__doc__ is not None:
|
||||
patcher = recipe.Patcher(args["input"], ifc_file, logger, *args["arguments"])
|
||||
else:
|
||||
patcher = recipe.Patcher(args["input"], ifc_file, logger, args["arguments"])
|
||||
print("# Patching ...")
|
||||
patcher.patch()
|
||||
ifc_file = 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 :-)")
|
||||
return getattr(patcher, "file_patched", patcher.file)
|
||||
|
||||
|
||||
def extract_docs(
|
||||
submodule_name: str,
|
||||
cls_name: str,
|
||||
method_name: str="__init__",
|
||||
boilerplate_args : typing.Iterable[str]=None):
|
||||
submodule_name: str, cls_name: str, method_name: str = "__init__", boilerplate_args: typing.Iterable[str] = None
|
||||
):
|
||||
"""Extract class docstrings and method arguments
|
||||
|
||||
: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
|
||||
"""
|
||||
spec = importlib.util.spec_from_file_location(
|
||||
submodule_name,
|
||||
f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py")
|
||||
submodule_name, f"{os.path.dirname(inspect.getabsfile(inspect.currentframe()))}/recipes/{submodule_name}.py"
|
||||
)
|
||||
submodule = importlib.util.module_from_spec(spec)
|
||||
try:
|
||||
spec.loader.exec_module(submodule)
|
||||
@@ -101,7 +84,7 @@ def _extract_docs(cls, method_name, boilerplate_args):
|
||||
type_hints = typing.get_type_hints(method)
|
||||
for input_name in inputs.keys():
|
||||
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
|
||||
if isinstance(type_hint, typing._UnionGenericAlias):
|
||||
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
|
||||
elif i > 2:
|
||||
description += "\n" + line
|
||||
|
||||
|
||||
docs["description"] = description.strip()
|
||||
docs["inputs"] = inputs
|
||||
return docs
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# 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.
|
||||
#
|
||||
@@ -20,6 +20,7 @@
|
||||
|
||||
import argparse
|
||||
import ifcpatch
|
||||
import ifcopenshell
|
||||
|
||||
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")
|
||||
@@ -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("-a", "--arguments", nargs="+", help="Specify custom arguments to the patch recipe")
|
||||
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
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
@@ -37,13 +36,13 @@ class Patcher:
|
||||
if self.file.schema == "IFC2X3":
|
||||
user = self.file_patched.add(self.file.by_type("IfcProject")[0].OwnerHistory.OwningUser)
|
||||
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")
|
||||
unit_assignment = ifcopenshell.api.run("unit.assign_unit", self.file_patched, **{"length": unit})
|
||||
|
||||
# Is there a better way?
|
||||
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
|
||||
for element in self.file.by_type("IfcOpeningElement"):
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
|
||||
@@ -44,13 +44,13 @@ class Patcher:
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(data)
|
||||
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()
|
||||
for polygon in bm.faces:
|
||||
v1, v2, v3 = [v.co.to_2d() for v in polygon.verts]
|
||||
d1 = degrees((v2-v1).angle(v3-v1))
|
||||
d2 = degrees((v3-v2).angle(v1-v2))
|
||||
d3 = degrees((v1-v3).angle(v2-v3))
|
||||
d1 = degrees((v2 - v1).angle(v3 - v1))
|
||||
d2 = degrees((v3 - v2).angle(v1 - v2))
|
||||
d3 = degrees((v1 - v3).angle(v2 - v3))
|
||||
if d1 < angle_threshold or d2 < angle_threshold or d3 < angle_threshold:
|
||||
bm.faces.remove(polygon)
|
||||
bm.to_mesh(data)
|
||||
|
||||
@@ -45,7 +45,7 @@ class Patcher:
|
||||
#
|
||||
# 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":
|
||||
# context.ContextIdentifier = None
|
||||
# context.ContextType = "Annotation"
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
@@ -20,6 +19,7 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -29,9 +29,9 @@ class Patcher:
|
||||
|
||||
def patch(self):
|
||||
source = ifcopenshell.open(self.args[0])
|
||||
original_project = self.file.by_type('IfcProject')[0]
|
||||
merged_project = self.file.add(source.by_type('IfcProject')[0])
|
||||
for element in source.by_type('IfcRoot'):
|
||||
original_project = self.file.by_type("IfcProject")[0]
|
||||
merged_project = self.file.add(source.by_type("IfcProject")[0])
|
||||
for element in source.by_type("IfcRoot"):
|
||||
self.file.add(element)
|
||||
for inverse in self.file.get_inverse(merged_project):
|
||||
ifcopenshell.util.element.replace_attribute(inverse, merged_project, original_project)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -25,11 +25,11 @@ class Patcher:
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
project = self.file.by_type('IfcProject')[0]
|
||||
storeys = self.find_decomposed_ifc_class(project, 'IfcBuildingStorey')
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
storeys = self.find_decomposed_ifc_class(project, "IfcBuildingStorey")
|
||||
for storey in storeys:
|
||||
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
|
||||
# NOTE If the geometric data is provided (ObjectPlacement is
|
||||
# specified), the Elevation value shall either not be included, or
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
@@ -22,7 +21,6 @@ import ifcopenshell.util.element
|
||||
from toposort import toposort_flatten as toposort
|
||||
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -31,24 +29,22 @@ class Patcher:
|
||||
self.args = args
|
||||
self.optimized_file = ifcopenshell.file(schema=self.file.schema)
|
||||
|
||||
|
||||
def patch(self):
|
||||
def generate_instances_and_references():
|
||||
"""
|
||||
Generator which yields an entity id and
|
||||
the set of all of its references contained in its attributes.
|
||||
Generator which yields an entity id and
|
||||
the set of all of its references contained in its attributes.
|
||||
"""
|
||||
for inst in self.file:
|
||||
yield inst.id(), set(i.id() for i in self.file.traverse(inst)[1:] if i.id())
|
||||
|
||||
instance_mapping = {}
|
||||
|
||||
|
||||
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
|
||||
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
|
||||
that reference them.
|
||||
"""
|
||||
@@ -59,15 +55,14 @@ class Patcher:
|
||||
if v.id() == 0:
|
||||
# express simple types are not part of the toposort and just copied
|
||||
return self.optimized_file.create_entity(v.is_a(), v[0])
|
||||
|
||||
|
||||
|
||||
return instance_mapping[v]
|
||||
else:
|
||||
# a plain python value can just be returned
|
||||
return v
|
||||
|
||||
info_to_id = {}
|
||||
|
||||
|
||||
for id in toposort(dict(generate_instances_and_references())):
|
||||
inst = self.file[id]
|
||||
info = inst.get_info(include_identifier=False, recursive=True, return_type=frozenset)
|
||||
@@ -76,10 +71,6 @@ class Patcher:
|
||||
|
||||
else:
|
||||
info_to_id[info] = id
|
||||
instance_mapping[inst] = self.optimized_file.create_entity(
|
||||
inst.is_a(),
|
||||
*map(map_value, inst)
|
||||
)
|
||||
instance_mapping[inst] = self.optimized_file.create_entity(inst.is_a(), *map(map_value, inst))
|
||||
|
||||
self.file = self.optimized_file
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
@@ -20,6 +19,7 @@
|
||||
from collections import deque
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -31,7 +31,7 @@ class Patcher:
|
||||
deleted = []
|
||||
hashes = {}
|
||||
for element in self.file:
|
||||
if element.is_a('IfcRoot'):
|
||||
if element.is_a("IfcRoot"):
|
||||
continue
|
||||
h = hash(tuple(element))
|
||||
if h in hashes:
|
||||
@@ -42,13 +42,13 @@ class Patcher:
|
||||
hashes[h] = element
|
||||
deleted.sort()
|
||||
deleted_q = deque(deleted)
|
||||
new = ''
|
||||
for line in self.file.wrapped_data.to_string().split('\n'):
|
||||
new = ""
|
||||
for line in self.file.wrapped_data.to_string().split("\n"):
|
||||
try:
|
||||
if int(line.split('=')[0][1:]) != deleted_q[0]:
|
||||
new += (line + '\n')
|
||||
if int(line.split("=")[0][1:]) != deleted_q[0]:
|
||||
new += line + "\n"
|
||||
else:
|
||||
deleted_q.popleft()
|
||||
except:
|
||||
new += (line + '\n')
|
||||
new += line + "\n"
|
||||
self.file = new
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# Copyright (C) 2020, 2021 Dion Moult <dion@thinkmoult.com>
|
||||
#
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -25,8 +25,8 @@ class Patcher:
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
project = self.file.by_type('IfcProject')[0]
|
||||
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
sites = self.find_decomposed_ifc_class(project, "IfcSite")
|
||||
for site in sites:
|
||||
site.Representation = None
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -26,8 +26,8 @@ class Patcher:
|
||||
|
||||
def patch(self):
|
||||
placement_coord_ids = set()
|
||||
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')]
|
||||
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")]
|
||||
|
||||
# Arbitrary threshold based on experience
|
||||
self.threshold = 1000000
|
||||
@@ -42,9 +42,9 @@ class Patcher:
|
||||
# the case, but is very fast to run, and works for most cases.
|
||||
offset_point = None
|
||||
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:
|
||||
point_lists = self.file.by_type('IfcCartesianPointList3D')
|
||||
point_lists = self.file.by_type("IfcCartesianPointList3D")
|
||||
except:
|
||||
# IFC2X3 does not have IfcCartesianPointList3D
|
||||
point_lists = []
|
||||
@@ -56,33 +56,29 @@ class Patcher:
|
||||
continue
|
||||
if not offset_point:
|
||||
offset_point = (-point[0], -point[1], -point[2])
|
||||
self.logger.info(f'Resetting absolute coordinates by {point}')
|
||||
point = (
|
||||
point[0] + offset_point[0],
|
||||
point[1] + offset_point[1],
|
||||
point[2] + offset_point[2]
|
||||
)
|
||||
self.logger.info(f"Resetting absolute coordinates by {point}")
|
||||
point = (point[0] + offset_point[0], point[1] + offset_point[1], point[2] + offset_point[2])
|
||||
coord_list[i] = point
|
||||
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):
|
||||
continue
|
||||
if point.id() in placement_coord_ids:
|
||||
continue
|
||||
if not offset_point:
|
||||
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[0] + offset_point[0],
|
||||
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):
|
||||
if hasattr(point, 'Coordinates'):
|
||||
return abs(point.Coordinates[0]) > self.threshold \
|
||||
or abs(point.Coordinates[1]) > self.threshold \
|
||||
if hasattr(point, "Coordinates"):
|
||||
return (
|
||||
abs(point.Coordinates[0]) > self.threshold
|
||||
or abs(point.Coordinates[1]) > self.threshold
|
||||
or abs(point.Coordinates[2]) > self.threshold
|
||||
return abs(point[0]) > self.threshold \
|
||||
or abs(point[1]) > self.threshold \
|
||||
or abs(point[2]) > self.threshold
|
||||
)
|
||||
return abs(point[0]) > self.threshold or abs(point[1]) > self.threshold or abs(point[2]) > self.threshold
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -25,7 +25,7 @@ class Patcher:
|
||||
self.args = args
|
||||
|
||||
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])
|
||||
for spatial_element in spatial_elements:
|
||||
self.patch_placement_to_origin(spatial_element)
|
||||
@@ -43,8 +43,8 @@ class Patcher:
|
||||
return results
|
||||
|
||||
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:
|
||||
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0., 0., 1.)
|
||||
element.ObjectPlacement.RelativePlacement.Axis.DirectionRatios = (0.0, 0.0, 1.0)
|
||||
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
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -25,8 +25,8 @@ class Patcher:
|
||||
self.args = args
|
||||
|
||||
def patch(self):
|
||||
project = self.file.by_type('IfcProject')[0]
|
||||
sites = self.find_decomposed_ifc_class(project, 'IfcSite')
|
||||
project = self.file.by_type("IfcProject")[0]
|
||||
sites = self.find_decomposed_ifc_class(project, "IfcSite")
|
||||
for site in sites:
|
||||
site.RefElevation = float(self.args[0])
|
||||
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Patcher:
|
||||
def __init__(self, src, file, logger, args=None):
|
||||
self.src = src
|
||||
@@ -27,35 +27,39 @@ class Patcher:
|
||||
def patch(self):
|
||||
import ifcopenshell
|
||||
from shutil import copyfile
|
||||
storeys = self.file.by_type('IfcBuildingStorey')
|
||||
|
||||
storeys = self.file.by_type("IfcBuildingStorey")
|
||||
for i, storey in enumerate(storeys):
|
||||
dest = '{}-{}.ifc'.format(i, storey.Name)
|
||||
dest = "{}-{}.ifc".format(i, storey.Name)
|
||||
copyfile(self.src, dest)
|
||||
old_ifc = ifcopenshell.open(dest)
|
||||
new_ifc = ifcopenshell.file(schema=self.file.schema)
|
||||
if self.file.schema == 'IFC2X3':
|
||||
elements = old_ifc.by_type('IfcProject') + old_ifc.by_type('IfcProduct')
|
||||
if self.file.schema == "IFC2X3":
|
||||
elements = old_ifc.by_type("IfcProject") + old_ifc.by_type("IfcProduct")
|
||||
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 = []
|
||||
for element in elements:
|
||||
if element.is_a('IfcElement') \
|
||||
and not self.is_in_storey(element, storey):
|
||||
if element.is_a("IfcElement") and not self.is_in_storey(element, storey):
|
||||
element.Representation = None
|
||||
continue
|
||||
if element.is_a('IfcElement'):
|
||||
styled_rep_items = [i for i in old_ifc.traverse(element) if i.is_a('IfcRepresentationItem') and i.StyledByItem]
|
||||
if element.is_a("IfcElement"):
|
||||
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(element)
|
||||
inverse_elements.extend(old_ifc.get_inverse(element))
|
||||
for inverse_element in inverse_elements:
|
||||
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):
|
||||
new_ifc.remove(element)
|
||||
new_ifc.write(dest)
|
||||
|
||||
def is_in_storey(self, element, storey):
|
||||
return element.ContainedInStructure \
|
||||
and element.ContainedInStructure[0].RelatingStructure.is_a('IfcBuildingStorey') \
|
||||
return (
|
||||
element.ContainedInStructure
|
||||
and element.ContainedInStructure[0].RelatingStructure.is_a("IfcBuildingStorey")
|
||||
and element.ContainedInStructure[0].RelatingStructure.GlobalId == storey.GlobalId
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
# IfcPatch - IFC patching utiliy
|
||||
# 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
|
||||
# along with IfcPatch. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
@@ -23,4 +23,3 @@ import subprocess
|
||||
|
||||
cmd = f'pyinstaller ./bootstrap.py --name ifcpatch --onefile --clean --add-data "ifcpatch{os.pathsep}ifcpatch"'
|
||||
subprocess.check_output(cmd, shell=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user