mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-23 12:32:39 +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
|
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"],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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,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,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/>.
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user