mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-27 10:51:13 +00:00
Merge branch 'v0.6.0' into GSoC#45-IDS-checking
This commit is contained in:
@@ -0,0 +1 @@
|
||||
tests/
|
||||
@@ -0,0 +1,250 @@
|
||||
import operator
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
import numpy
|
||||
|
||||
import ifcopenshell
|
||||
import ifcopenshell.geom
|
||||
import ifcopenshell.express
|
||||
import ifcopenshell.transition_curve
|
||||
|
||||
# geometric primitives
|
||||
|
||||
# @notes
|
||||
# - not sure if the separation of geometric primitives make sense
|
||||
# does it make handling the variety of distance expressions and
|
||||
# interpolation harder?
|
||||
|
||||
|
||||
@dataclass
|
||||
class line:
|
||||
start_point: numpy.ndarray
|
||||
direction_vector: numpy.ndarray
|
||||
|
||||
def __call__(self, u):
|
||||
p = numpy.ndarray((3,))
|
||||
p[0:2] = self.start_point + self.direction_vector * u
|
||||
p[2] = numpy.nan
|
||||
return p
|
||||
|
||||
|
||||
@dataclass
|
||||
class circle:
|
||||
radius: numpy.ndarray
|
||||
|
||||
def __call__(self, u):
|
||||
return numpy.array(
|
||||
[self.radius * numpy.cos(u), self.radius * numpy.sin(u), numpy.nan]
|
||||
)
|
||||
|
||||
|
||||
def place(matrix, func):
|
||||
"""
|
||||
Higher order function for application of a 3x3 matrix
|
||||
to a 2D point. Assumes a functor such as line or circle.
|
||||
"""
|
||||
|
||||
def inner(*args):
|
||||
v = func(*args)
|
||||
# homogenize
|
||||
v = numpy.insert(v[0:2], v[0:2].shape, 1, axis=-1)
|
||||
p = numpy.ndarray((3,))
|
||||
p[0:2] = (matrix @ v)[0:2]
|
||||
p[2] = numpy.nan
|
||||
return p
|
||||
|
||||
return inner
|
||||
|
||||
|
||||
# primitives for manipulating and joining curve functor domains
|
||||
|
||||
|
||||
def reparametrized_curve(fn, a, b):
|
||||
return lambda u: fn(a * u + b)
|
||||
|
||||
|
||||
def normalized_curve(fn):
|
||||
return lambda u: fn(u / fn.length)
|
||||
|
||||
|
||||
class trimmed_curve:
|
||||
def __init__(self, fn, length):
|
||||
self.fn = fn
|
||||
self.length = length
|
||||
|
||||
def __call__(self, u):
|
||||
assert u >= 0.0 and u <= self.length
|
||||
return self.fn(u)
|
||||
|
||||
|
||||
class piecewise:
|
||||
# takes a set of functors and returns a function f(u) that delegates to the correct segment
|
||||
|
||||
def __init__(self, fns):
|
||||
self.fns = fns
|
||||
self.length = sum(map(operator.attrgetter("length"), fns))
|
||||
|
||||
def __call__(self, u):
|
||||
# this is silly, assuming `u` is monotonically increases we should not always start
|
||||
# searching from the first segment or at least binary search into the segment
|
||||
# lengths
|
||||
u0 = 0
|
||||
for fn in self.fns:
|
||||
u1 = u0 + fn.length
|
||||
if u >= u0 and u <= u1:
|
||||
return fn(u - u0)
|
||||
u0 = u1
|
||||
|
||||
|
||||
# mapping functions from IFC entities
|
||||
|
||||
|
||||
def map_inst(inst):
|
||||
"""
|
||||
Looks up one of the implementation functions below in the global namespace
|
||||
"""
|
||||
return globals()[f"impl_{inst.is_a()}"](inst)
|
||||
|
||||
|
||||
def impl_IfcLine(inst):
|
||||
return line(
|
||||
numpy.array(inst.Pnt.Coordinates),
|
||||
numpy.array(inst.Dir.Orientation.DirectionRatios) * inst.Dir.Magnitude,
|
||||
)
|
||||
|
||||
|
||||
def impl_IfcCircle(inst):
|
||||
return place(map_inst(inst.Position), circle(inst.Radius))
|
||||
|
||||
|
||||
def impl_IfcClothoid(inst):
|
||||
# @todo
|
||||
# place = map_inst(inst.Position)
|
||||
# ifcopenshell.transition_curve.TransitionCurve(
|
||||
# StartPoint = place.T[2]
|
||||
# StartDirection = numpy.arctan2(place.T[0][1], place.T[0][0]),
|
||||
# SegmentLength =
|
||||
# IsStartRadiusCCW =
|
||||
# IsEndRadiusCCW =
|
||||
# TransitionCurveType =
|
||||
# StartRadius =
|
||||
# EndRadius =
|
||||
# )
|
||||
return lambda *args: numpy.array((0.0, 0.0))
|
||||
|
||||
|
||||
def impl_IfcAxis2Placement2D(inst):
|
||||
arr = numpy.eye(3)
|
||||
|
||||
if inst is None:
|
||||
return arr
|
||||
|
||||
arr.T[2, 0:2] = inst.Location.Coordinates
|
||||
|
||||
if inst.RefDirection is None:
|
||||
return arr
|
||||
|
||||
arr.T[0, 0:2] = inst.RefDirection.DirectionRatios
|
||||
arr.T[0, 0:2] /= numpy.linalg.norm(arr.T[0, 0:2])
|
||||
arr.T[1, 0:2] = -arr.T[0, 1], arr.T[0, 0]
|
||||
|
||||
return arr
|
||||
|
||||
|
||||
# conversion functions for semantic design parameters (not used atm)
|
||||
|
||||
|
||||
def convert(inst):
|
||||
"""
|
||||
Looks up one of the conversion functions below in the global namespace
|
||||
"""
|
||||
yield from globals()[f"convert_{inst.is_a()}_{inst.PredefinedType}"](inst)
|
||||
|
||||
|
||||
def convert_IfcAlignmentHorizontalSegment_LINE(data):
|
||||
xy = numpy.array(data.StartPoint.Coordinates)
|
||||
yield xy
|
||||
di = numpy.array([numpy.cos(data.StartDirection), numpy.sin(data.StartDirection)])
|
||||
yield xy + di * data.SegmentLength
|
||||
|
||||
|
||||
# Two approaches, either DesignParameters or Representation
|
||||
|
||||
|
||||
def interpret_linear_element_semantics(settings, crv):
|
||||
# traverse decomposition
|
||||
for rel in crv.IsNestedBy:
|
||||
for obj in rel.RelatedObjects:
|
||||
yield from interpret_linear_element_semantics(settings, obj)
|
||||
|
||||
# lookup design parameters and dispatch to conversion function
|
||||
if crv.is_a("IfcAlignmentSegment"):
|
||||
dp = crv.DesignParameters
|
||||
yield from convert(dp)
|
||||
|
||||
|
||||
def evaluate_segment(segment):
|
||||
# print(segment)
|
||||
# print(segment.ParentCurve)
|
||||
# print()
|
||||
|
||||
func = place(map_inst(segment.Placement), map_inst(segment.ParentCurve))
|
||||
|
||||
# reparam so domain starts at zero
|
||||
reparam = reparametrized_curve(func, 1.0, -segment.SegmentStart[0])
|
||||
|
||||
# embed curve length (doesn't do much, just make length recoverable)
|
||||
trimmed = trimmed_curve(reparam, segment.SegmentLength[0])
|
||||
|
||||
return trimmed
|
||||
|
||||
|
||||
def interpret_linear_element_geometry(settings, crv):
|
||||
func = piecewise(
|
||||
list(
|
||||
map(
|
||||
evaluate_segment,
|
||||
crv.Representation.Representations[0].Items[0].Segments,
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for u in numpy.linspace(0, func.length, num=int(numpy.ceil(func.length / 0.05))):
|
||||
yield func(u)
|
||||
|
||||
|
||||
interpret_linear_element = interpret_linear_element_geometry
|
||||
|
||||
|
||||
def create_shape(settings, elem):
|
||||
if elem.is_a("IfcLinearPositioningElement") or elem.is_a("IfcLinearElement"):
|
||||
return numpy.row_stack(list(interpret_linear_element(settings, elem)))
|
||||
else:
|
||||
return ifcopenshell.geom.create_shape(settings, elem)
|
||||
|
||||
|
||||
def print_structure(alignment, indent=0):
|
||||
"""
|
||||
Debugging function to print alignment decomposition
|
||||
"""
|
||||
print(" " * indent, str(alignment)[0:100])
|
||||
for rel in alignment.IsNestedBy:
|
||||
for child in rel.RelatedObjects:
|
||||
print_structure(child, indent + 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
from matplotlib import pyplot as plt
|
||||
|
||||
s = ifcopenshell.express.parse("IFC4x3_RC3.exp")
|
||||
ifcopenshell.register_schema(s)
|
||||
f = ifcopenshell.open(sys.argv[1])
|
||||
print_structure(f.by_type("IfcAlignment")[0])
|
||||
|
||||
al_hor = f.by_type("IfcAlignmentHorizontal")[0]
|
||||
xy = create_shape({}, al_hor)
|
||||
|
||||
plt.plot(xy.T[0], xy.T[1])
|
||||
plt.savefig("horizontal_alignment.png")
|
||||
@@ -1,11 +1,99 @@
|
||||
import json
|
||||
import numpy
|
||||
import importlib
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
def run(usecase_path, ifc_file=None, **settings):
|
||||
pre_listeners = {}
|
||||
post_listeners = {}
|
||||
|
||||
|
||||
def run(usecase_path, ifc_file=None, should_run_listeners=True, **settings):
|
||||
if should_run_listeners:
|
||||
for listener in pre_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
def serialise_entity_instance(entity):
|
||||
return {"cast_type": "entity_instance", "value": entity.id(), "Name": getattr(entity, "Name", None)}
|
||||
|
||||
vcs_settings = settings.copy()
|
||||
for key, value in settings.items():
|
||||
if isinstance(value, ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = serialise_entity_instance(value)
|
||||
elif isinstance(value, numpy.ndarray):
|
||||
vcs_settings[key] = {"cast_type": "ndarray", "value": value.tolist()}
|
||||
elif isinstance(value, list) and value and isinstance(value[0], ifcopenshell.entity_instance):
|
||||
vcs_settings[key] = [serialise_entity_instance(i) for i in value]
|
||||
if "add_representation" in usecase_path:
|
||||
pass
|
||||
# print(usecase_path, "{ ... settings too complex right now ... }")
|
||||
elif "owner." in usecase_path:
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
# print(vcs_settings)
|
||||
# try:
|
||||
# print(usecase_path, json.dumps(vcs_settings))
|
||||
# except:
|
||||
# print(usecase_path, vcs_settings)
|
||||
|
||||
importlib.import_module(f"ifcopenshell.api.{usecase_path}")
|
||||
module, usecase = usecase_path.split(".")
|
||||
usecase_class = getattr(getattr(getattr(ifcopenshell.api, module), usecase), "Usecase")
|
||||
|
||||
if ifc_file:
|
||||
return usecase_class(ifc_file, **settings).execute()
|
||||
return usecase_class(**settings).execute()
|
||||
result = usecase_class(ifc_file, **settings).execute()
|
||||
else:
|
||||
result = usecase_class(**settings).execute()
|
||||
|
||||
if should_run_listeners:
|
||||
for listener in post_listeners.get(usecase_path, {}).values():
|
||||
listener(usecase_path, ifc_file, settings)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def add_pre_listener(usecase_path, name, callback):
|
||||
"""Add a pre listener
|
||||
|
||||
:param usecase_path: string, ifcopenshell api use case path
|
||||
:param name: string, name of listener
|
||||
:param callback: callback function
|
||||
"""
|
||||
pre_listeners.setdefault(usecase_path, {})[name] = callback
|
||||
|
||||
|
||||
def add_post_listener(usecase_path, name, callback):
|
||||
"""Add a post listener
|
||||
|
||||
:param usecase_path: string, ifcopenshell api use case path
|
||||
:param name: string, name of listener
|
||||
:param callback: callback function
|
||||
"""
|
||||
post_listeners.setdefault(usecase_path, {})[name] = callback
|
||||
|
||||
|
||||
def remove_pre_listener(usecase_path, name, callback):
|
||||
"""Remove a pre listener
|
||||
|
||||
:param usecase_path: string, ifcopenshell api use case path
|
||||
:param name: string, name of listener
|
||||
:param callback: callback function
|
||||
"""
|
||||
pre_listeners.get(usecase_path, {}).pop(name, None)
|
||||
|
||||
|
||||
def remove_post_listener(usecase_path, name, callback):
|
||||
"""Remove a post listener
|
||||
|
||||
:param usecase_path: string, ifcopenshell api use case path
|
||||
:param name: string, name of listener
|
||||
:param callback: callback function
|
||||
"""
|
||||
post_listeners.get(usecase_path, {}).pop(name, None)
|
||||
|
||||
|
||||
def remove_all_listeners():
|
||||
pre_listeners.clear()
|
||||
post_listeners.clear()
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
class Data:
|
||||
is_loaded = False
|
||||
boundaries = {}
|
||||
spaces = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.is_loaded = False
|
||||
cls.boundaries = {}
|
||||
cls.spaces = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
cls._file = file
|
||||
for boundary in cls._file.by_type("IfcRelSpaceBoundary"):
|
||||
data = boundary.get_info()
|
||||
data["RelatingSpace"] = data["RelatingSpace"].id() if data["RelatingSpace"] else None
|
||||
data["RelatedBuildingElement"] = (
|
||||
data["RelatedBuildingElement"].id() if data["RelatedBuildingElement"] else None
|
||||
)
|
||||
del data["ConnectionGeometry"]
|
||||
if cls._file.schema == "IFC2X3":
|
||||
pass
|
||||
else:
|
||||
if boundary.is_a("IfcRelSpaceBoundary1stLevel"):
|
||||
data["ParentBoundary"] = data["ParentBoundary"].id() if data["ParentBoundary"] else None
|
||||
if boundary.is_a("IfcRelSpaceBoundary2ndLevel"):
|
||||
data["CorrespondingBoundary"] = (
|
||||
data["CorrespondingBoundary"].id() if data["CorrespondingBoundary"] else None
|
||||
)
|
||||
cls.boundaries[boundary.id()] = data
|
||||
cls.spaces.setdefault(data["RelatingSpace"], []).append(boundary.id())
|
||||
cls.is_loaded = True
|
||||
@@ -1,16 +0,0 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "ifc_class": "IfcQuantityCount"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
value = self.file.create_entity("IfcCostValue")
|
||||
values = list(self.settings["cost_item"].CostValues or [])
|
||||
values.append(value)
|
||||
self.settings["cost_item"].CostValues = values
|
||||
return value
|
||||
@@ -0,0 +1,18 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"parent": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
value = self.file.create_entity("IfcCostValue")
|
||||
if self.settings["parent"].is_a("IfcCostItem"):
|
||||
values = list(self.settings["parent"].CostValues or [])
|
||||
values.append(value)
|
||||
self.settings["parent"].CostValues = values
|
||||
elif self.settings["parent"].is_a("IfcCostValue"):
|
||||
values = list(self.settings["parent"].Components or [])
|
||||
values.append(value)
|
||||
self.settings["parent"].Components = values
|
||||
return value
|
||||
@@ -0,0 +1,31 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "products": []}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
quantity_names = set()
|
||||
for quantity in self.settings["cost_item"].CostQuantities or []:
|
||||
if quantity.Name:
|
||||
quantity_names.add(quantity.Name)
|
||||
|
||||
for product in self.settings["products"]:
|
||||
ifcopenshell.api.run(
|
||||
"control.assign_control",
|
||||
self.file,
|
||||
related_object=product,
|
||||
relating_control=self.settings["cost_item"],
|
||||
)
|
||||
|
||||
for name in quantity_names:
|
||||
ifcopenshell.api.run(
|
||||
"cost.assign_cost_item_product_quantities",
|
||||
self.file,
|
||||
cost_item=self.settings["cost_item"],
|
||||
prop_name=name
|
||||
)
|
||||
+2
-2
@@ -4,7 +4,7 @@ import ifcopenshell.api
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "qto_name": "", "prop_name": ""}
|
||||
self.settings = {"cost_item": None, "prop_name": ""}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
@@ -25,7 +25,7 @@ class Usecase:
|
||||
self.add_quantity_from_qto(relationship.RelatingPropertyDefinition)
|
||||
|
||||
def add_quantity_from_qto(self, qto):
|
||||
if not qto.is_a("IfcElementQuantity") or qto.Name.lower() != self.settings["qto_name"].lower():
|
||||
if not qto.is_a("IfcElementQuantity"):
|
||||
return
|
||||
for prop in qto.Quantities:
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity") and prop.Name.lower() == self.settings["prop_name"].lower():
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"source": None, "destination": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for cost_value in self.settings["destination"].CostValues or []:
|
||||
ifcopenshell.api.run("cost.remove_cost_item_value", self.file, cost_value=cost_value)
|
||||
copied_cost_values = []
|
||||
for cost_value in self.settings["source"].CostValues or []:
|
||||
copied_cost_values.append(ifcopenshell.util.element.copy_deep(self.file, cost_value))
|
||||
self.settings["destination"].CostValues = copied_cost_values
|
||||
@@ -87,12 +87,31 @@ class Data:
|
||||
value_data["Components"] = [c.id() for c in value_data["Components"] or []]
|
||||
value_data["AppliedValue"] = cls.calculate_applied_value(cost_item, cost_value)
|
||||
cls.cost_values[cost_value.id()] = value_data
|
||||
for component in cost_value.Components or []:
|
||||
cls.load_cost_item_value(cost_item, component)
|
||||
|
||||
@classmethod
|
||||
def calculate_applied_value(cls, cost_item, cost_value, category_filter=None):
|
||||
result = 0
|
||||
if cost_value.ArithmeticOperator and cost_value.Components:
|
||||
pass # TODO
|
||||
component_values = []
|
||||
for component in cost_value.Components:
|
||||
component_values.append(cls.calculate_applied_value(cost_item, component, category_filter))
|
||||
if cost_value.ArithmeticOperator == "ADD":
|
||||
return sum(component_values)
|
||||
result = component_values.pop(0)
|
||||
if cost_value.ArithmeticOperator == "DIVIDE":
|
||||
for value in component_values:
|
||||
try:
|
||||
result /= value
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
elif cost_value.ArithmeticOperator == "MULTIPLY":
|
||||
for value in component_values:
|
||||
result *= value
|
||||
elif cost_value.ArithmeticOperator == "SUBTRACT":
|
||||
for value in component_values:
|
||||
result -= value
|
||||
return result
|
||||
if cost_value.Category is None:
|
||||
return cls.get_primitive_applied_value(cost_value.AppliedValue)
|
||||
elif cost_value.Category == "*":
|
||||
@@ -105,7 +124,7 @@ class Data:
|
||||
return cls.sum_child_cost_items(cost_item, category_filter=cost_value.Category)
|
||||
else:
|
||||
return cls.get_primitive_applied_value(cost_value.AppliedValue)
|
||||
return result
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def sum_child_cost_items(cls, cost_item, category_filter=None):
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -7,4 +10,13 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
for inverse in self.file.get_inverse(self.settings["cost_item"]):
|
||||
if inverse.is_a("IfcRelNests"):
|
||||
if inverse.RelatingObject == self.settings["cost_item"]:
|
||||
for related_object in inverse.RelatedObjects:
|
||||
ifcopenshell.api.run("cost.remove_cost_item", self.file, cost_item=related_object)
|
||||
elif inverse.RelatedObjects == tuple(self.settings["cost_item"]):
|
||||
self.file.remove(inverse)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
self.file.remove(inverse)
|
||||
self.file.remove(self.settings["cost_item"])
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"cost_item": None, "products": []}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.quantities = set(self.settings["cost_item"].CostQuantities or [])
|
||||
for quantity in self.settings["cost_item"].CostQuantities or []:
|
||||
for inverse in self.file.get_inverse(quantity):
|
||||
if not inverse.is_a("IfcElementQuantity"):
|
||||
continue
|
||||
for rel in inverse.DefinesOccurrence or []:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object in self.settings["products"]:
|
||||
self.quantities.remove(quantity)
|
||||
self.settings["cost_item"].CostQuantities = list(self.quantities)
|
||||
|
||||
for product in self.settings["products"]:
|
||||
ifcopenshell.api.run(
|
||||
"control.unassign_control",
|
||||
self.file,
|
||||
related_object=product,
|
||||
relating_control=self.settings["cost_item"],
|
||||
)
|
||||
@@ -1,9 +1,13 @@
|
||||
import bpy
|
||||
import bmesh
|
||||
import ifcopenshell.util.unit
|
||||
from mathutils import Vector
|
||||
from mathutils import Vector, Matrix
|
||||
from blenderbim.bim.module.geometry.helper import Helper
|
||||
|
||||
Z_AXIS = Vector((0, 0, 1))
|
||||
X_AXIS = Vector((1, 0, 0))
|
||||
EPSILON = 1e-6
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
@@ -25,15 +29,20 @@ class Usecase:
|
||||
# IfcExtrudedAreaSolid/IfcRectangleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcCircleProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryClosedProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryProfileDef
|
||||
# IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids
|
||||
# IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage
|
||||
"ifc_representation_class": None, # Whether to cast a mesh into a particular class
|
||||
"profile_set_usage": None, # The material profile set if the extrusion requires it
|
||||
}
|
||||
self.ifc_vertices = []
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if isinstance(self.settings["geometry"], bpy.types.Mesh):
|
||||
if (
|
||||
isinstance(self.settings["geometry"], bpy.types.Mesh)
|
||||
and self.settings["geometry"] == self.settings["blender_object"].data
|
||||
):
|
||||
self.evaluate_geometry()
|
||||
if self.settings["unit_scale"] is None:
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
@@ -43,35 +52,45 @@ class Usecase:
|
||||
return self.create_plan_representation()
|
||||
return self.create_variable_representation()
|
||||
|
||||
def evaluate_geometry(self):
|
||||
self.boolean_modifiers = []
|
||||
for modifier in self.settings["blender_object"].modifiers:
|
||||
if not modifier.type == "BOOLEAN":
|
||||
continue
|
||||
modifier_data = {}
|
||||
for name in ["operation", "operand_type", "object", "solver", "use_self"]:
|
||||
modifier_data[name] = getattr(modifier, name)
|
||||
self.boolean_modifiers.append(modifier_data)
|
||||
self.settings["blender_object"].modifiers.remove(modifier)
|
||||
|
||||
if self.settings["should_force_triangulation"]:
|
||||
mesh = self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
del bm
|
||||
self.settings["geometry"] = mesh
|
||||
def should_triangulate_face(self, face, threshold=EPSILON):
|
||||
vz = face.normal
|
||||
co = face.verts[0].co
|
||||
if vz.length < 0.5:
|
||||
return True
|
||||
if abs(vz.z) < 0.5:
|
||||
vx = vz.cross(Z_AXIS)
|
||||
else:
|
||||
self.settings["geometry"] = (
|
||||
self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
)
|
||||
vx = vz.cross(X_AXIS)
|
||||
vy = vx.cross(vz)
|
||||
tM = Matrix(
|
||||
[[vx.x, vy.x, vz.x, co.x], [vx.y, vy.y, vz.y, co.y], [vx.z, vy.z, vz.z, co.z], [0, 0, 0, 1]]
|
||||
).inverted()
|
||||
|
||||
for modifier in self.boolean_modifiers:
|
||||
new = self.settings["blender_object"].modifiers.new("IfcOpeningElement", "BOOLEAN")
|
||||
for key, value in modifier.items():
|
||||
setattr(new, key, value)
|
||||
return any([abs((tM @ v.co).z) > threshold for v in face.verts])
|
||||
|
||||
def evaluate_geometry(self):
|
||||
for modifier in self.settings["blender_object"].modifiers:
|
||||
if modifier.type == "BOOLEAN":
|
||||
modifier.show_viewport = False
|
||||
|
||||
mesh = self.settings["blender_object"].evaluated_get(bpy.context.evaluated_depsgraph_get()).to_mesh()
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(mesh)
|
||||
if self.settings["should_force_triangulation"]:
|
||||
faces = bm.faces
|
||||
else:
|
||||
faces = [f for f in bm.faces if self.should_triangulate_face(f)]
|
||||
bmesh.ops.triangulate(bm, faces=faces)
|
||||
bm.to_mesh(mesh)
|
||||
mesh.update()
|
||||
bm.free()
|
||||
del bm
|
||||
|
||||
self.settings["geometry"] = mesh
|
||||
|
||||
for modifier in self.settings["blender_object"].modifiers:
|
||||
if modifier.type == "BOOLEAN":
|
||||
modifier.show_viewport = True
|
||||
|
||||
def create_model_representation(self):
|
||||
if self.settings["context"].is_a() == "IfcGeometricRepresentationContext":
|
||||
@@ -97,6 +116,8 @@ class Usecase:
|
||||
return self.create_curve3d_representation()
|
||||
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
|
||||
return self.create_geometric_curve_set_representation()
|
||||
elif self.settings["context"].ContextIdentifier == "Lighting":
|
||||
return self.create_lighting_representation()
|
||||
|
||||
def create_plan_representation(self):
|
||||
if self.settings["context"].ContextIdentifier == "Annotation":
|
||||
@@ -116,7 +137,7 @@ class Usecase:
|
||||
elif self.settings["context"].ContextIdentifier == "CoG":
|
||||
pass
|
||||
elif self.settings["context"].ContextIdentifier == "FootPrint":
|
||||
if self.settings["context"].TargetView in ["PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||
if self.settings["context"].TargetView in ["SKETCH_VIEW", "PLAN_VIEW", "REFLECTED_PLAN_VIEW"]:
|
||||
return self.create_geometric_curve_set_representation(is_2d=True)
|
||||
elif self.settings["context"].ContextIdentifier == "Reference":
|
||||
pass
|
||||
@@ -125,11 +146,68 @@ class Usecase:
|
||||
elif self.settings["context"].ContextIdentifier == "SurveyPoints":
|
||||
pass
|
||||
|
||||
def create_lighting_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"LightSource",
|
||||
[self.create_light_source()],
|
||||
)
|
||||
|
||||
def create_light_source(self):
|
||||
if self.settings["geometry"].type == "POINT":
|
||||
return self.create_light_source_positional()
|
||||
|
||||
def create_light_source_positional(self):
|
||||
return self.file.create_entity(
|
||||
"IfcLightSourcePositional",
|
||||
**{
|
||||
"LightColour": self.file.createIfcColourRgb(None, *self.settings["geometry"].color),
|
||||
"Position": self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||
"Radius": self.convert_si_to_unit(self.settings["geometry"].shadow_soft_size),
|
||||
},
|
||||
)
|
||||
|
||||
def create_text_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Annotation2D",
|
||||
[self.create_text()],
|
||||
)
|
||||
|
||||
def create_text(self):
|
||||
text = self.settings["geometry"]
|
||||
if text.align_y in ["TOP_BASELINE", "BOTTOM_BASELINE", "BOTTOM"]:
|
||||
y = "bottom"
|
||||
elif text.align_y == "CENTER":
|
||||
y = "middle"
|
||||
elif text.align_y == "TOP":
|
||||
y = "top"
|
||||
|
||||
if text.align_x == "LEFT":
|
||||
x = "left"
|
||||
elif text.align_x == "CENTER":
|
||||
x = "middle"
|
||||
elif text.align_x == "RIGHT":
|
||||
x = "right"
|
||||
|
||||
origin = self.file.createIfcAxis2Placement3D(
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||
self.file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||
)
|
||||
|
||||
# TODO: Planar extent right now is wrong ...
|
||||
return self.file.createIfcTextLiteralWithExtent(
|
||||
text.body, origin, "RIGHT", self.file.createIfcPlanarExtent(1000, 1000), f"{y}-{x}"
|
||||
)
|
||||
|
||||
def create_variable_representation(self):
|
||||
if self.settings["is_wireframe"]:
|
||||
return self.create_wireframe_representation()
|
||||
elif self.settings["is_curve"]:
|
||||
return self.create_curve_representation()
|
||||
return self.create_curve3d_representation()
|
||||
elif self.settings["is_point_cloud"]:
|
||||
return self.create_point_cloud_representation()
|
||||
elif isinstance(self.settings["geometry"], bpy.types.Camera):
|
||||
@@ -142,6 +220,8 @@ class Usecase:
|
||||
return self.create_arbitrary_extrusion_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcArbitraryProfileDefWithVoids":
|
||||
return self.create_arbitrary_void_extrusion_representation()
|
||||
elif self.settings["ifc_representation_class"] == "IfcExtrudedAreaSolid/IfcMaterialProfileSetUsage":
|
||||
return self.create_material_profile_set_extrusion_representation()
|
||||
return self.create_mesh_representation()
|
||||
|
||||
def create_camera_block_representation(self):
|
||||
@@ -164,7 +244,7 @@ class Usecase:
|
||||
"XLength": self.convert_si_to_unit(width),
|
||||
"YLength": self.convert_si_to_unit(height),
|
||||
"ZLength": self.convert_si_to_unit(self.settings["geometry"].clip_end),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
@@ -188,6 +268,14 @@ class Usecase:
|
||||
self.create_curves(),
|
||||
)
|
||||
|
||||
def create_curve2d_representation(self):
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"Curve2D",
|
||||
self.create_curves(is_2d=True),
|
||||
)
|
||||
|
||||
def create_curves(self, is_2d=False):
|
||||
if isinstance(self.settings["geometry"], bpy.types.Mesh):
|
||||
if self.file.schema == "IFC2X3":
|
||||
@@ -223,7 +311,7 @@ class Usecase:
|
||||
def create_curves_from_mesh_ifc2x3(self, is_2d=False):
|
||||
curves = []
|
||||
points = [
|
||||
self.create_cartesian_point(v.co.x, v.co.y, v.co.z if is_2d else None)
|
||||
self.create_cartesian_point(v.co.x, v.co.y, v.co.z if not is_2d else None)
|
||||
for v in self.settings["geometry"].vertices
|
||||
]
|
||||
coord_list = [p.Coordinates for p in points]
|
||||
@@ -308,6 +396,8 @@ class Usecase:
|
||||
def create_arbitrary_void_extrusion_representation(self):
|
||||
helper = Helper(self.file)
|
||||
indices = helper.auto_detect_arbitrary_profile_with_voids_extruded_area_solid(self.settings["geometry"])
|
||||
if not indices["inner_curves"]:
|
||||
return self.create_arbitrary_extrusion_representation()
|
||||
profile_def = helper.create_arbitrary_profile_def_with_voids(
|
||||
self.settings["geometry"], indices["profile"], indices["inner_curves"]
|
||||
)
|
||||
@@ -319,6 +409,29 @@ class Usecase:
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_material_profile_set_extrusion_representation(self):
|
||||
profile_set = self.settings["profile_set_usage"].ForProfileSet
|
||||
profile_def = profile_set.CompositeProfile or profile_set.MaterialProfiles[0].Profile
|
||||
position = None
|
||||
if self.file.schema == "IFC2X3":
|
||||
position = self.file.createIfcAxis2Placement3D(
|
||||
self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)),
|
||||
self.file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||
self.file.createIfcDirection((1.0, 0.0, 0.0)),
|
||||
)
|
||||
item = self.file.createIfcExtrudedAreaSolid(
|
||||
profile_def,
|
||||
position,
|
||||
self.file.createIfcDirection((0.0, 0.0, 1.0)),
|
||||
self.convert_si_to_unit(self.settings["blender_object"].dimensions[2]),
|
||||
)
|
||||
return self.file.createIfcShapeRepresentation(
|
||||
self.settings["context"],
|
||||
self.settings["context"].ContextIdentifier,
|
||||
"SweptSolid",
|
||||
[item],
|
||||
)
|
||||
|
||||
def create_mesh_representation(self):
|
||||
if self.file.schema == "IFC2X3" or self.settings["should_force_faceted_brep"]:
|
||||
return self.create_faceted_brep()
|
||||
|
||||
@@ -35,4 +35,18 @@ class Usecase:
|
||||
)
|
||||
)
|
||||
self.settings["product"].RepresentationMaps = maps
|
||||
if self.file.schema == "IFC2X3":
|
||||
types = self.settings["product"].ObjectTypeOf
|
||||
else:
|
||||
types = self.settings["product"].Types
|
||||
if types:
|
||||
for element in types[0].RelatedObjects:
|
||||
mapped_representation = ifcopenshell.api.run(
|
||||
"geometry.map_representation", self.file, **{"representation": self.settings["representation"]}
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation",
|
||||
self.file,
|
||||
**{"product": element, "representation": mapped_representation}
|
||||
)
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": self.settings["product"]})
|
||||
|
||||
@@ -14,11 +14,8 @@ class Data:
|
||||
cls.products[product_id] = []
|
||||
product = file.by_id(product_id)
|
||||
representations = []
|
||||
if product.is_a("IfcProduct"):
|
||||
if product.Representation:
|
||||
representations = product.Representation.Representations
|
||||
else:
|
||||
representations = []
|
||||
if product.is_a("IfcProduct") and product.Representation:
|
||||
representations = product.Representation.Representations
|
||||
elif product.is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in product.RepresentationMaps or []]
|
||||
for representation in representations:
|
||||
|
||||
@@ -33,6 +33,15 @@ class Usecase:
|
||||
elif hasattr(self.settings["product"], "Decomposes") and self.settings["product"].Decomposes:
|
||||
relating_object = self.settings["product"].Decomposes[0].RelatingObject
|
||||
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif hasattr(self.settings["product"], "VoidsElements") and self.settings["product"].VoidsElements:
|
||||
relating_object = self.settings["product"].VoidsElements[0].RelatingBuildingElement
|
||||
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif hasattr(self.settings["product"], "FillsVoids") and self.settings["product"].FillsVoids:
|
||||
relating_object = self.settings["product"].FillsVoids[0].RelatingOpeningElement
|
||||
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
elif hasattr(self.settings["product"], "ProjectsElements") and self.settings["product"].ProjectsElements:
|
||||
relating_object = self.settings["product"].ProjectsElements[0].RelatingElement
|
||||
placement_rel_to = relating_object.ObjectPlacement if hasattr(relating_object, "ObjectPlacement") else None
|
||||
|
||||
placement = self.file.createIfcLocalPlacement(placement_rel_to, self.get_relative_placement(placement_rel_to))
|
||||
if self.settings["product"].ObjectPlacement:
|
||||
|
||||
@@ -14,12 +14,12 @@ class Usecase:
|
||||
return self.remove_entire_representation_tree()
|
||||
|
||||
def remove_mapped_representation_portion_only(self):
|
||||
dummy_context = self.file.create_entity("IfcRepresentationContext")
|
||||
dummy_representation_map = self.file.createIfcRepresentationMap()
|
||||
self.settings["representation"].ContextOfItems = dummy_context
|
||||
for item in self.settings["representation"].Items:
|
||||
item.MappingSource = dummy_representation_map
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["representation"])
|
||||
if len(self.file.get_inverse(item.MappingTarget)) == 1:
|
||||
ifcopenshell.util.element.remove_deep(self.file, item.MappingTarget)
|
||||
self.file.remove(item.MappingTarget)
|
||||
self.file.remove(item)
|
||||
self.file.remove(self.settings["representation"])
|
||||
|
||||
def remove_entire_representation_tree(self):
|
||||
dummy_context = self.file.create_entity("IfcRepresentationContext")
|
||||
|
||||
@@ -53,4 +53,4 @@ class Usecase:
|
||||
for item in mapped_representations:
|
||||
self.unassign_product_representation(item["product"], item["representation"])
|
||||
for representation in just_representations:
|
||||
ifcopenshell.api.run("owner.remove_representation", self.file, **{"representation": representation})
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
|
||||
|
||||
@@ -8,22 +8,24 @@ class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"AxisCurve": None, # A Blender object
|
||||
"axis_curve": None, # A Blender object
|
||||
"grid_axis": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["grid_axis"].AxisCurve:
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["grid_axis"].AxisCurve)
|
||||
existing_curve = self.settings["grid_axis"].AxisCurve
|
||||
if existing_curve and len(self.file.get_inverse(existing_curve)) == 1:
|
||||
ifcopenshell.util.element.remove_deep(self.file, existing_curve)
|
||||
self.file.remove(existing_curve)
|
||||
|
||||
self.settings["unit_scale"] = ifcopenshell.util.unit.calculate_unit_scale(self.file)
|
||||
grid = [i for i in self.file.get_inverse(self.settings["grid_axis"]) if i.is_a("IfcGrid")][0]
|
||||
points = [
|
||||
Matrix(ifcopenshell.util.placement.get_local_placement(grid.ObjectPlacement)).inverted()
|
||||
@ (self.settings["AxisCurve"].matrix_world @ v.co)
|
||||
for v in self.settings["AxisCurve"].data.vertices[0:2]
|
||||
@ (self.settings["axis_curve"].matrix_world @ v.co)
|
||||
for v in self.settings["axis_curve"].data.vertices[0:2]
|
||||
]
|
||||
self.settings["grid_axis"].AxisCurve = self.file.createIfcPolyline(
|
||||
[
|
||||
|
||||
@@ -2,20 +2,20 @@ class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"AxisTag": "A",
|
||||
"SameSense": True,
|
||||
"UVWAxes": "UAxes", # Choose which axes
|
||||
"Grid": None,
|
||||
"axis_tag": "A",
|
||||
"same_sense": True,
|
||||
"uvw_axes": "UAxes", # Choose which axes
|
||||
"grid": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
element = self.file.create_entity("IfcGridAxis", **{
|
||||
"AxisTag": self.settings["AxisTag"],
|
||||
"SameSense": self.settings["SameSense"]
|
||||
"axis_tag": self.settings["axis_tag"],
|
||||
"SameSense": self.settings["same_sense"]
|
||||
})
|
||||
axes = list(getattr(self.settings["Grid"], self.settings["UVWAxes"]) or [])
|
||||
axes = list(getattr(self.settings["grid"], self.settings["uvw_axes"]) or [])
|
||||
axes.append(element)
|
||||
setattr(self.settings["Grid"], self.settings["UVWAxes"], axes)
|
||||
setattr(self.settings["grid"], self.settings["uvw_axes"], axes)
|
||||
return element
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"axis": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["axis"].AxisCurve)) == 1:
|
||||
ifcopenshell.util.element.remove_deep(self.file, self.settings["axis"].AxisCurve)
|
||||
self.file.remove(self.settings["axis"].AxisCurve)
|
||||
self.file.remove(self.settings["axis"])
|
||||
@@ -4,9 +4,9 @@ import ifcopenshell
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"Name": "Unnamed"}
|
||||
self.settings = {"name": "Unnamed"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity("IfcMaterial", **{"Name": self.settings["Name"] or "Unnamed"})
|
||||
return self.file.create_entity("IfcMaterial", **{"Name": self.settings["name"] or "Unnamed"})
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -9,6 +12,9 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
material = ifcopenshell.util.element.get_material(self.settings["product"])
|
||||
if material:
|
||||
ifcopenshell.api.run("material.unassign_material", self.file, product=self.settings["product"])
|
||||
if self.settings["type"] == "IfcMaterial":
|
||||
self.assign_ifc_material()
|
||||
elif self.settings["type"] == "IfcMaterialConstituentSet":
|
||||
@@ -18,14 +24,32 @@ class Usecase:
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialLayerSetUsage":
|
||||
material_set = self.file.create_entity("IfcMaterialLayerSet")
|
||||
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
|
||||
if element_type:
|
||||
element_type_material = ifcopenshell.util.element.get_material(element_type)
|
||||
if element_type_material and element_type_material.is_a("IfcMaterialLayerSet"):
|
||||
material_set = element_type_material
|
||||
else:
|
||||
material_set = self.file.create_entity("IfcMaterialLayerSet")
|
||||
else:
|
||||
material_set = self.file.create_entity("IfcMaterialLayerSet")
|
||||
material_set_usage = self.create_layer_set_usage(material_set)
|
||||
self.create_material_association(material_set_usage)
|
||||
elif self.settings["type"] == "IfcMaterialProfileSet":
|
||||
material_set = self.file.create_entity(self.settings["type"])
|
||||
self.create_material_association(material_set)
|
||||
elif self.settings["type"] == "IfcMaterialProfileSetUsage":
|
||||
material_set = self.file.create_entity("IfcMaterialProfileSet")
|
||||
element_type = ifcopenshell.util.element.get_type(self.settings["product"])
|
||||
if element_type:
|
||||
element_type_material = ifcopenshell.util.element.get_material(element_type)
|
||||
if element_type_material and element_type_material.is_a("IfcMaterialProfileSet"):
|
||||
material_set = element_type_material
|
||||
else:
|
||||
material_set = self.file.create_entity("IfcMaterialProfileSet")
|
||||
else:
|
||||
material_set = self.file.create_entity("IfcMaterialProfileSet")
|
||||
|
||||
self.update_representation_profile(material_set)
|
||||
material_set_usage = self.create_profile_set_usage(material_set)
|
||||
self.create_material_association(material_set_usage)
|
||||
elif self.settings["type"] == "IfcMaterialList":
|
||||
@@ -33,6 +57,21 @@ class Usecase:
|
||||
material_set.Materials = [self.settings["material"]]
|
||||
self.create_material_association(material_set)
|
||||
|
||||
def update_representation_profile(self, material_set):
|
||||
profile = material_set.CompositeProfile
|
||||
if not profile and material_set.MaterialProfiles:
|
||||
profile = material_set.MaterialProfiles[0].Profile
|
||||
if not profile:
|
||||
return
|
||||
representation = ifcopenshell.util.representation.get_representation(
|
||||
self.settings["product"], "Model", "Body", "MODEL_VIEW"
|
||||
)
|
||||
if not representation:
|
||||
return
|
||||
for subelement in self.file.traverse(representation):
|
||||
if subelement.is_a("IfcSweptAreaSolid"):
|
||||
subelement.SweptArea = profile
|
||||
|
||||
def create_layer_set_usage(self, material_set):
|
||||
return self.file.create_entity(
|
||||
"IfcMaterialLayerSetUsage",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.util.representation
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -6,9 +9,31 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if (
|
||||
self.settings["material_profile"].Profile
|
||||
and len(self.file.get_inverse(self.settings["material_profile"].Profile)) == 1
|
||||
):
|
||||
self.file.remove(self.settings["material_profile"].Profile)
|
||||
# TODO: handle composite profiles
|
||||
old_profile = self.settings["material_profile"].Profile
|
||||
self.settings["material_profile"].Profile = self.settings["profile"]
|
||||
for profile_set in self.settings["material_profile"].ToMaterialProfileSet:
|
||||
for inverse in self.file.get_inverse(profile_set):
|
||||
if not inverse.is_a("IfcMaterialProfileSetUsage"):
|
||||
continue
|
||||
if self.file.schema == "IFC2X3":
|
||||
for rel in self.file.get_inverse(inverse):
|
||||
if not rel.is_a("IfcRelAssociatesMaterial"):
|
||||
continue
|
||||
for element in rel.RelatedObjects:
|
||||
self.change_profile(element)
|
||||
else:
|
||||
for rel in inverse.AssociatedTo:
|
||||
for element in rel.RelatedObjects:
|
||||
self.change_profile(element)
|
||||
|
||||
if old_profile and len(self.file.get_inverse(old_profile)) == 0:
|
||||
self.file.remove(old_profile)
|
||||
|
||||
def change_profile(self, element):
|
||||
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
|
||||
if not representation:
|
||||
return
|
||||
for subelement in self.file.traverse(representation):
|
||||
if subelement.is_a("IfcSweptAreaSolid"):
|
||||
subelement.SweptArea = self.settings["profile"]
|
||||
|
||||
@@ -9,6 +9,15 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for association in self.settings["product"].HasAssociations:
|
||||
if association.is_a("IfcRelAssociatesMaterial"):
|
||||
self.file.remove(association)
|
||||
for rel in self.settings["product"].HasAssociations:
|
||||
if rel.is_a("IfcRelAssociatesMaterial"):
|
||||
if rel.RelatingMaterial.is_a("IfcMaterialLayerSetUsage") or rel.RelatingMaterial.is_a(
|
||||
"IfcMaterialProfileSetUsage"
|
||||
):
|
||||
self.file.remove(rel.RelatingMaterial)
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
self.file.remove(rel)
|
||||
continue
|
||||
related_objects = set(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["product"])
|
||||
rel.RelatedObjects = list(related_objects)
|
||||
|
||||
@@ -2,14 +2,16 @@ class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"Identification": "APTR",
|
||||
"Name": "Aperture Science",
|
||||
"identification": "APTR",
|
||||
"name": "Aperture Science",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.settings["Id"] = self.settings["Identification"]
|
||||
del self.settings["Identification"]
|
||||
return self.file.create_entity("IfcOrganization", **self.settings)
|
||||
data = {"Name": self.settings["name"]}
|
||||
if self.file.schema == "IFC2X3":
|
||||
data["Id"] = self.settings["identification"]
|
||||
else:
|
||||
data["Identification"] = self.settings["identification"]
|
||||
return self.file.create_entity("IfcOrganization", **data)
|
||||
|
||||
@@ -2,15 +2,17 @@ class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"Identification": "HSeldon",
|
||||
"FamilyName": "Seldon",
|
||||
"GivenName": "Hari",
|
||||
"identification": "HSeldon",
|
||||
"family_name": "Seldon",
|
||||
"given_name": "Hari",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.file.schema == "IFC2X3":
|
||||
self.settings["Id"] = self.settings["Identification"]
|
||||
del self.settings["Identification"]
|
||||
return self.file.create_entity("IfcPerson", **self.settings)
|
||||
data = {"FamilyName": self.settings["family_name"], "GivenName": self.settings["given_name"]}
|
||||
if self.file.schema == "IFC2X3":
|
||||
data["Id"] = self.settings["identification"]
|
||||
else:
|
||||
data["Identification"] = self.settings["identification"]
|
||||
return self.file.create_entity("IfcPerson", **data)
|
||||
|
||||
@@ -11,6 +11,8 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not hasattr(self.settings["element"], "OwnerHistory"):
|
||||
return
|
||||
self.settings["person"] = ifcopenshell.api.owner.settings.get_person(self.file)
|
||||
self.settings["organisation"] = ifcopenshell.api.owner.settings.get_organisation(self.file)
|
||||
if not self.settings["element"].OwnerHistory:
|
||||
|
||||
@@ -5,12 +5,22 @@ import ifcopenshell.api
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"element": None}
|
||||
self.settings = {"library": None, "element": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
element = self.file.add(self.settings["element"])
|
||||
self.added_elements = set()
|
||||
if self.settings["element"].is_a("IfcTypeProduct"):
|
||||
return self.append_type_product()
|
||||
|
||||
def append_type_product(self):
|
||||
self.whitelisted_inverse_attributes = {
|
||||
"IfcObjectDefinition": ["HasAssociations"],
|
||||
"IfcMaterialDefinition": ["HasExternalReferences", "HasProperties"],
|
||||
"IfcRepresentationItem": ["StyledByItem"],
|
||||
}
|
||||
element = self.add_element(self.settings["element"])
|
||||
self.existing_contexts = self.file.by_type("IfcGeometricRepresentationContext")
|
||||
added_contexts = [e for e in self.file.traverse(element) if e.is_a("IfcGeometricRepresentationContext")]
|
||||
for added_context in added_contexts:
|
||||
@@ -24,6 +34,24 @@ class Usecase:
|
||||
ifcopenshell.util.element.remove_deep(self.file, added_context)
|
||||
return element
|
||||
|
||||
def add_element(self, element):
|
||||
if element.id() == 0 or element.id() in self.added_elements:
|
||||
return
|
||||
new = self.file.add(element)
|
||||
self.added_elements.add(element.id())
|
||||
self.add_inverse(element)
|
||||
for subelement in self.settings["library"].traverse(element):
|
||||
self.add_inverse(subelement)
|
||||
return new
|
||||
|
||||
def add_inverse(self, element):
|
||||
inverse_attributes = []
|
||||
[inverse_attributes.extend(v) for k, v in self.whitelisted_inverse_attributes.items() if element.is_a(k)]
|
||||
inverse_attributes = set(inverse_attributes)
|
||||
for attribute in inverse_attributes:
|
||||
for inverse in getattr(element, attribute, []):
|
||||
self.add_element(inverse)
|
||||
|
||||
def get_equivalent_existing_context(self, added_context):
|
||||
for context in self.existing_contexts:
|
||||
if context.is_a() != added_context.is_a():
|
||||
|
||||
@@ -22,4 +22,5 @@ class Usecase:
|
||||
self.file.wrapped_data.header.file_name.preprocessor_version = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.originating_system = "IfcOpenShell {}".format(ifcopenshell.version)
|
||||
self.file.wrapped_data.header.file_name.authorization = "Nobody"
|
||||
self.file.wrapped_data.header.file_description.description = ('ViewDefinition[DesignTransferView]',)
|
||||
return self.file
|
||||
|
||||
@@ -4,36 +4,53 @@ import ifcopenshell
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "Name": None}
|
||||
self.settings = {"product": None, "name": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcObject"):
|
||||
for rel in self.settings["product"].IsDefinedBy or []:
|
||||
if (
|
||||
rel.is_a("IfcRelDefinesByProperties")
|
||||
and rel.RelatingPropertyDefinition.Name == self.settings["name"]
|
||||
):
|
||||
return rel.RelatingPropertyDefinition
|
||||
|
||||
pset = self.file.create_entity(
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcRelDefinesByProperties",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
# TODO: owner history
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatingPropertyDefinition": pset,
|
||||
}
|
||||
)
|
||||
return pset
|
||||
elif self.settings["product"].is_a("IfcTypeObject"):
|
||||
for definition in self.settings["product"].HasPropertySets or []:
|
||||
if definition.Name == self.settings["name"]:
|
||||
return definition
|
||||
|
||||
pset = self.file.create_entity(
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
"IfcPropertySet", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
|
||||
)
|
||||
has_property_sets = list(self.settings["product"].HasPropertySets or [])
|
||||
has_property_sets.append(pset)
|
||||
self.settings["product"].HasPropertySets = has_property_sets
|
||||
return pset
|
||||
elif self.settings["product"].is_a("IfcMaterialDefinition"):
|
||||
pset = self.file.create_entity(
|
||||
for definition in self.settings["product"].HasProperties or []:
|
||||
if definition.Name == self.settings["name"]:
|
||||
return definition
|
||||
|
||||
return self.file.create_entity(
|
||||
"IfcMaterialProperties",
|
||||
**{
|
||||
"Name": self.settings["Name"],
|
||||
"Name": self.settings["name"],
|
||||
"Material": self.settings["product"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"product": None, "Name": None}
|
||||
self.settings = {"product": None, "name": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["product"].is_a("IfcObject"):
|
||||
for rel in self.settings["product"].IsDefinedBy or []:
|
||||
if (
|
||||
rel.is_a("IfcRelDefinesByProperties")
|
||||
and rel.RelatingPropertyDefinition.Name == self.settings["name"]
|
||||
):
|
||||
return rel.RelatingPropertyDefinition
|
||||
|
||||
qto = self.file.create_entity(
|
||||
"IfcElementQuantity", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["Name"]}
|
||||
"IfcElementQuantity", **{"GlobalId": ifcopenshell.guid.new(), "Name": self.settings["name"]}
|
||||
)
|
||||
self.file.create_entity(
|
||||
"IfcRelDefinesByProperties",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
# TODO: owner history
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["product"]],
|
||||
"RelatingPropertyDefinition": qto,
|
||||
}
|
||||
)
|
||||
return qto
|
||||
|
||||
@@ -7,18 +7,17 @@ class Data:
|
||||
products = {}
|
||||
psets = {}
|
||||
qtos = {}
|
||||
properties = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
cls.products = {}
|
||||
cls.psets = {}
|
||||
cls.qtos = {}
|
||||
cls.properties = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id):
|
||||
cls._file = file
|
||||
cls._psetqto = ifcopenshell.util.pset.get_template("IFC4")
|
||||
cls._schema = ifcopenshell.ifcopenshell_wrapper.schema_by_name(file.schema)
|
||||
if not file:
|
||||
return
|
||||
product = file.by_id(product_id)
|
||||
@@ -59,133 +58,48 @@ class Data:
|
||||
|
||||
@classmethod
|
||||
def add_pset(cls, pset, product_id):
|
||||
new_pset = {
|
||||
"Name": pset.Name,
|
||||
"is_expanded": True,
|
||||
"Properties": cls.get_properties_from_template(pset.Name) or [],
|
||||
}
|
||||
cls.products[product_id]["psets"].add(int(pset.id()))
|
||||
cls.psets[int(pset.id())] = new_pset
|
||||
try:
|
||||
if hasattr(pset, "HasProperties"):
|
||||
props = pset.HasProperties
|
||||
elif hasattr(pset, "Properties"): # For IfcMaterialProperties
|
||||
props = pset.Properties
|
||||
except:
|
||||
return # I've seen ArchiCAD produce invalid IFCs with empty data
|
||||
# Invalid IFC, but some vendors like Solidworks do this so we accomodate it
|
||||
if not props:
|
||||
return
|
||||
data = pset.get_info()
|
||||
if not pset.is_a("IfcMaterialProperties"):
|
||||
del data["OwnerHistory"]
|
||||
del data["HasProperties"]
|
||||
if hasattr(pset, "HasProperties"):
|
||||
props = pset.HasProperties or []
|
||||
elif hasattr(pset, "Properties"):
|
||||
props = pset.Properties or []
|
||||
# TODO: support more than single values
|
||||
data["Properties"] = [p.id() for p in props if p.is_a("IfcPropertySingleValue")]
|
||||
cls.psets[pset.id()] = data
|
||||
cls.products[product_id]["psets"].add(pset.id())
|
||||
for prop in props:
|
||||
if prop.is_a("IfcPropertySingleValue") and prop.NominalValue:
|
||||
has_existing_prop = False
|
||||
for existing_prop in new_pset["Properties"]:
|
||||
if existing_prop["Name"] == prop.Name:
|
||||
if prop.NominalValue is None:
|
||||
existing_prop["value"] = None
|
||||
elif existing_prop["type"] == "string":
|
||||
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "float":
|
||||
existing_prop["value"] = float(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "integer":
|
||||
existing_prop["value"] = int(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "boolean":
|
||||
existing_prop["value"] = bool(prop.NominalValue.wrappedValue)
|
||||
elif existing_prop["type"] == "enum":
|
||||
existing_prop["value"] = str(prop.NominalValue.wrappedValue)
|
||||
existing_prop["is_null"] = prop.NominalValue.wrappedValue is None
|
||||
has_existing_prop = True
|
||||
break
|
||||
if not has_existing_prop:
|
||||
value = prop.NominalValue.wrappedValue
|
||||
if isinstance(value, str):
|
||||
data_type = "string"
|
||||
elif isinstance(value, float):
|
||||
data_type = "float"
|
||||
elif isinstance(value, bool):
|
||||
data_type = "boolean"
|
||||
elif isinstance(value, int):
|
||||
data_type = "integer"
|
||||
else:
|
||||
data_type = "string"
|
||||
value = str(value)
|
||||
new_pset["Properties"].append(
|
||||
{
|
||||
"Name": prop.Name,
|
||||
"value": value,
|
||||
"type": data_type,
|
||||
"enum_items": [],
|
||||
"is_null": prop.NominalValue.wrappedValue is None,
|
||||
}
|
||||
)
|
||||
# TODO: support more than single values
|
||||
if prop.is_a("IfcPropertySingleValue"):
|
||||
cls.load_prop(prop)
|
||||
|
||||
@classmethod
|
||||
def load_prop(cls, prop):
|
||||
data = prop.get_info()
|
||||
if prop.is_a("IfcProperty"):
|
||||
# TODO: support units
|
||||
del data["Unit"]
|
||||
if prop.NominalValue is not None:
|
||||
data["NominalValue"] = prop.NominalValue.wrappedValue
|
||||
elif prop.is_a("IfcPhysicalQuantity"):
|
||||
# TODO: support units
|
||||
del data["Unit"]
|
||||
# For convenience, which trumps correctness in this case
|
||||
data["NominalValue"] = prop[3]
|
||||
cls.properties[prop.id()] = data
|
||||
|
||||
@classmethod
|
||||
def add_qto(cls, qto, product_id):
|
||||
new_qto = {
|
||||
"Name": qto.Name,
|
||||
"is_expanded": True,
|
||||
"Properties": cls.get_properties_from_template(qto.Name) or [],
|
||||
}
|
||||
cls.products[product_id]["qtos"].add(int(qto.id()))
|
||||
cls.qtos[int(qto.id())] = new_qto
|
||||
for prop in qto.Quantities or []:
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
value = prop[3]
|
||||
has_existing_prop = False
|
||||
for existing_prop in new_qto["Properties"]:
|
||||
if existing_prop["Name"] == prop.Name:
|
||||
existing_prop["value"] = float(value)
|
||||
existing_prop["is_null"] = value is None
|
||||
has_existing_prop = True
|
||||
break
|
||||
if not has_existing_prop:
|
||||
new_qto["Properties"].append(
|
||||
{
|
||||
"Name": prop.Name,
|
||||
"value": float(value),
|
||||
"type": "float",
|
||||
"enum_items": [],
|
||||
"is_null": value is None,
|
||||
}
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_properties_from_template(cls, name):
|
||||
template = cls._psetqto.get_by_name(name)
|
||||
if not template:
|
||||
return
|
||||
properties = []
|
||||
for prop_template in template.HasPropertyTemplates:
|
||||
if not prop_template.is_a("IfcSimplePropertyTemplate"):
|
||||
continue # Other types not yet supported
|
||||
enum_items = []
|
||||
|
||||
if prop_template.TemplateType == "P_SINGLEVALUE":
|
||||
try:
|
||||
data_type = ifcopenshell.util.attribute.get_primitive_type(
|
||||
cls._schema.declaration_by_name(prop_template.PrimaryMeasureType or "IfcLabel")
|
||||
)
|
||||
except:
|
||||
# TODO: Occurs if the data type is something that exists in IFC4 and not in IFC2X3. To fully fix
|
||||
# this we need to generate the IFC2X3 pset template definitions.
|
||||
continue
|
||||
elif prop_template.TemplateType == "P_ENUMERATEDVALUE":
|
||||
data_type = "enum"
|
||||
enum_items = [v.wrappedValue for v in prop_template.Enumerators.EnumerationValues]
|
||||
elif prop_template.TemplateType in ["Q_LENGTH", "Q_AREA", "Q_VOLUME", "Q_WEIGHT", "Q_TIME"]:
|
||||
data_type = "float"
|
||||
elif prop_template.TemplateType == "Q_COUNT":
|
||||
data_type = "integer"
|
||||
else:
|
||||
continue # Other types not yet supported
|
||||
|
||||
properties.append(
|
||||
{
|
||||
"Name": prop_template.Name,
|
||||
"value": None,
|
||||
"type": data_type,
|
||||
"enum_items": enum_items,
|
||||
"is_null": True,
|
||||
}
|
||||
)
|
||||
return properties
|
||||
data = qto.get_info()
|
||||
del data["OwnerHistory"]
|
||||
del data["Quantities"]
|
||||
# TODO: support more than just simple quantities
|
||||
# We call it properties for convenience, not for correctness
|
||||
data["Properties"] = [q.id() for q in qto.Quantities or [] if q.is_a("IfcPhysicalSimpleQuantity")]
|
||||
cls.qtos[qto.id()] = data
|
||||
cls.products[product_id]["qtos"].add(qto.id())
|
||||
for quantity in qto.Quantities or []:
|
||||
if quantity.is_a("IfcPhysicalSimpleQuantity"):
|
||||
cls.load_prop(quantity)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.util.pset
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"pset": None, "Name": None, "Properties": {}}
|
||||
self.settings = {"pset": None, "name": None, "properties": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
@@ -16,8 +17,8 @@ class Usecase:
|
||||
self.extend_pset_with_new_properties(new_properties)
|
||||
|
||||
def update_pset_name(self):
|
||||
if self.settings["Name"]:
|
||||
self.settings["pset"].Name = self.settings["Name"]
|
||||
if self.settings["name"]:
|
||||
self.settings["pset"].Name = self.settings["name"]
|
||||
|
||||
def load_pset_template(self):
|
||||
# TODO: add IFC2X3 PsetQto template support
|
||||
@@ -29,19 +30,19 @@ class Usecase:
|
||||
self.update_existing_property(prop)
|
||||
|
||||
def update_existing_property(self, prop):
|
||||
if prop.Name not in self.settings["Properties"]:
|
||||
if prop.Name not in self.settings["properties"]:
|
||||
return
|
||||
value = self.settings["Properties"][prop.Name]
|
||||
value = self.settings["properties"][prop.Name]
|
||||
if value is None:
|
||||
prop.NominalValue = None
|
||||
else:
|
||||
primary_measure_type = self.get_primary_measure_type(prop.Name, previous_value=prop.NominalValue)
|
||||
prop.NominalValue = self.file.create_entity(primary_measure_type, value)
|
||||
del self.settings["Properties"][prop.Name]
|
||||
del self.settings["properties"][prop.Name]
|
||||
|
||||
def add_new_properties(self):
|
||||
properties = []
|
||||
for name, value in self.settings["Properties"].items():
|
||||
for name, value in self.settings["properties"].items():
|
||||
if value is None:
|
||||
continue
|
||||
primary_measure_type = self.get_primary_measure_type(name)
|
||||
|
||||
@@ -4,7 +4,7 @@ import ifcopenshell
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"qto": None, "Name": None, "Properties": {}}
|
||||
self.settings = {"qto": None, "name": None, "properties": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
@@ -16,8 +16,8 @@ class Usecase:
|
||||
self.extend_qto_with_new_properties(new_properties)
|
||||
|
||||
def update_qto_name(self):
|
||||
if self.settings["Name"]:
|
||||
self.settings["qto"].Name = self.settings["Name"]
|
||||
if self.settings["name"]:
|
||||
self.settings["qto"].Name = self.settings["name"]
|
||||
|
||||
def load_qto_template(self):
|
||||
# TODO: add IFC2X3 PsetQto template support
|
||||
@@ -29,16 +29,19 @@ class Usecase:
|
||||
self.update_existing_property(prop)
|
||||
|
||||
def update_existing_property(self, prop):
|
||||
if prop.Name not in self.settings["Properties"]:
|
||||
if prop.Name not in self.settings["properties"]:
|
||||
return
|
||||
value = self.settings["Properties"][prop.Name]
|
||||
if prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
prop[3] = float(value) if value else None
|
||||
del self.settings["Properties"][prop.Name]
|
||||
value = self.settings["properties"][prop.Name]
|
||||
name = prop.Name
|
||||
if value is None:
|
||||
self.file.remove(prop)
|
||||
elif prop.is_a("IfcPhysicalSimpleQuantity"):
|
||||
prop[3] = float(value)
|
||||
del self.settings["properties"][name]
|
||||
|
||||
def add_new_properties(self):
|
||||
properties = []
|
||||
for name, value in self.settings["Properties"].items():
|
||||
for name, value in self.settings["properties"].items():
|
||||
if value is None:
|
||||
continue
|
||||
property_type = self.get_canonical_property_type(name)
|
||||
|
||||
@@ -18,9 +18,20 @@ class Usecase:
|
||||
elif self.settings["product"].is_a("IfcTypeProduct"):
|
||||
representations = [rm.MappedRepresentation for rm in self.settings["product"].RepresentationMaps or []]
|
||||
for representation in representations:
|
||||
ifcopenshell.api.run("geometry.unassign_representation",
|
||||
self.file, **{"product": self.settings["product"], "representation": representation}
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation",
|
||||
self.file,
|
||||
**{"product": self.settings["product"], "representation": representation}
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
|
||||
for opening in getattr(self.settings["product"], "HasOpenings", []) or []:
|
||||
ifcopenshell.api.run("void.remove_opening", self.file, opening=opening.RelatedOpeningElement)
|
||||
|
||||
if self.settings["product"].is_a("IfcGrid"):
|
||||
for axis in (
|
||||
self.settings["product"].UAxes + self.settings["product"].VAxes + (self.settings["product"].WAxes or ())
|
||||
):
|
||||
ifcopenshell.api.run("grid.remove_grid_axis", self.file, axis=axis)
|
||||
|
||||
# TODO: remove object placement and other relationships
|
||||
self.file.remove(self.settings["product"])
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import ifcopenshell.util.date
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
|
||||
@@ -4,7 +4,7 @@ import ifcopenshell.api
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"name": "Unnamed", "predefined_type": "NOTDEFINED", "working_times": [], "exception_times": []}
|
||||
self.settings = {"name": "Unnamed", "predefined_type": "NOTDEFINED"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"rel_sequence": None, "lag_value": None, "duration_type": "NOTDEFINED"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
lag_value = self.file.createIfcDuration(
|
||||
ifcopenshell.util.date.datetime2ifc(self.settings["lag_value"], "IfcDuration")
|
||||
)
|
||||
lag_time = self.file.create_entity(
|
||||
"IfcLagTime",
|
||||
**{
|
||||
"DurationType": self.settings["duration_type"],
|
||||
"LagValue": lag_value,
|
||||
}
|
||||
)
|
||||
if self.settings["rel_sequence"].is_a("IfcRelSequence"):
|
||||
if (
|
||||
self.settings["rel_sequence"].TimeLag
|
||||
and len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1
|
||||
):
|
||||
self.file.remove(self.settings["rel_sequence"].TimeLag)
|
||||
self.settings["rel_sequence"].TimeLag = lag_time
|
||||
@@ -0,0 +1,43 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_process": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["related_object"].HasAssignments:
|
||||
for assignment in self.settings["related_object"].HasAssignments:
|
||||
if (
|
||||
assignment.is_a("IfclRelAssignsToProcess")
|
||||
and assignment.RelatingProcess == self.settings["relating_process"]
|
||||
):
|
||||
return
|
||||
|
||||
operates_on = None
|
||||
if self.settings["relating_process"].OperatesOn:
|
||||
operates_on = self.settings["relating_process"].OperatesOn[0]
|
||||
|
||||
if operates_on:
|
||||
related_objects = list(operates_on.RelatedObjects)
|
||||
related_objects.append(self.settings["related_object"])
|
||||
operates_on.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": operates_on})
|
||||
else:
|
||||
operates_on = self.file.create_entity(
|
||||
"IfcRelAssignsToProcess",
|
||||
**{
|
||||
"GlobalId": ifcopenshell.guid.new(),
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatedObjects": [self.settings["related_object"]],
|
||||
"RelatingProcess": self.settings["relating_process"],
|
||||
}
|
||||
)
|
||||
return operates_on
|
||||
@@ -19,3 +19,4 @@ class Usecase:
|
||||
if len(self.file.get_inverse(self.settings["parent"].Recurrence)) == 1:
|
||||
self.file.remove(self.settings["parent"].Recurrence)
|
||||
self.settings["parent"].Recurrence = recurrence
|
||||
return recurrence
|
||||
|
||||
@@ -23,5 +23,6 @@ class Usecase:
|
||||
"OwnerHistory": ifcopenshell.api.run("owner.create_owner_history", self.file),
|
||||
"RelatingProcess": self.settings["relating_process"],
|
||||
"RelatedProcess": self.settings["related_process"],
|
||||
"SequenceType": "FINISH_START",
|
||||
}
|
||||
)
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import datetime
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"task": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.calendar_cache = {}
|
||||
self.cascade_task(self.settings["task"])
|
||||
|
||||
def cascade_task(self, task):
|
||||
if not task.TaskTime:
|
||||
return
|
||||
|
||||
duration = (
|
||||
ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration)
|
||||
if task.TaskTime.ScheduleDuration
|
||||
else datetime.timedelta()
|
||||
)
|
||||
|
||||
finishes = []
|
||||
starts = []
|
||||
|
||||
for rel in task.IsSuccessorFrom:
|
||||
predecessor = rel.RelatingProcess
|
||||
if rel.SequenceType == "FINISH_START":
|
||||
finish = self.get_task_time_attribute(predecessor, "ScheduleFinish")
|
||||
if not finish:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
starts.append(self.offset_date(finish, days, duration_type, self.get_calendar(task)))
|
||||
starts.append(self.offset_date(finish, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
starts.append(finish)
|
||||
elif rel.SequenceType == "START_START":
|
||||
start = self.get_task_time_attribute(predecessor, "ScheduleStart")
|
||||
if not start:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
starts.append(self.offset_date(start, days, duration_type, self.get_calendar(task)))
|
||||
starts.append(self.offset_date(start, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
starts.append(start)
|
||||
elif rel.SequenceType == "FINISH_FINISH":
|
||||
finish = self.get_task_time_attribute(predecessor, "ScheduleFinish")
|
||||
if not finish:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
finishes.append(self.offset_date(finish, days, duration_type, self.get_calendar(task)))
|
||||
finishes.append(self.offset_date(finish, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
finishes.append(finish)
|
||||
elif rel.SequenceType == "START_FINISH":
|
||||
start = self.get_task_time_attribute(predecessor, "ScheduleStart")
|
||||
if not start:
|
||||
continue
|
||||
if rel.TimeLag:
|
||||
days = self.get_lag_time_days(rel.TimeLag)
|
||||
duration_type = rel.TimeLag.DurationType
|
||||
finishes.append(self.offset_date(start, days, duration_type, self.get_calendar(task)))
|
||||
finishes.append(self.offset_date(start, days, duration_type, self.get_calendar(predecessor)))
|
||||
else:
|
||||
finishes.append(start)
|
||||
|
||||
if starts and finishes:
|
||||
start = max(starts)
|
||||
finish = max(finishes)
|
||||
potential_finish = datetime.datetime.combine(
|
||||
ifcopenshell.util.sequence.get_finish_date(
|
||||
start,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
),
|
||||
datetime.datetime.min.time(),
|
||||
)
|
||||
if potential_finish > finish:
|
||||
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleStart == start_ifc:
|
||||
return
|
||||
task.TaskTime.ScheduleStart = start_ifc
|
||||
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(potential_finish, "IfcDateTime")
|
||||
else:
|
||||
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleFinish == finish_ifc:
|
||||
return
|
||||
task.TaskTime.ScheduleFinish = finish_ifc
|
||||
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_finish_date(
|
||||
finish,
|
||||
-duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
elif finishes:
|
||||
finish = max(finishes)
|
||||
finish_ifc = ifcopenshell.util.date.datetime2ifc(finish, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleFinish == finish_ifc:
|
||||
return
|
||||
task.TaskTime.ScheduleFinish = finish_ifc
|
||||
task.TaskTime.ScheduleStart = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_finish_date(
|
||||
finish,
|
||||
-duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
elif starts:
|
||||
start = max(starts)
|
||||
start_ifc = ifcopenshell.util.date.datetime2ifc(start, "IfcDateTime")
|
||||
if task.TaskTime.ScheduleStart == start_ifc:
|
||||
return
|
||||
task.TaskTime.ScheduleStart = start_ifc
|
||||
task.TaskTime.ScheduleFinish = ifcopenshell.util.date.datetime2ifc(
|
||||
ifcopenshell.util.sequence.get_finish_date(
|
||||
start,
|
||||
duration,
|
||||
task.TaskTime.DurationType,
|
||||
self.get_calendar(task),
|
||||
),
|
||||
"IfcDateTime",
|
||||
)
|
||||
|
||||
for rel in task.IsPredecessorTo:
|
||||
self.cascade_task(rel.RelatedProcess)
|
||||
|
||||
def get_lag_time_days(self, lag_time):
|
||||
return ifcopenshell.util.date.ifc2datetime(lag_time.LagValue.wrappedValue).days
|
||||
|
||||
def get_calendar(self, task):
|
||||
if task.id() not in self.calendar_cache:
|
||||
self.calendar_cache[task.id()] = ifcopenshell.util.sequence.derive_calendar(task)
|
||||
return self.calendar_cache[task.id()]
|
||||
|
||||
def offset_date(self, date, days, duration_type, calendar):
|
||||
return datetime.datetime.combine(
|
||||
ifcopenshell.util.sequence.get_finish_date(date, datetime.timedelta(days=days), duration_type, calendar),
|
||||
datetime.datetime.min.time(),
|
||||
)
|
||||
|
||||
def get_task_time_attribute(self, task, attribute):
|
||||
if task.TaskTime:
|
||||
value = getattr(task.TaskTime, attribute)
|
||||
if value:
|
||||
return ifcopenshell.util.date.ifc2datetime(value)
|
||||
@@ -11,6 +11,8 @@ class Data:
|
||||
time_periods = {}
|
||||
tasks = {}
|
||||
task_times = {}
|
||||
lag_times = {}
|
||||
sequences = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
@@ -23,6 +25,8 @@ class Data:
|
||||
cls.time_periods = {}
|
||||
cls.tasks = {}
|
||||
cls.task_times = {}
|
||||
cls.lag_times = {}
|
||||
cls.sequences = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file):
|
||||
@@ -37,6 +41,8 @@ class Data:
|
||||
cls.load_time_periods()
|
||||
cls.load_tasks()
|
||||
cls.load_task_times()
|
||||
cls.load_lag_times()
|
||||
cls.load_sequences()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -118,21 +124,30 @@ class Data:
|
||||
for task in cls._file.by_type("IfcTask"):
|
||||
data = task.get_info()
|
||||
del data["OwnerHistory"]
|
||||
data["HasAssignmentsWorkCalendar"] = []
|
||||
data["RelatedObjects"] = []
|
||||
data["RelatingProducts"] = []
|
||||
data["OperatesOn"] = []
|
||||
data["IsPredecessorTo"] = []
|
||||
data["IsSuccessorFrom"] = []
|
||||
if task.TaskTime:
|
||||
data["TaskTime"] = data["TaskTime"].id()
|
||||
for rel in task.IsNestedBy:
|
||||
[data["RelatedObjects"].append(o.id()) for o in rel.RelatedObjects if o.is_a("IfcTask")]
|
||||
data["Nests"] = [r.RelatingObject.id() for r in task.Nests or []]
|
||||
[
|
||||
data["RelatingProducts"].append(r.RelatingProduct.id())
|
||||
for r in task.HasAssignments
|
||||
if r.is_a("IfcRelAssignsToProduct")
|
||||
]
|
||||
[data["IsPredecessorTo"].append(rel.RelatedProcess.id()) for rel in task.IsPredecessorTo or []]
|
||||
[data["IsSuccessorFrom"].append(rel.RelatingProcess.id()) for rel in task.IsSuccessorFrom or []]
|
||||
[data["OperatesOn"].extend([o.id() for o in r.RelatedObjects]) for r in task.OperatesOn]
|
||||
[data["IsPredecessorTo"].append(rel.id()) for rel in task.IsPredecessorTo or []]
|
||||
[data["IsSuccessorFrom"].append(rel.id()) for rel in task.IsSuccessorFrom or []]
|
||||
[
|
||||
data["HasAssignmentsWorkCalendar"].append(rel.RelatingControl.id())
|
||||
for rel in task.HasAssignments or []
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
|
||||
]
|
||||
cls.tasks[task.id()] = data
|
||||
|
||||
@classmethod
|
||||
@@ -145,5 +160,28 @@ class Data:
|
||||
continue
|
||||
if "Start" in key or "Finish" in key or key == "StatusTime":
|
||||
data[key] = ifcopenshell.util.date.ifc2datetime(value)
|
||||
# TODO parse duration
|
||||
elif key == "ScheduleDuration":
|
||||
data[key] = ifcopenshell.util.date.ifc2datetime(value)
|
||||
cls.task_times[task_time.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_lag_times(cls):
|
||||
cls.lag_times = {}
|
||||
for lag_time in cls._file.by_type("IfcLagTime"):
|
||||
data = lag_time.get_info()
|
||||
if data["LagValue"]:
|
||||
if data["LagValue"].is_a("IfcDuration"):
|
||||
data["LagValue"] = ifcopenshell.util.date.ifc2datetime(data["LagValue"].wrappedValue)
|
||||
else:
|
||||
data["LagValue"] = float(data["LagValue"].wrappedValue)
|
||||
cls.lag_times[lag_time.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_sequences(cls):
|
||||
cls.sequences = {}
|
||||
for sequence in cls._file.by_type("IfcRelSequence"):
|
||||
data = sequence.get_info()
|
||||
data["RelatingProcess"] = sequence.RelatingProcess.id()
|
||||
data["RelatedProcess"] = sequence.RelatedProcess.id()
|
||||
data["TimeLag"] = sequence.TimeLag.id() if sequence.TimeLag else None
|
||||
cls.sequences[sequence.id()] = data
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"lag_time": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if name == "LagValue" and value is not None:
|
||||
if isinstance(value, float):
|
||||
value = self.file.createIfcRatioMeasure(value)
|
||||
else:
|
||||
value = self.file.createIfcDuration(ifcopenshell.util.date.datetime2ifc(value, "IfcDuration"))
|
||||
setattr(self.settings["lag_time"], name, value)
|
||||
for rel in [r for r in self.file.get_inverse(self.settings["lag_time"]) if r.is_a("IfcRelSequence")]:
|
||||
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=rel.RelatedProcess)
|
||||
@@ -1,6 +1,3 @@
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -10,17 +7,4 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if name == "TimePeriods" and value:
|
||||
periods = []
|
||||
for period in value:
|
||||
periods.append(
|
||||
self.file.create_entity(
|
||||
"IfcTimePeriod",
|
||||
**{
|
||||
"StartTime": ifcopenshell.util.date.datetime2ifc(period[0]),
|
||||
"EndTime": ifcopenshell.util.date.datetime2ifc(period[1]),
|
||||
},
|
||||
)
|
||||
)
|
||||
value = periods
|
||||
setattr(self.settings["recurrence_pattern"], name, value)
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"rel_sequence": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["rel_sequence"], name, value)
|
||||
if "SequenceType" in self.settings["attributes"].keys():
|
||||
ifcopenshell.api.run(
|
||||
"sequence.cascade_schedule", self.file, task=self.settings["rel_sequence"].RelatedProcess
|
||||
)
|
||||
@@ -1,4 +1,6 @@
|
||||
import datetime
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -9,8 +11,82 @@ class Usecase:
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.task = self.get_task()
|
||||
self.calendar = ifcopenshell.util.sequence.derive_calendar(self.task)
|
||||
|
||||
# If the user specifies both an end date and a duration, the duration takes priority
|
||||
if (
|
||||
"ScheduleDuration" in self.settings["attributes"].keys()
|
||||
and "ScheduleFinish" in self.settings["attributes"].keys()
|
||||
):
|
||||
del self.settings["attributes"]["ScheduleFinish"]
|
||||
if (
|
||||
"ActualDuration" in self.settings["attributes"].keys()
|
||||
and "ActualFinish" in self.settings["attributes"].keys()
|
||||
):
|
||||
del self.settings["attributes"]["ActualFinish"]
|
||||
|
||||
duration_type = self.settings["attributes"].get("DurationType", self.settings["task_time"].DurationType)
|
||||
if "ScheduleFinish" in self.settings["attributes"]:
|
||||
self.settings["attributes"]["ScheduleFinish"] = ifcopenshell.util.sequence.get_soonest_working_day(
|
||||
self.settings["attributes"]["ScheduleFinish"], duration_type, self.calendar
|
||||
)
|
||||
if "ScheduleStart" in self.settings["attributes"]:
|
||||
self.settings["attributes"]["ScheduleStart"] = ifcopenshell.util.sequence.get_soonest_working_day(
|
||||
self.settings["attributes"]["ScheduleStart"], duration_type, self.calendar
|
||||
)
|
||||
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if "Start" in name or "Finish" in name or name == "StatusTime":
|
||||
if value:
|
||||
if value:
|
||||
if "Start" in name or "Finish" in name or name == "StatusTime":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "ScheduleDuration" or name == "ActualDuration" or name == "RemainingTime":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(self.settings["task_time"], name, value)
|
||||
|
||||
if (
|
||||
"ScheduleDuration" in self.settings["attributes"].keys()
|
||||
and self.settings["task_time"].ScheduleDuration
|
||||
and self.settings["task_time"].ScheduleStart
|
||||
):
|
||||
self.calculate_finish()
|
||||
elif "ScheduleStart" in self.settings["attributes"].keys() and self.settings["task_time"].ScheduleDuration:
|
||||
self.calculate_finish()
|
||||
elif "ScheduleFinish" in self.settings["attributes"].keys() and self.settings["task_time"].ScheduleStart:
|
||||
self.calculate_duration()
|
||||
|
||||
if (
|
||||
self.settings["task_time"].ScheduleDuration
|
||||
and (
|
||||
"ScheduleStart" in self.settings["attributes"].keys()
|
||||
or "ScheduleFinish" in self.settings["attributes"].keys()
|
||||
or "ScheduleDuration" in self.settings["attributes"].keys()
|
||||
)
|
||||
):
|
||||
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=self.task)
|
||||
|
||||
def calculate_finish(self):
|
||||
finish_date = ifcopenshell.util.sequence.get_finish_date(
|
||||
ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleStart),
|
||||
ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleDuration),
|
||||
self.settings["task_time"].DurationType,
|
||||
self.calendar,
|
||||
)
|
||||
self.settings["task_time"].ScheduleFinish = ifcopenshell.util.date.datetime2ifc(finish_date, "IfcDateTime")
|
||||
|
||||
def calculate_duration(self):
|
||||
start = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleStart)
|
||||
finish = ifcopenshell.util.date.ifc2datetime(self.settings["task_time"].ScheduleFinish)
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
finish_date = datetime.date(finish.year, finish.month, finish.day)
|
||||
duration = datetime.timedelta()
|
||||
while current_date < finish_date:
|
||||
if self.settings["task_time"].DurationType == "ELAPSEDTIME" or not self.calendar:
|
||||
duration += datetime.timedelta(days=1)
|
||||
elif ifcopenshell.util.sequence.is_working_day(current_date, self.calendar):
|
||||
duration += datetime.timedelta(days=1)
|
||||
current_date += datetime.timedelta(days=1)
|
||||
self.settings["task_time"].ScheduleDuration = ifcopenshell.util.date.datetime2ifc(duration, "IfcDuration")
|
||||
|
||||
def get_task(self):
|
||||
return [e for e in self.file.get_inverse(self.settings["task_time"]) if e.is_a("IfcTask")][0]
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -7,4 +10,9 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if value:
|
||||
if "Date" in name or "Time" in name:
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "Duration" or name == "TotalFloat":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(self.settings["work_plan"], name, value)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.util.date
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -7,4 +10,9 @@ class Usecase:
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
if value:
|
||||
if "Date" in name or "Time" in name:
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDateTime")
|
||||
elif name == "Duration" or name == "TotalFloat":
|
||||
value = ifcopenshell.util.date.datetime2ifc(value, "IfcDuration")
|
||||
setattr(self.settings["work_schedule"], name, value)
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_product": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
products = set()
|
||||
related_object = None
|
||||
if self.settings["related_object"]:
|
||||
related_object = self.settings["related_object"]
|
||||
elif self.settings["relating_product"]:
|
||||
for reference in self.settings["relating_product"].ReferencedBy:
|
||||
if reference.is_a("IfcRelAssignsToProduct"):
|
||||
related_object = referenced_by.RelatedObjects[0]
|
||||
if related_object:
|
||||
assignments = self.settings["related_object"].HasAssignments
|
||||
for assignment in assignments:
|
||||
if assignment.is_a("IfcRelAssignsToProduct"):
|
||||
products.add(assignment.RelatingProduct.id())
|
||||
return products
|
||||
@@ -0,0 +1,320 @@
|
||||
import datetime
|
||||
import networkx as nx
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.date
|
||||
import ifcopenshell.util.sequence
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"work_schedule": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
# The method implemented is the same as shown here:
|
||||
# https://www.youtube.com/watch?v=qTErIV6OqLg
|
||||
self.start_dates = []
|
||||
self.build_network_graph()
|
||||
|
||||
self.pending_nodes = set(self.g.nodes)
|
||||
while self.pending_nodes:
|
||||
remaining_nodes = set()
|
||||
for pending_node in self.pending_nodes:
|
||||
if not self.forward_pass(pending_node):
|
||||
remaining_nodes.add(pending_node)
|
||||
self.pending_nodes = remaining_nodes
|
||||
|
||||
self.pending_nodes = set(self.g.nodes)
|
||||
while self.pending_nodes:
|
||||
remaining_nodes = set()
|
||||
for pending_node in self.pending_nodes:
|
||||
if not self.backward_pass(pending_node):
|
||||
remaining_nodes.add(pending_node)
|
||||
self.pending_nodes = remaining_nodes
|
||||
|
||||
self.update_task_times()
|
||||
|
||||
def build_network_graph(self):
|
||||
self.sequence_type_map = {
|
||||
None: "FS",
|
||||
"START_START": "SS",
|
||||
"START_FINISH": "SF",
|
||||
"FINISH_START": "FS",
|
||||
"FINISH_FINISH": "FF",
|
||||
"USERDEFINED": "FS",
|
||||
"NOTDEFINED": "FS",
|
||||
}
|
||||
self.g = nx.DiGraph()
|
||||
self.edges = []
|
||||
self.g.add_node("start", duration=0, duration_type="ELAPSEDTIME", calendar=None)
|
||||
self.g.add_node("finish", duration=0, duration_type="ELAPSEDTIME", calendar=None)
|
||||
for rel in self.settings["work_schedule"].Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if not related_object.is_a("IfcTask"):
|
||||
continue
|
||||
self.add_node(related_object)
|
||||
self.g.add_edges_from(self.edges)
|
||||
|
||||
def add_node(self, task):
|
||||
if task.IsNestedBy:
|
||||
for rel in task.IsNestedBy:
|
||||
[self.add_node(o) for o in rel.RelatedObjects]
|
||||
return
|
||||
|
||||
if task.TaskTime and task.TaskTime.ScheduleDuration:
|
||||
duration = ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleDuration).days
|
||||
duration_type = task.TaskTime.DurationType
|
||||
else:
|
||||
duration = 0
|
||||
duration_type = "ELAPSEDTIME"
|
||||
|
||||
self.g.add_node(
|
||||
task.id(),
|
||||
duration=duration,
|
||||
duration_type=duration_type,
|
||||
calendar=ifcopenshell.util.sequence.derive_calendar(task),
|
||||
)
|
||||
|
||||
self.edges.extend(
|
||||
[
|
||||
(
|
||||
rel.RelatingProcess.id(),
|
||||
rel.RelatedProcess.id(),
|
||||
{
|
||||
"lag_time": 0
|
||||
if not rel.TimeLag
|
||||
else ifcopenshell.util.date.ifc2datetime(rel.TimeLag.LagValue.wrappedValue).days,
|
||||
"type": self.sequence_type_map[rel.SequenceType],
|
||||
},
|
||||
)
|
||||
for rel in task.IsSuccessorFrom or []
|
||||
]
|
||||
)
|
||||
predecessor_types = [rel.SequenceType for rel in task.IsSuccessorFrom]
|
||||
successor_types = [rel.SequenceType for rel in task.IsPredecessorTo]
|
||||
|
||||
if not predecessor_types or (
|
||||
"FINISH_START" not in predecessor_types and "START_START" not in predecessor_types
|
||||
):
|
||||
self.edges.append(("start", task.id(), {"lag_time": 0, "type": "FS"}))
|
||||
if task.TaskTime and task.TaskTime.ScheduleStart:
|
||||
self.start_dates.append(ifcopenshell.util.date.ifc2datetime(task.TaskTime.ScheduleStart))
|
||||
if not successor_types or ("FINISH_START" not in successor_types and "FINISH_FINISH" not in successor_types):
|
||||
self.edges.append((task.id(), "finish", {"lag_time": 0, "type": "FS"}))
|
||||
|
||||
def update_task_times(self):
|
||||
for ifc_definition_id in self.g.nodes:
|
||||
data = self.g.nodes[ifc_definition_id]
|
||||
if not data["duration"]:
|
||||
continue
|
||||
ifcopenshell.api.run(
|
||||
"sequence.edit_task_time",
|
||||
self.file,
|
||||
task_time=self.file.by_id(ifc_definition_id).TaskTime,
|
||||
attributes={
|
||||
"FreeFloat": ifcopenshell.util.date.datetime2ifc(data["free_float"], "IfcDuration"),
|
||||
"TotalFloat": ifcopenshell.util.date.datetime2ifc(data["total_float"], "IfcDuration"),
|
||||
"IsCritical": data["total_float"].days == 0,
|
||||
"EarlyStart": ifcopenshell.util.date.datetime2ifc(data["early_start"], "IfcDateTime"),
|
||||
"EarlyFinish": ifcopenshell.util.date.datetime2ifc(data["early_finish"], "IfcDateTime"),
|
||||
"LateStart": ifcopenshell.util.date.datetime2ifc(data["late_start"], "IfcDateTime"),
|
||||
"LateFinish": ifcopenshell.util.date.datetime2ifc(data["late_finish"], "IfcDateTime"),
|
||||
},
|
||||
)
|
||||
|
||||
def offset_date(self, date, days, node):
|
||||
return ifcopenshell.util.sequence.get_finish_date(
|
||||
date, datetime.timedelta(days=days), node["duration_type"], node["calendar"]
|
||||
)
|
||||
|
||||
def forward_pass(self, node):
|
||||
successors = self.g.successors(node)
|
||||
predecessors = list(self.g.predecessors(node))
|
||||
data = self.g.nodes[node]
|
||||
|
||||
if node == "start":
|
||||
data["early_start"] = min(self.start_dates)
|
||||
else:
|
||||
finishes = []
|
||||
starts = []
|
||||
for predecessor in predecessors:
|
||||
predecessor_data = self.g.nodes[predecessor]
|
||||
edge = self.g[predecessor][node]
|
||||
if edge["type"] == "FS":
|
||||
finish = predecessor_data.get("early_finish")
|
||||
if finish is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
starts.append(finish)
|
||||
else:
|
||||
starts.append(self.offset_date(finish, edge["lag_time"], data))
|
||||
starts.append(self.offset_date(finish, edge["lag_time"], predecessor_data))
|
||||
elif edge["type"] == "SS":
|
||||
start = predecessor_data.get("early_start")
|
||||
if start is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
starts.append(start)
|
||||
else:
|
||||
starts.append(self.offset_date(start, edge["lag_time"], data))
|
||||
starts.append(self.offset_date(start, edge["lag_time"], predecessor_data))
|
||||
elif edge["type"] == "FF":
|
||||
finish = predecessor_data.get("early_finish")
|
||||
if finish is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
finishes.append(finish)
|
||||
else:
|
||||
finishes.append(self.offset_date(finish, edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(finish, edge["lag_time"], predecessor_data))
|
||||
elif edge["type"] == "SF":
|
||||
start = predecessor_data.get("early_start")
|
||||
if start is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
finishes.append(start)
|
||||
else:
|
||||
finishes.append(self.offset_date(start, edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(start, edge["lag_time"], predecessor_data))
|
||||
if starts and finishes:
|
||||
data["early_start"] = max(starts)
|
||||
data["early_finish"] = max(finishes)
|
||||
if self.offset_date(data["early_start"], data["duration"], data) > data["early_finish"]:
|
||||
data["early_finish"] = self.offset_date(data["early_start"], data["duration"], data)
|
||||
else:
|
||||
data["early_start"] = self.offset_date(data["early_finish"], -data["duration"], data)
|
||||
elif finishes:
|
||||
data["early_finish"] = max(finishes)
|
||||
elif starts:
|
||||
data["early_start"] = max(starts)
|
||||
else:
|
||||
print("How did this happen?")
|
||||
|
||||
if data.get("early_finish") is None:
|
||||
data["early_finish"] = self.offset_date(data["early_start"], data["duration"], data)
|
||||
elif data.get("early_start") is None:
|
||||
data["early_start"] = self.offset_date(data["early_finish"], -data["duration"], data)
|
||||
|
||||
return True
|
||||
|
||||
def backward_pass(self, node):
|
||||
successors = list(self.g.successors(node))
|
||||
predecessors = self.g.predecessors(node)
|
||||
data = self.g.nodes[node]
|
||||
free_floats = []
|
||||
|
||||
if node == "finish":
|
||||
data["late_finish"] = data["early_finish"]
|
||||
else:
|
||||
finishes = []
|
||||
starts = []
|
||||
for successor in successors:
|
||||
successor_data = self.g.nodes[successor]
|
||||
edge = self.g[node][successor]
|
||||
if edge["type"] == "FS":
|
||||
start = successor_data.get("late_start")
|
||||
if start is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
finishes.append(start)
|
||||
else:
|
||||
finishes.append(self.offset_date(start, -edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(start, -edge["lag_time"], successor_data))
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_finish"], successor_data["early_start"], edge["lag_time"], data, successor_data
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "SS":
|
||||
start = successor_data.get("late_start")
|
||||
if start is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
starts.append(start)
|
||||
else:
|
||||
starts.append(self.offset_date(start, -edge["lag_time"], data))
|
||||
starts.append(self.offset_date(start, -edge["lag_time"], successor_data))
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_start"], successor_data["early_start"], edge["lag_time"], data, successor_data
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "FF":
|
||||
finish = successor_data.get("late_finish")
|
||||
if finish is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
finishes.append(finish)
|
||||
else:
|
||||
finishes.append(self.offset_date(finish, -edge["lag_time"], data))
|
||||
finishes.append(self.offset_date(finish, -edge["lag_time"], successor_data))
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_finish"], successor_data["early_finish"], edge["lag_time"], data, successor_data
|
||||
)
|
||||
)
|
||||
elif edge["type"] == "SF":
|
||||
finish = successor_data.get("late_finish")
|
||||
if finish is None:
|
||||
return
|
||||
if not edge["lag_time"]:
|
||||
starts.append(finish)
|
||||
else:
|
||||
starts.append(self.offset_date(finish, -edge["lag_time"], data))
|
||||
starts.append(self.offset_date(finish, -edge["lag_time"], successor_data))
|
||||
free_floats.append(
|
||||
self.calculate_free_float(
|
||||
data["early_start"], successor_data["early_finish"], edge["lag_time"], data, successor_data
|
||||
)
|
||||
)
|
||||
if starts and finishes:
|
||||
data["late_start"] = min(starts)
|
||||
data["late_finish"] = min(finishes)
|
||||
if self.offset_date(data["late_start"], data["duration"], data) < data["late_finish"]:
|
||||
data["late_finish"] = self.offset_date(data["late_start"], data["duration"], data)
|
||||
else:
|
||||
data["late_start"] = self.offset_date(data["late_finish"], -data["duration"], data)
|
||||
elif finishes:
|
||||
data["late_finish"] = min(finishes)
|
||||
elif starts:
|
||||
data["late_start"] = min(starts)
|
||||
else:
|
||||
print("How did this happen?")
|
||||
|
||||
if data.get("late_finish") is None:
|
||||
data["late_finish"] = self.offset_date(data["late_start"], data["duration"], data)
|
||||
elif data.get("late_start") is None:
|
||||
data["late_start"] = self.offset_date(data["late_finish"], -data["duration"], data)
|
||||
|
||||
if data["duration_type"] == "WORKTIME":
|
||||
data["total_float"] = datetime.timedelta(
|
||||
days=ifcopenshell.util.sequence.count_working_days(
|
||||
data["early_finish"], data["late_finish"], data["calendar"]
|
||||
)
|
||||
)
|
||||
else:
|
||||
data["total_float"] = data["late_finish"] - data["early_finish"]
|
||||
|
||||
data["free_float"] = min(free_floats) if free_floats else None
|
||||
|
||||
return True
|
||||
|
||||
def calculate_free_float(self, predecessor_date, successor_date, lag_time, predecessor_data, successor_data):
|
||||
if not lag_time:
|
||||
min_successor_date = successor_date
|
||||
else:
|
||||
min_successor_date = min(
|
||||
(
|
||||
self.offset_date(successor_date, -lag_time, predecessor_data),
|
||||
self.offset_date(successor_date, -lag_time, successor_data),
|
||||
)
|
||||
)
|
||||
if predecessor_data["duration_type"] == "WORKTIME":
|
||||
return datetime.timedelta(
|
||||
days=ifcopenshell.util.sequence.count_working_days(
|
||||
predecessor_date, min_successor_date, predecessor_data["calendar"]
|
||||
)
|
||||
)
|
||||
return min_successor_date - predecessor_date
|
||||
@@ -16,4 +16,15 @@ class Usecase:
|
||||
definition=self.settings["task"],
|
||||
relating_context=self.file.by_type("IfcContext")[0],
|
||||
)
|
||||
for inverse in self.file.get_inverse(self.settings["task"]):
|
||||
if inverse.is_a("IfcRelSequence"):
|
||||
self.file.remove(inverse)
|
||||
elif inverse.is_a("IfcRelNests"):
|
||||
if inverse.RelatingObject == self.settings["task"]:
|
||||
for related_object in inverse.RelatedObjects:
|
||||
ifcopenshell.api.run("sequence.remove_task", self.file, task=related_object)
|
||||
elif inverse.RelatedObjects == tuple(self.settings["task"]):
|
||||
self.file.remove(inverse)
|
||||
elif inverse.is_a("IfcRelAssignsToControl"):
|
||||
self.file.remove(inverse)
|
||||
self.file.remove(self.settings["task"])
|
||||
|
||||
@@ -16,4 +16,12 @@ class Usecase:
|
||||
definition=self.settings["work_schedule"],
|
||||
relating_context=self.file.by_type("IfcContext")[0],
|
||||
)
|
||||
for rel in self.settings["work_schedule"].Controls:
|
||||
for related_object in rel.RelatedObjects:
|
||||
if related_object.is_a("IfcTask"):
|
||||
ifcopenshell.api.run(
|
||||
"sequence.remove_task",
|
||||
self.file,
|
||||
task=related_object
|
||||
)
|
||||
self.file.remove(self.settings["work_schedule"])
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"rel_sequence": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["rel_sequence"].TimeLag)) == 1:
|
||||
self.file.remove(self.settings["rel_sequence"].TimeLag)
|
||||
else:
|
||||
self.settings["rel_sequence"].TimeLag = None
|
||||
ifcopenshell.api.run(
|
||||
"sequence.cascade_schedule", self.file, task=self.settings["rel_sequence"].RelatedProcess
|
||||
)
|
||||
@@ -0,0 +1,25 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"relating_process": None,
|
||||
"related_object": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for rel in self.settings["related_object"].HasAssignments or []:
|
||||
if not rel.is_a("IfcRelAssignsToProcess") or rel.RelatingProcess != self.settings["relating_process"]:
|
||||
continue
|
||||
if len(rel.RelatedObjects) == 1:
|
||||
return self.file.remove(rel)
|
||||
related_objects = list(rel.RelatedObjects)
|
||||
related_objects.remove(self.settings["related_object"])
|
||||
rel.RelatedObjects = related_objects
|
||||
ifcopenshell.api.run("owner.update_owner_history", self.file, **{"element": rel})
|
||||
return rel
|
||||
@@ -16,3 +16,4 @@ class Usecase:
|
||||
for rel in self.settings["related_process"].IsSuccessorFrom or []:
|
||||
if rel.RelatingProcess == self.settings["relating_process"]:
|
||||
self.file.remove(rel)
|
||||
ifcopenshell.api.run("sequence.cascade_schedule", self.file, task=self.settings["related_process"])
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"ifc_class": "IfcStructuralPlanarAction",
|
||||
"predefined_type": "CONST",
|
||||
"global_or_local": "GLOBAL_COORDS",
|
||||
"applied_load": None,
|
||||
"structural_member": None,
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
activity = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
ifc_class=self.settings["ifc_class"],
|
||||
predefined_type=self.settings["predefined_type"],
|
||||
)
|
||||
activity.AppliedLoad = self.settings["applied_load"]
|
||||
activity.GlobalOrLocal = self.settings["global_or_local"]
|
||||
|
||||
rel = ifcopenshell.api.run("root.create_entity", self.file, ifc_class="IfcRelConnectsStructuralActivity")
|
||||
rel.RelatingElement = self.settings["structural_member"]
|
||||
rel.RelatedStructuralActivity = activity
|
||||
return activity
|
||||
+20
-13
@@ -1,21 +1,28 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"connection": None}
|
||||
self.settings = {"name": None, "connection": None, "ifc_class": "IfcBoundaryNodeCondition"}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["connection"].is_a("IfcRelConnectsStructuralMember"):
|
||||
related_connection = self.settings["connection"].RelatedStructuralConnection
|
||||
if self.settings["connection"]:
|
||||
# assign boundary condition to a connection
|
||||
if self.settings["connection"].is_a("IfcRelConnectsStructuralMember"):
|
||||
related_connection = self.settings["connection"].RelatedStructuralConnection
|
||||
else:
|
||||
related_connection = self.settings["connection"]
|
||||
|
||||
if related_connection.is_a("IfcStructuralPointConnection"):
|
||||
boundary_class = "IfcBoundaryNodeCondition"
|
||||
elif related_connection.is_a("IfcStructuralCurveConnection"):
|
||||
boundary_class = "IfcBoundaryEdgeCondition"
|
||||
elif related_connection.is_a("IfcStructuralSurfaceConnection"):
|
||||
boundary_class = "IfcBoundaryFaceCondition"
|
||||
|
||||
self.settings["connection"].AppliedCondition = self.file.create_entity(
|
||||
boundary_class, Name=self.settings["name"]
|
||||
)
|
||||
else:
|
||||
related_connection = self.settings["connection"]
|
||||
|
||||
if related_connection.is_a("IfcStructuralPointConnection"):
|
||||
boundary_class = "IfcBoundaryNodeCondition"
|
||||
elif related_connection.is_a("IfcStructuralCurveConnection"):
|
||||
boundary_class = "IfcBoundaryEdgeCondition"
|
||||
elif related_connection.is_a("IfcStructuralSurfaceConnection"):
|
||||
boundary_class = "IfcBoundaryFaceCondition"
|
||||
|
||||
self.settings["connection"].AppliedCondition = self.file.create_entity(boundary_class)
|
||||
# add an orphan boundary condition
|
||||
return self.file.create_entity(self.settings["ifc_class"], Name=self.settings["name"])
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"name": None,
|
||||
"ifc_class": "IfcStructuralLoadLinearForce",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
return self.file.create_entity(self.settings["ifc_class"], Name=self.settings["name"])
|
||||
@@ -0,0 +1,26 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"name": "Unnamed",
|
||||
"predefined_type": "LOAD_CASE",
|
||||
"action_type": "NOTDEFINED",
|
||||
"action_source": "NOTDEFINED",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
load_case = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
ifc_class="IfcStructuralLoadCase",
|
||||
predefined_type=self.settings["predefined_type"],
|
||||
name=self.settings["name"],
|
||||
)
|
||||
load_case.ActionType = self.settings["action_type"]
|
||||
load_case.ActionSource = self.settings["action_source"]
|
||||
return load_case
|
||||
@@ -0,0 +1,26 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"name": "Unnamed",
|
||||
"predefined_type": "LOAD_GROUP",
|
||||
"action_type": "NOTDEFINED",
|
||||
"action_source": "NOTDEFINED",
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
load_group = ifcopenshell.api.run(
|
||||
"root.create_entity",
|
||||
self.file,
|
||||
ifc_class="IfcStructuralLoadGroup",
|
||||
predefined_type=self.settings["predefined_type"],
|
||||
name=self.settings["name"],
|
||||
)
|
||||
load_group.ActionType = self.settings["action_type"]
|
||||
load_group.ActionSource = self.settings["action_source"]
|
||||
return load_group
|
||||
@@ -5,6 +5,14 @@ class Data:
|
||||
connections = {}
|
||||
boundary_conditions = {}
|
||||
connects_structural_members = {}
|
||||
members = {}
|
||||
structural_activities = {}
|
||||
structural_loads = {}
|
||||
connects_structural_activities = {}
|
||||
|
||||
load_cases = {}
|
||||
load_case_combinations = {}
|
||||
load_groups = {}
|
||||
|
||||
@classmethod
|
||||
def purge(cls):
|
||||
@@ -14,6 +22,14 @@ class Data:
|
||||
cls.connections = {}
|
||||
cls.boundary_conditions = {}
|
||||
cls.connects_structural_members = {}
|
||||
cls.members = {}
|
||||
cls.structural_activities = {}
|
||||
cls.structural_loads = {}
|
||||
cls.connects_structural_activities = {}
|
||||
|
||||
cls.load_cases = {}
|
||||
cls.load_case_combinations = {}
|
||||
cls.load_groups = {}
|
||||
|
||||
@classmethod
|
||||
def load(cls, file, product_id=None):
|
||||
@@ -21,8 +37,20 @@ class Data:
|
||||
if not cls._file:
|
||||
return
|
||||
if product_id:
|
||||
return cls.load_structural_connection(product_id)
|
||||
product = cls._file.by_id(product_id)
|
||||
if product.is_a("IfcStructuralConnection"):
|
||||
return cls.load_structural_connection(product_id)
|
||||
if product.is_a("IfcStructuralMember"):
|
||||
return cls.load_structural_member(product_id)
|
||||
# if product.is_a("IfcStructuralAction"):
|
||||
# return cls.load_structural_action(product_id)
|
||||
cls.load_structural_analysis_models()
|
||||
cls.load_structural_load_cases()
|
||||
cls.load_structural_load_case_combinations()
|
||||
cls.load_structural_load_groups()
|
||||
cls.load_structural_activities()
|
||||
cls.load_structural_loads()
|
||||
cls.load_boundary_conditions()
|
||||
cls.is_loaded = True
|
||||
|
||||
@classmethod
|
||||
@@ -37,7 +65,6 @@ class Data:
|
||||
cls.products.setdefault(product.id(), []).append(model.id())
|
||||
data = model.get_info()
|
||||
del data["OwnerHistory"]
|
||||
del data["OrientationOf2DPlane"]
|
||||
|
||||
loaded_by = []
|
||||
for load_group in model.LoadedBy or []:
|
||||
@@ -49,28 +76,98 @@ class Data:
|
||||
has_results.append(result_group.id())
|
||||
data["HasResults"] = has_results
|
||||
|
||||
data["OrientationOf2DPlane"] = model.OrientationOf2DPlane.id() if model.OrientationOf2DPlane else None
|
||||
data["SharedPlacement"] = model.SharedPlacement.id() if model.SharedPlacement else None
|
||||
|
||||
cls.structural_analysis_models[model.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_load_cases(cls):
|
||||
cls.load_cases = {}
|
||||
|
||||
for case in cls._file.by_type("IfcStructuralLoadCase"):
|
||||
data = case.get_info()
|
||||
del data["OwnerHistory"]
|
||||
is_grouped_by = []
|
||||
for rel in case.IsGroupedBy or []:
|
||||
is_grouped_by.extend([o.id() for o in rel.RelatedObjects])
|
||||
data["IsGroupedBy"] = is_grouped_by
|
||||
cls.load_cases[case.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_load_case_combinations(cls):
|
||||
cls.load_case_combinations = {}
|
||||
|
||||
for case in cls._file.by_type("IfcStructuralLoadGroup", include_subtypes=False):
|
||||
if case.PredefinedType != "LOAD_COMBINATION":
|
||||
continue
|
||||
data = case.get_info()
|
||||
del data["OwnerHistory"]
|
||||
|
||||
is_grouped_by = []
|
||||
for load_group in case.IsGroupedBy or []:
|
||||
is_grouped_by.append(load_group.id())
|
||||
data["IsGroupedBy"] = is_grouped_by
|
||||
|
||||
cls.load_case_combinations[case.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_load_groups(cls):
|
||||
cls.load_groups = {}
|
||||
|
||||
for case in cls._file.by_type("IfcStructuralLoadGroup", include_subtypes=False):
|
||||
if case.PredefinedType == "LOAD_COMBINATION":
|
||||
continue
|
||||
data = case.get_info()
|
||||
del data["OwnerHistory"]
|
||||
|
||||
is_grouped_by = []
|
||||
for rel in case.IsGroupedBy or []:
|
||||
is_grouped_by.extend([o.id() for o in rel.RelatedObjects])
|
||||
data["IsGroupedBy"] = is_grouped_by
|
||||
|
||||
cls.load_groups[case.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_activities(cls):
|
||||
cls.structural_activities = {}
|
||||
for activity in cls._file.by_type("IfcStructuralActivity"):
|
||||
data = activity.get_info()
|
||||
del data["OwnerHistory"]
|
||||
del data["ObjectPlacement"]
|
||||
del data["Representation"]
|
||||
data["AppliedLoad"] = data["AppliedLoad"].id() if data["AppliedLoad"] else None
|
||||
data["AssignedToStructuralItem"] = None
|
||||
if activity.AssignedToStructuralItem:
|
||||
data["AssignedToStructuralItem"] = activity.AssignedToStructuralItem[0].RelatingElement.id()
|
||||
cls.structural_activities[activity.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_structural_connection(cls, product_id):
|
||||
cls.connections = {}
|
||||
cls.boundary_conditions = {}
|
||||
cls.connects_structural_members = {}
|
||||
|
||||
connection = cls._file.by_id(product_id)
|
||||
connection_data = {"AppliedCondition": None, "ConnectsStructuralMembers": []}
|
||||
data = connection.get_info()
|
||||
del data["OwnerHistory"]
|
||||
|
||||
data["ObjectPlacement"] = data["ObjectPlacement"].id() if data["ObjectPlacement"] is not None else None
|
||||
data["Representation"] = data["Representation"].id() if data["Representation"] is not None else None
|
||||
if connection.is_a("IfcStructuralCurveConnection"):
|
||||
data["Axis"] = data["Axis"].id() if data["Axis"] is not None else None
|
||||
if connection.is_a("IfcStructuralPointConnection"):
|
||||
data["ConditionCoordinateSystem"] = data["ConditionCoordinateSystem"].id() if data["ConditionCoordinateSystem"] is not None else None
|
||||
|
||||
data["ConnectsStructuralMembers"] = []
|
||||
|
||||
if connection.AppliedCondition:
|
||||
cls.load_boundary_condition(connection.AppliedCondition)
|
||||
connection_data["AppliedCondition"] = connection.AppliedCondition.id()
|
||||
data["AppliedCondition"] = connection.AppliedCondition.id()
|
||||
|
||||
for rel in connection.ConnectsStructuralMembers or []:
|
||||
cls.load_connects_structural_member(rel)
|
||||
connection_data["ConnectsStructuralMembers"].append(rel.id())
|
||||
data["ConnectsStructuralMembers"].append(rel.id())
|
||||
|
||||
cls.connections[connection.id()] = connection_data
|
||||
cls.connections[connection.id()] = data
|
||||
|
||||
@classmethod
|
||||
def load_boundary_condition(cls, boundary_condition):
|
||||
@@ -90,10 +187,68 @@ class Data:
|
||||
del rel_data["ConditionCoordinateSystem"] # TODO: consider orientation
|
||||
|
||||
if rel.is_a("IfcRelConnectsWithEccentricity"):
|
||||
rel_data["ConnectionConstraint"] = rel.ConnectionConstraint # TODO
|
||||
rel_data["ConnectionConstraint"] = rel.ConnectionConstraint.id() # TODO
|
||||
|
||||
if rel.AppliedCondition:
|
||||
cls.load_boundary_condition(rel.AppliedCondition)
|
||||
rel_data["AppliedCondition"] = rel.AppliedCondition.id()
|
||||
|
||||
cls.connects_structural_members[rel.id()] = rel_data
|
||||
|
||||
@classmethod
|
||||
def load_structural_loads(cls):
|
||||
cls.structural_loads = {}
|
||||
for load in cls._file.by_type("IfcStructuralLoad"):
|
||||
cls.load_structural_load(load)
|
||||
|
||||
@classmethod
|
||||
def load_structural_load(cls, load):
|
||||
cls.structural_loads[load.id()] = load.get_info()
|
||||
|
||||
@classmethod
|
||||
def load_boundary_conditions(cls):
|
||||
cls.boundary_conditions = {}
|
||||
for bc in cls._file.by_type("IfcBoundaryCondition"):
|
||||
cls.load_boundary_condition(bc)
|
||||
|
||||
@classmethod
|
||||
def load_connects_structural_activity(cls, rel):
|
||||
rel_data = rel.get_info()
|
||||
del rel_data["OwnerHistory"]
|
||||
rel_data["RelatingElement"] = rel.RelatingElement.id()
|
||||
rel_data["RelatedStructuralActivity"] = rel.RelatedStructuralActivity.id()
|
||||
|
||||
if rel.RelatedStructuralActivity.AppliedLoad:
|
||||
cls.load_structural_load(rel.RelatedStructuralActivity.AppliedLoad)
|
||||
# rel_data["AppliedCondition"] = rel.RelatedStructuralActivity.AppliedLoad.id()
|
||||
|
||||
cls.connects_structural_activities[rel.id()] = rel_data
|
||||
|
||||
@classmethod
|
||||
def load_structural_member(cls, product_id):
|
||||
cls.connects_structural_activities = {}
|
||||
cls.connects_structural_members = {}
|
||||
|
||||
member = cls._file.by_id(product_id)
|
||||
data = member.get_info()
|
||||
|
||||
del data["OwnerHistory"]
|
||||
|
||||
data["ObjectPlacement"] = data["ObjectPlacement"].id() if data["ObjectPlacement"] is not None else None
|
||||
data["Representation"] = data["Representation"].id() if data["Representation"] is not None else None
|
||||
if member.is_a("IfcStructuralCurveMember"):
|
||||
data["Axis"] = data["Axis"].id() if data["Axis"] is not None else None
|
||||
|
||||
data["ConnectsStructuralActivities"] = []
|
||||
data["ConnectedBy"] = []
|
||||
|
||||
for activity in member.AssignedStructuralActivity or []:
|
||||
cls.load_connects_structural_activity(activity)
|
||||
data["ConnectsStructuralActivities"].append(activity.id())
|
||||
|
||||
for rel in member.ConnectedBy or []:
|
||||
cls.load_connects_structural_member(rel)
|
||||
data["ConnectedBy"].append(rel.id())
|
||||
|
||||
|
||||
cls.members[member.id()] = data
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"structural_item": None, "axis": [0.0, 0.0, 1.0], "ref_direction": [1.0, 0.0, 0.0]}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.settings["structural_item"].ConditionCoordinateSystem is None:
|
||||
point = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
|
||||
ccs = self.file.createIfcAxis2Placement3D(point, None, None)
|
||||
self.settings["structural_item"].ConditionCoordinateSystem = ccs
|
||||
print(ccs)
|
||||
|
||||
ccs = self.settings["structural_item"].ConditionCoordinateSystem
|
||||
print("use case")
|
||||
print(ccs)
|
||||
if ccs.Axis and len(self.file.get_inverse(ccs.Axis)) == 1:
|
||||
self.file.remove(ccs.Axis)
|
||||
ccs.Axis = self.file.createIfcDirection(self.settings["axis"])
|
||||
if ccs.RefDirection and len(self.file.get_inverse(ccs.RefDirection)) == 1:
|
||||
self.file.remove(ccs.RefDirection)
|
||||
ccs.RefDirection = self.file.createIfcDirection(self.settings["ref_direction"])
|
||||
@@ -0,0 +1,11 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"structural_item": None, "axis": [0.0, 0.0, 1.0]}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if len(self.file.get_inverse(self.settings["structural_item"].Axis)) == 1:
|
||||
self.file.remove(self.settings["structural_item"].Axis)
|
||||
self.settings["structural_item"].Axis = self.file.createIfcDirection(self.settings["axis"])
|
||||
@@ -0,0 +1,13 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"structural_load": None,
|
||||
"attributes": {}
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["structural_load"], name, value)
|
||||
@@ -0,0 +1,10 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"load_case": None, "attributes": {}}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
for name, value in self.settings["attributes"].items():
|
||||
setattr(self.settings["load_case"], name, value)
|
||||
@@ -1,11 +0,0 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"structural_member": None, "axis": [0.0, 0.0, 1.0]}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if self.file.get_inverse(self.settings["structural_member"].Axis) == 1:
|
||||
self.file.remove(self.settings["structural_member"].Axis)
|
||||
self.settings["structural_member"].Axis = self.file.createIfcDirection(self.settings["axis"])
|
||||
+13
-6
@@ -1,13 +1,20 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"connection": None}
|
||||
self.settings = {"connection": None, "boundary_condition": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
if not self.settings["connection"].AppliedCondition:
|
||||
return
|
||||
if len(self.file.get_inverse(self.settings["connection"].AppliedCondition)) == 1:
|
||||
self.file.remove(self.settings["connection"].AppliedCondition)
|
||||
self.settings["connection"].AppliedCondition = None
|
||||
if self.settings["connection"]:
|
||||
# remove boundary condition from a connection
|
||||
if not self.settings["connection"].AppliedCondition:
|
||||
return
|
||||
if len(self.file.get_inverse(self.settings["connection"].AppliedCondition)) == 1:
|
||||
self.file.remove(self.settings["connection"].AppliedCondition)
|
||||
self.settings["connection"].AppliedCondition = None
|
||||
else:
|
||||
# remove the boundary condition
|
||||
for conn in self.file.get_inverse(self.settings["boundary_condition"]):
|
||||
conn.AppliedCondition = None
|
||||
self.file.remove(self.settings["boundary_condition"])
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"structural_load": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
self.file.remove(self.settings["structural_load"])
|
||||
@@ -0,0 +1,15 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"load_case": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
for rel in self.settings["load_case"].IsGroupedBy or []:
|
||||
self.file.remove(rel)
|
||||
self.file.remove(self.settings["load_case"])
|
||||
@@ -0,0 +1,16 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {"load_group": None}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
|
||||
def execute(self):
|
||||
# TODO: do a deep purge
|
||||
for inverse in self.file.get_inverse(self.settings["load_group"]):
|
||||
if inverse.is_a("IfcRelAssignsToGroup") and len(inverse.RelatedObjects) == 1:
|
||||
self.file.remove(inverse)
|
||||
self.file.remove(self.settings["load_group"])
|
||||
@@ -2,15 +2,11 @@ class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"Name": "Name",
|
||||
"SurfaceColour": [], # RGB
|
||||
"DiffuseColour": [], # RGB
|
||||
"Transparency": 0,
|
||||
"external_definition": {
|
||||
"Location": None,
|
||||
"Identification": None,
|
||||
"Name": "Name"
|
||||
},
|
||||
"name": "Name",
|
||||
"surface_colour": [], # RGB
|
||||
"diffuse_colour": [], # RGB
|
||||
"transparency": 0,
|
||||
"external_definition": {"location": None, "identification": None, "name": "Name"},
|
||||
}
|
||||
for key, value in settings.items():
|
||||
self.settings[key] = value
|
||||
@@ -20,22 +16,26 @@ class Usecase:
|
||||
if self.settings["external_definition"]:
|
||||
styles.append(self.create_externally_defined_surface_style())
|
||||
# Name is filled out because Revit treats this incorrectly as the material name
|
||||
return self.file.createIfcSurfaceStyle(self.settings["Name"], "BOTH", styles)
|
||||
return self.file.createIfcSurfaceStyle(self.settings["name"], "BOTH", styles)
|
||||
|
||||
def create_surface_style_rendering(self):
|
||||
return self.file.create_entity("IfcSurfaceStyleRendering", **{
|
||||
"SurfaceColour": self.create_colour_rgb(self.settings["SurfaceColour"]),
|
||||
"Transparency": (self.settings["Transparency"] - 1) * -1,
|
||||
"ReflectanceMethod": "NOTDEFINED",
|
||||
"DiffuseColour": self.create_colour_rgb(self.settings["DiffuseColour"])
|
||||
})
|
||||
return self.file.create_entity(
|
||||
"IfcSurfaceStyleRendering",
|
||||
**{
|
||||
"SurfaceColour": self.create_colour_rgb(self.settings["surface_colour"]),
|
||||
"Transparency": self.settings["transparency"],
|
||||
"ReflectanceMethod": "NOTDEFINED",
|
||||
"DiffuseColour": self.create_colour_rgb(self.settings["diffuse_colour"]),
|
||||
}
|
||||
)
|
||||
|
||||
def create_externally_defined_surface_style(self):
|
||||
self.file.create_entity(
|
||||
"IfcExternallyDefinedSurfaceStyle", **{
|
||||
"Location": self.settings["Location"],
|
||||
"Identification": self.settings["Identification"],
|
||||
"Name": self.settings["Name"],
|
||||
"IfcExternallyDefinedSurfaceStyle",
|
||||
**{
|
||||
"Location": self.settings["location"],
|
||||
"Identification": self.settings["identification"],
|
||||
"Name": self.settings["name"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -3,13 +3,13 @@ class Usecase:
|
||||
self.file = file
|
||||
self.settings = {
|
||||
"style": None,
|
||||
"SurfaceColour": [], # RGB
|
||||
"DiffuseColour": [], # RGB
|
||||
"Transparency": 0,
|
||||
"surface_colour": [], # RGB
|
||||
"diffuse_colour": [], # RGB
|
||||
"transparency": 0,
|
||||
"external_definition": {
|
||||
"Location": None,
|
||||
"Identification": None,
|
||||
"Name": "Name"
|
||||
"location": None,
|
||||
"identification": None,
|
||||
"name": "Name"
|
||||
},
|
||||
}
|
||||
for key, value in settings.items():
|
||||
@@ -20,20 +20,20 @@ class Usecase:
|
||||
for element in self.file.traverse(self.settings["style"]):
|
||||
if element.is_a("IfcSurfaceStyleShading"):
|
||||
if element.SurfaceColour:
|
||||
self.update_colour_rgb(element.SurfaceColour, self.settings["SurfaceColour"])
|
||||
self.update_colour_rgb(element.SurfaceColour, self.settings["surface_colour"])
|
||||
else:
|
||||
element.SurfaceColour = self.create_colour_rgb(self.settings["SurfaceColour"])
|
||||
element.Transparency = (self.settings["Transparency"] - 1) * -1
|
||||
element.SurfaceColour = self.create_colour_rgb(self.settings["surface_colour"])
|
||||
element.Transparency = (self.settings["transparency"] - 1) * -1
|
||||
if element.is_a("IfcSurfaceStyleRendering"):
|
||||
if element.DiffuseColour:
|
||||
self.update_colour_rgb(element.DiffuseColour, self.settings["DiffuseColour"])
|
||||
self.update_colour_rgb(element.DiffuseColour, self.settings["diffuse_colour"])
|
||||
else:
|
||||
element.DiffuseColour = self.create_colour_rgb(self.settings["DiffuseColour"])
|
||||
element.DiffuseColour = self.create_colour_rgb(self.settings["diffuse_colour"])
|
||||
# TODO: Move to separate usecase
|
||||
#if element.is_a("IfcExternallyDefinedSurfaceStyle"):
|
||||
# element.Location = self.settings["Location"]
|
||||
# element.Identification = self.settings["Identification"]
|
||||
# element.Name = self.settings["Name"]
|
||||
# element.Location = self.settings["location"]
|
||||
# element.Identification = self.settings["identification"]
|
||||
# element.Name = self.settings["name"]
|
||||
# has_external_definition = True
|
||||
#if not has_external_definition:
|
||||
# styles = list(self.settings["style"].Styles)
|
||||
@@ -43,18 +43,18 @@ class Usecase:
|
||||
|
||||
def create_surface_style_rendering(self):
|
||||
return self.file.create_entity("IfcSurfaceStyleRendering", **{
|
||||
"SurfaceColour": self.create_colour_rgb(self.settings["SurfaceColour"]),
|
||||
"Transparency": (self.settings["Transparency"] - 1) * -1,
|
||||
"SurfaceColour": self.create_colour_rgb(self.settings["surface_colour"]),
|
||||
"Transparency": (self.settings["transparency"] - 1) * -1,
|
||||
"ReflectanceMethod": "NOTDEFINED",
|
||||
"DiffuseColour": self.create_colour_rgb(self.settings["DiffuseColour"])
|
||||
"DiffuseColour": self.create_colour_rgb(self.settings["diffuse_colour"])
|
||||
})
|
||||
|
||||
def create_externally_defined_surface_style(self):
|
||||
self.file.create_entity(
|
||||
"IfcExternallyDefinedSurfaceStyle", **{
|
||||
"Location": self.settings["Location"],
|
||||
"Identification": self.settings["Identification"],
|
||||
"Name": self.settings["Name"],
|
||||
"Location": self.settings["location"],
|
||||
"Identification": self.settings["identification"],
|
||||
"Name": self.settings["name"],
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import ifcopenshell
|
||||
import ifcopenshell.api
|
||||
import ifcopenshell.util.element
|
||||
|
||||
|
||||
class Usecase:
|
||||
@@ -52,3 +53,50 @@ class Usecase:
|
||||
"RelatingType": self.settings["relating_type"],
|
||||
}
|
||||
)
|
||||
|
||||
self.map_representations()
|
||||
self.map_material_usages()
|
||||
|
||||
def map_representations(self):
|
||||
if not self.settings["relating_type"].RepresentationMaps:
|
||||
return
|
||||
representations = []
|
||||
if self.settings["related_object"].Representation:
|
||||
representations = self.settings["related_object"].Representation.Representations
|
||||
for representation in representations:
|
||||
# TODO: check if this is right? Surely this can be a single usecase?
|
||||
ifcopenshell.api.run(
|
||||
"geometry.unassign_representation",
|
||||
self.file,
|
||||
**{"product": self.settings["related_object"], "representation": representation}
|
||||
)
|
||||
ifcopenshell.api.run("geometry.remove_representation", self.file, **{"representation": representation})
|
||||
for representation_map in self.settings["relating_type"].RepresentationMaps:
|
||||
representation = representation_map.MappedRepresentation
|
||||
mapped_representation = ifcopenshell.api.run(
|
||||
"geometry.map_representation", self.file, **{"representation": representation}
|
||||
)
|
||||
ifcopenshell.api.run(
|
||||
"geometry.assign_representation",
|
||||
self.file,
|
||||
**{"product": self.settings["related_object"], "representation": mapped_representation}
|
||||
)
|
||||
|
||||
def map_material_usages(self):
|
||||
type_material = ifcopenshell.util.element.get_material(self.settings["relating_type"])
|
||||
if not type_material:
|
||||
return
|
||||
if type_material.is_a("IfcMaterialLayerSet"):
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_material",
|
||||
self.file,
|
||||
product=self.settings["related_object"],
|
||||
type="IfcMaterialLayerSetUsage",
|
||||
)
|
||||
elif type_material.is_a("IfcMaterialProfileSet"):
|
||||
ifcopenshell.api.run(
|
||||
"material.assign_material",
|
||||
self.file,
|
||||
product=self.settings["related_object"],
|
||||
type="IfcMaterialProfileSetUsage",
|
||||
)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import ifcopenshell.api
|
||||
|
||||
|
||||
class Usecase:
|
||||
def __init__(self, file, **settings):
|
||||
self.file = file
|
||||
@@ -15,6 +18,6 @@ class Usecase:
|
||||
if rel.RelatingOpeningElement == self.settings["opening"]:
|
||||
to_remove.append(rel)
|
||||
break
|
||||
self.file.remove(self.settings["opening"])
|
||||
ifcopenshell.api.run("root.remove_product", self.file, product=self.settings["opening"])
|
||||
for element in to_remove:
|
||||
self.file.remove(element)
|
||||
|
||||
@@ -149,9 +149,14 @@ class tree(ifcopenshell_wrapper.tree):
|
||||
args = [self, unwrap(value)]
|
||||
if isinstance(value, entity_instance):
|
||||
args.append(kwargs.get("completely_within", False))
|
||||
if "extend" in kwargs:
|
||||
args.append(kwargs["extend"])
|
||||
elif has_occ:
|
||||
if isinstance(value, TopoDS.TopoDS_Shape):
|
||||
args[1] = utils.serialize_shape(value)
|
||||
args.append(kwargs.get("completely_within", False))
|
||||
if "extend" in kwargs:
|
||||
args.append(kwargs["extend"])
|
||||
return [entity_instance(e) for e in ifcopenshell_wrapper.tree.select(*args)]
|
||||
|
||||
def select_box(self, value, **kwargs):
|
||||
|
||||
@@ -94,7 +94,7 @@ class entity(facet):
|
||||
inst.is_a(self.name),
|
||||
self.message % {"name": inst.is_a()}
|
||||
)
|
||||
|
||||
|
||||
|
||||
class classification(facet):
|
||||
"""
|
||||
@@ -241,7 +241,7 @@ class restriction:
|
||||
self.restriction_on = node['@base'][3:]
|
||||
self.type = ""
|
||||
self.options = []
|
||||
|
||||
|
||||
for n in node:
|
||||
if n[0:3] == "xs:":
|
||||
if n[3:] == "enumeration":
|
||||
@@ -274,6 +274,21 @@ class restriction:
|
||||
#TODO add whiteSpace
|
||||
else:
|
||||
logger.error({'result':'ERROR', 'sentence':'Restriction not implemented'})
|
||||
self.type = []
|
||||
|
||||
for n in node.childNodes:
|
||||
if n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("enumeration"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "enumeration"
|
||||
elif n.nodeType == n.ELEMENT_NODE and (n.tagName.endswith("Inclusive") or n.tagName.endswith("Exclusive")):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "bounds"
|
||||
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("length"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "length"
|
||||
elif n.nodeType == n.ELEMENT_NODE and n.tagName.endswith("pattern"):
|
||||
self.options.append(n.getAttribute("value"))
|
||||
self.type = "pattern"
|
||||
|
||||
def __eq__(self, other):
|
||||
result=False
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
###############################################################################
|
||||
# #
|
||||
# This file is part of IfcOpenShell. #
|
||||
# #
|
||||
# IfcOpenShell is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the Lesser GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3.0 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# IfcOpenShell is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# Lesser GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the Lesser GNU General Public License #
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
###############################################################################
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
import math
|
||||
|
||||
from OCC.Core.gp import gp_Pnt2d
|
||||
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeEdge2d
|
||||
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeWire
|
||||
|
||||
|
||||
class IfcTransitionCurveType(Enum):
|
||||
"""IFC 4.1 Section 8.9.2.9
|
||||
[https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/schema/ifcgeometryresource/lexical/ifctransitioncurvetype.htm]
|
||||
|
||||
The IfcTransitionCurveType indicates the curvature of a transition curve.
|
||||
"""
|
||||
BIQUADRATICPARABOLA = 1 # NOTE also referred to as Schramm curve.
|
||||
BLOSSCURVE = 2
|
||||
CLOTHOIDCURVE = 3
|
||||
COSINECURVE = 4
|
||||
CUBICPARABOLA = 5
|
||||
SINECURVE = 6 # NOTE also referred to as Klein curve
|
||||
|
||||
|
||||
@dataclass
|
||||
class TransitionCurve:
|
||||
"""
|
||||
A curve that transitions between a straight line and a circular arc
|
||||
(or the reverse).
|
||||
"""
|
||||
StartPoint: tuple # IfcSchema::IfcCartesianPoint
|
||||
StartDirection: float # IfcSchema::IfcPlaneAngleMeasure
|
||||
SegmentLength: float # IfcSchema::IfcPositiveLengthMeasure
|
||||
IsStartRadiusCCW: bool # IfcSchema::IfcBoolean
|
||||
IsEndRadiusCCW: bool # IfcSchema::IfcBoolean
|
||||
TransitionCurveType: IfcTransitionCurveType
|
||||
StartRadius: float = None # IfcSchema::IfcPositiveLengthMeasure
|
||||
EndRadius: float = None # IfcSchema::IfcPositiveLengthMeasure
|
||||
|
||||
def _calc_biquadratic_parabola_point(self, lpt, L, R, ccw):
|
||||
x = lpt
|
||||
if (x <= (L / 2)):
|
||||
y = x**4 / (6 * R * L**2)
|
||||
else:
|
||||
|
||||
yterm_1 = (-1 * x**4) / (6 * R * L**2)
|
||||
yterm_2 = (2 * x**3) / (3 * R * L)
|
||||
yterm_3 = x**2 / (2 * R)
|
||||
yterm_4 = (L * x) / (6 * R)
|
||||
yterm_5 = L**2 / (48 * R)
|
||||
|
||||
y = yterm_1 + yterm_2 - yterm_3 + yterm_4 - yterm_5
|
||||
|
||||
if not ccw:
|
||||
y = -y
|
||||
|
||||
return gp_Pnt2d(x, y)
|
||||
|
||||
def _calc_bloss_curve_point(self, lpt, L, R, ccw):
|
||||
pass
|
||||
|
||||
def _calc_clothoid_curve_point(self, lpt, L, R, ccw):
|
||||
RL = R * L
|
||||
xterm_1 = 1
|
||||
xterm_2 = lpt**4 / (40 * RL**2)
|
||||
xterm_3 = lpt**8 / (3456 * RL**4)
|
||||
xterm_4 = lpt**12 / (599040 * RL**6)
|
||||
x = lpt * (xterm_1 - xterm_2 + xterm_3 - xterm_4)
|
||||
|
||||
factor = lpt**3 / (6 * RL)
|
||||
yterm_1 = 1
|
||||
yterm_2 = lpt**4 / (56 * RL**2)
|
||||
yterm_3 = lpt**8 / (7040 * RL**4)
|
||||
yterm_4 = lpt**12 / (1612800 * RL**6)
|
||||
|
||||
y = factor * (yterm_1 - yterm_2 + yterm_3 - yterm_4)
|
||||
|
||||
if not ccw:
|
||||
y = -y
|
||||
|
||||
return gp_Pnt2d(x, y)
|
||||
|
||||
def _calc_cosine_curve_point(self, lpt, L, R, ccw):
|
||||
pi = math.pi
|
||||
psi_x = (pi * lpt) / L
|
||||
|
||||
xterm_1 = (L**2) / (8.0 * pi**2 * R**2)
|
||||
xterm_2 = L / pi
|
||||
xterm_3 = psi_x**3 / (3.0)
|
||||
xterm_4 = psi_x / (2.0)
|
||||
xterm_5 = (math.sin(psi_x) * math.cos(psi_x)) / (2.0)
|
||||
xterm_6 = psi_x * math.cos(psi_x)
|
||||
|
||||
x = lpt - xterm_1 * xterm_2 * ( xterm_3 + xterm_4 - xterm_5 - (2.0 * xterm_6))
|
||||
|
||||
# TODO: code for y - coordinate
|
||||
y = 0
|
||||
|
||||
if not ccw:
|
||||
y = -y
|
||||
|
||||
return gp_Pnt2d(x, y)
|
||||
|
||||
def _calc_cubic_parabola_point(self, lpt, L, R, ccw):
|
||||
|
||||
x = lpt
|
||||
y = math.pow(x, 3) / (6 * R * L)
|
||||
if not ccw:
|
||||
y = -y
|
||||
|
||||
return gp_Pnt2d(x, y)
|
||||
|
||||
def _calc_sine_curve_point(self, lpt, L, R, ccw):
|
||||
pass
|
||||
|
||||
def _calc_transition_curve_point(self, lpt, L, R, ccw, trans_type):
|
||||
|
||||
if trans_type == "BIQUADRATICPARABOLA":
|
||||
return self._calc_cubic_parabola_point(lpt, L, R, ccw)
|
||||
elif trans_type == "BLOSSCURVE":
|
||||
# return _calc_bloss_curve_point(lpt, L, R, ccw)
|
||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
||||
elif trans_type == "CLOTHOIDCURVE":
|
||||
return self._calc_clothoid_curve_point(lpt, L, R, ccw)
|
||||
elif trans_type == "COSINECURVE":
|
||||
# return _calc_cosine_curve_point(lpt, L, R, ccw)
|
||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
||||
elif trans_type == "CUBICPARABOLA":
|
||||
return self._calc_cubic_parabola_point(lpt, L, R, ccw)
|
||||
elif trans_type == "SINECURVE":
|
||||
# return _calc_sine_curve_point(lpt, L, R, ccw)
|
||||
raise ValueError(f"Transition Curve type '{trans_type}' not implemented yet.")
|
||||
else:
|
||||
raise ValueError(f"Invalid Transition Curve type '{trans_type}'.")
|
||||
|
||||
def to_wire(self, stroking_interval=5.0):
|
||||
"""convert IfcTransitionSegment2D to OCC wire
|
||||
|
||||
:param stroking_interval: maximum curve length between points to be calculated
|
||||
:type stroking_interval: float
|
||||
:return: OCC wire containing interpolated points
|
||||
"""
|
||||
points = list()
|
||||
|
||||
L = self.SegmentLength
|
||||
R = self.EndRadius
|
||||
ccw = self.IsStartRadiusCCW
|
||||
trans_type = self.TransitionCurveType.name
|
||||
|
||||
num_intervals = math.ceil(L / stroking_interval)
|
||||
interval_dist = L / num_intervals
|
||||
lpt = 0.0 # length along the curve at the point to be calculated
|
||||
|
||||
for _ in range(num_intervals):
|
||||
points.append(self._calc_transition_curve_point(
|
||||
lpt, L, R, ccw, trans_type
|
||||
))
|
||||
lpt += interval_dist
|
||||
|
||||
edges = list()
|
||||
for i in range(len(points) - 1):
|
||||
edges.append(BRepBuilderAPI_MakeEdge2d(
|
||||
points[i], points[i + 1]
|
||||
))
|
||||
|
||||
wire = BRepBuilderAPI_MakeWire()
|
||||
for e in edges:
|
||||
wire.Add(e.Edge())
|
||||
# return wire
|
||||
return points
|
||||
@@ -1,18 +1,33 @@
|
||||
import datetime
|
||||
from re import findall
|
||||
|
||||
try:
|
||||
import isodate
|
||||
except:
|
||||
pass # Duration parsing not supported
|
||||
|
||||
def duration2dict(duration):
|
||||
results = {}
|
||||
for number, unit in findall("(?P<number>\d+)(?P<period>S|M|H|D|W|Y)", duration):
|
||||
results[unit] = number
|
||||
return results
|
||||
|
||||
def timedelta2duration(timedelta):
|
||||
components = {
|
||||
"days": getattr(timedelta, "days", 0),
|
||||
"hours": 0,
|
||||
"minutes": 0,
|
||||
"seconds": getattr(timedelta, "seconds", 0),
|
||||
}
|
||||
if components["seconds"]:
|
||||
components["hours"], components["minutes"], components["seconds"] = [
|
||||
int(i) for i in str(datetime.timedelta(seconds=components["seconds"])).split(":")
|
||||
]
|
||||
return isodate.Duration(**components)
|
||||
|
||||
|
||||
def ifc2datetime(element):
|
||||
if isinstance(element, str) and element[0] == "P": # IfcDuration
|
||||
return duration2dict(element)
|
||||
elif isinstance(element, str) and element[2] == ":": # IfcTime
|
||||
if isinstance(element, str) and "P" in element[0:2]: # IfcDuration
|
||||
duration = isodate.parse_duration(element)
|
||||
if isinstance(duration, datetime.timedelta):
|
||||
return timedelta2duration(duration)
|
||||
return duration
|
||||
elif isinstance(element, str) and len(element) > 3 and element[2] == ":": # IfcTime
|
||||
return datetime.time.fromisoformat(element)
|
||||
elif isinstance(element, str) and ":" in element: # IfcDateTime
|
||||
return datetime.datetime.fromisoformat(element)
|
||||
@@ -40,8 +55,13 @@ def ifc2datetime(element):
|
||||
|
||||
def datetime2ifc(dt, ifc_type):
|
||||
if isinstance(dt, str):
|
||||
if ifc_type == "IfcDuration":
|
||||
return dt
|
||||
dt = datetime.datetime.fromisoformat(dt)
|
||||
if ifc_type == "IfcTimeStamp":
|
||||
|
||||
if ifc_type == "IfcDuration":
|
||||
return isodate.duration_isoformat(dt)
|
||||
elif ifc_type == "IfcTimeStamp":
|
||||
return int(dt.timestamp())
|
||||
elif ifc_type == "IfcDateTime":
|
||||
if isinstance(dt, datetime.datetime):
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
import ifcopenshell
|
||||
|
||||
|
||||
def get_psets(element):
|
||||
psets = {}
|
||||
try:
|
||||
if element.is_a("IfcTypeObject"):
|
||||
if element.HasPropertySets:
|
||||
for definition in element.HasPropertySets:
|
||||
psets[definition.Name] = get_property_definition(definition)
|
||||
else:
|
||||
for relationship in element.IsDefinedBy:
|
||||
if relationship.is_a("IfcRelDefinesByProperties"):
|
||||
definition = relationship.RelatingPropertyDefinition
|
||||
psets[definition.Name] = get_property_definition(definition)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
|
||||
print("failed to load properties: {}".format(e))
|
||||
traceback.print_exc()
|
||||
if element.is_a("IfcTypeObject"):
|
||||
if element.HasPropertySets:
|
||||
for definition in element.HasPropertySets:
|
||||
psets[definition.Name] = get_property_definition(definition)
|
||||
elif hasattr(element, "IsDefinedBy"):
|
||||
for relationship in element.IsDefinedBy:
|
||||
if relationship.is_a("IfcRelDefinesByProperties"):
|
||||
definition = relationship.RelatingPropertyDefinition
|
||||
psets[definition.Name] = get_property_definition(definition)
|
||||
return psets
|
||||
|
||||
|
||||
@@ -55,7 +52,9 @@ def get_properties(properties):
|
||||
|
||||
|
||||
def get_type(element):
|
||||
if hasattr(element, "IsTypedBy") and element.IsTypedBy:
|
||||
if element.is_a("IfcTypeObject"):
|
||||
return element
|
||||
elif hasattr(element, "IsTypedBy") and element.IsTypedBy:
|
||||
return element.IsTypedBy[0].RelatingType
|
||||
elif hasattr(element, "IsDefinedBy") and element.IsDefinedBy: # IFC2X3
|
||||
for relationship in element.IsDefinedBy:
|
||||
@@ -63,23 +62,20 @@ def get_type(element):
|
||||
return relationship.RelatingType
|
||||
|
||||
|
||||
def get_material(element):
|
||||
def get_material(element, should_skip_usage=False):
|
||||
if hasattr(element, "HasAssociations") and element.HasAssociations:
|
||||
for relationship in element.HasAssociations:
|
||||
if relationship.is_a("IfcRelAssociatesMaterial"):
|
||||
if relationship.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"):
|
||||
return relationship.RelatingMaterial.ForLayerSet
|
||||
elif relationship.RelatingMaterial.is_a("IfcMaterialProfileSetUsage"):
|
||||
return relationship.RelatingMaterial.ForProfileSet
|
||||
if should_skip_usage:
|
||||
if relationship.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"):
|
||||
return relationship.RelatingMaterial.ForLayerSet
|
||||
elif relationship.RelatingMaterial.is_a("IfcMaterialProfileSetUsage"):
|
||||
return relationship.RelatingMaterial.ForProfileSet
|
||||
return relationship.RelatingMaterial
|
||||
relating_type = get_type(element)
|
||||
if hasattr(relating_type, "HasAssociations") and relating_type.HasAssociations:
|
||||
for relationship in relating_type.HasAssociations:
|
||||
if relationship.is_a("IfcRelAssociatesMaterial"):
|
||||
if relationship.RelatingMaterial.is_a("IfcMaterialLayerSetUsage"):
|
||||
return relationship.RelatingMaterial.ForLayerSet
|
||||
elif relationship.RelatingMaterial.is_a("IfcMaterialProfileSetUsage"):
|
||||
return relationship.RelatingMaterial.ForProfileSet
|
||||
return relationship.RelatingMaterial
|
||||
|
||||
|
||||
@@ -105,24 +101,6 @@ def replace_attribute(element, old, new):
|
||||
element[i] = new_attribute
|
||||
|
||||
|
||||
def is_representation_of_context(representation, context, subcontext=None, target_view=None):
|
||||
if target_view is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.TargetView == target_view
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif subcontext is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif representation.ContextOfItems.ContextType == context:
|
||||
return True
|
||||
|
||||
|
||||
def remove_deep(ifc_file, element):
|
||||
# @todo maybe some sort of try-finally mechanism.
|
||||
ifc_file.batch()
|
||||
@@ -134,12 +112,25 @@ def remove_deep(ifc_file, element):
|
||||
ifc_file.unbatch()
|
||||
|
||||
|
||||
def get_representation(element, context, subcontext=None, target_view=None):
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
for r in element.Representation.Representations:
|
||||
if is_representation_of_context(r, context, subcontext, target_view):
|
||||
return r
|
||||
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
|
||||
for r in element.RepresentationMaps:
|
||||
if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view):
|
||||
return r.MappedRepresentation
|
||||
def copy(ifc_file, element):
|
||||
new = ifc_file.create_entity(element.is_a())
|
||||
for i, attribute in enumerate(element):
|
||||
if attribute is None:
|
||||
continue
|
||||
new[i] = attribute
|
||||
return new
|
||||
|
||||
|
||||
def copy_deep(ifc_file, element):
|
||||
new = ifc_file.create_entity(element.is_a())
|
||||
for i, attribute in enumerate(element):
|
||||
if attribute is None:
|
||||
continue
|
||||
if isinstance(attribute, ifcopenshell.entity_instance):
|
||||
attribute = copy_deep(ifc_file, attribute)
|
||||
elif isinstance(attribute, tuple) and attribute and isinstance(attribute[0], ifcopenshell.entity_instance):
|
||||
attribute = list(attribute)
|
||||
for j, item in enumerate(attribute):
|
||||
attribute[j] = copy_deep(ifc_file, item)
|
||||
new[i] = attribute
|
||||
return new
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
def get_context(ifc_file, context, subcontext=None, target_view=None):
|
||||
if subcontext or target_view:
|
||||
elements = ifc_file.by_type("IfcGeometricRepresentationSubContext")
|
||||
else:
|
||||
elements = ifc_file.by_type("IfcGeometricRepresentationContext", include_subtypes=False)
|
||||
for element in elements:
|
||||
if context and element.ContextType != context:
|
||||
continue
|
||||
if subcontext and getattr(element, "ContextIdentifier") != subcontext:
|
||||
continue
|
||||
if target_view and getattr(element, "TargetView") != target_view:
|
||||
continue
|
||||
return element
|
||||
|
||||
|
||||
def is_representation_of_context(representation, context, subcontext=None, target_view=None):
|
||||
if target_view is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.TargetView == target_view
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif subcontext is not None:
|
||||
return (
|
||||
representation.ContextOfItems.is_a("IfcGeometricRepresentationSubContext")
|
||||
and representation.ContextOfItems.ContextIdentifier == subcontext
|
||||
and representation.ContextOfItems.ContextType == context
|
||||
)
|
||||
elif representation.ContextOfItems.ContextType == context:
|
||||
return True
|
||||
|
||||
|
||||
def get_representation(element, context, subcontext=None, target_view=None):
|
||||
if element.is_a("IfcProduct") and element.Representation:
|
||||
for r in element.Representation.Representations:
|
||||
if is_representation_of_context(r, context, subcontext, target_view):
|
||||
return r
|
||||
elif element.is_a("IfcTypeProduct") and element.RepresentationMaps:
|
||||
for r in element.RepresentationMaps:
|
||||
if is_representation_of_context(r.MappedRepresentation, context, subcontext, target_view):
|
||||
return r.MappedRepresentation
|
||||
@@ -221,7 +221,7 @@ class Selector:
|
||||
key = ".".join(key.split(".")[1:])
|
||||
elif "." in key and key.split(".")[0] == "material":
|
||||
try:
|
||||
element = ifcopenshell.util.element.get_material(element)
|
||||
element = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if not element:
|
||||
return None
|
||||
except:
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
import datetime
|
||||
import ifcopenshell.util.date
|
||||
from functools import lru_cache
|
||||
|
||||
|
||||
def derive_calendar(task):
|
||||
calendar = [
|
||||
rel.RelatingControl
|
||||
for rel in task.HasAssignments or []
|
||||
if rel.is_a("IfcRelAssignsToControl") and rel.RelatingControl.is_a("IfcWorkCalendar")
|
||||
]
|
||||
if calendar:
|
||||
return calendar[0]
|
||||
for rel in task.Nests or []:
|
||||
return derive_calendar(rel.RelatingObject)
|
||||
|
||||
|
||||
def count_working_days(start, finish, calendar):
|
||||
result = 0
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
finish_date = datetime.date(finish.year, finish.month, finish.day)
|
||||
while current_date < finish_date:
|
||||
if is_working_day(current_date, calendar):
|
||||
result += 1
|
||||
current_date += datetime.timedelta(days=1)
|
||||
return result
|
||||
|
||||
|
||||
def get_finish_date(start, duration, duration_type, calendar):
|
||||
current_date = datetime.date(start.year, start.month, start.day)
|
||||
abs_duration = abs(duration.days)
|
||||
date_offset = datetime.timedelta(days=1 if duration.days > 0 else -1)
|
||||
while abs_duration > 0:
|
||||
if duration_type == "ELAPSEDTIME" or not calendar:
|
||||
abs_duration -= 1
|
||||
elif ifcopenshell.util.sequence.is_working_day(current_date, calendar):
|
||||
abs_duration -= 1
|
||||
current_date += date_offset
|
||||
|
||||
if duration.days > 0:
|
||||
current_date = get_soonest_working_day(current_date, duration_type, calendar)
|
||||
else:
|
||||
current_date = get_recent_working_day(current_date, duration_type, calendar)
|
||||
return current_date
|
||||
|
||||
|
||||
def get_soonest_working_day(start, duration_type, calendar):
|
||||
if duration_type == "ELAPSEDTIME" or not calendar:
|
||||
return start
|
||||
while not is_working_day(start, calendar):
|
||||
start += datetime.timedelta(days=1)
|
||||
return start
|
||||
|
||||
|
||||
def get_recent_working_day(start, duration_type, calendar):
|
||||
if duration_type == "ELAPSEDTIME" or not calendar:
|
||||
return start
|
||||
while not is_working_day(start, calendar):
|
||||
start -= datetime.timedelta(days=1)
|
||||
return start
|
||||
|
||||
|
||||
@lru_cache(maxsize=None)
|
||||
def is_working_day(day, calendar):
|
||||
is_working_day = False
|
||||
for work_time in calendar.WorkingTimes or []:
|
||||
if is_work_time_applicable_to_day(work_time, day):
|
||||
is_working_day = True
|
||||
break
|
||||
if not is_working_day:
|
||||
return is_working_day
|
||||
for work_time in calendar.ExceptionTimes or []:
|
||||
if is_work_time_applicable_to_day(work_time, day):
|
||||
is_working_day = False
|
||||
break
|
||||
return is_working_day
|
||||
|
||||
|
||||
def is_work_time_applicable_to_day(work_time, day):
|
||||
start = None
|
||||
finish = None
|
||||
if isinstance(day, datetime.datetime):
|
||||
day = datetime.date(day.year, day.month, day.day)
|
||||
|
||||
if work_time.Start:
|
||||
start = ifcopenshell.util.date.ifc2datetime(work_time.Start)
|
||||
if start > day:
|
||||
return False
|
||||
|
||||
if work_time.Finish:
|
||||
finish = ifcopenshell.util.date.ifc2datetime(work_time.Finish)
|
||||
if finish < day:
|
||||
return False
|
||||
|
||||
if not work_time.RecurrencePattern:
|
||||
return True
|
||||
|
||||
recurrence = work_time.RecurrencePattern
|
||||
|
||||
if recurrence.RecurrenceType == "DAILY":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return True
|
||||
if not work_time.Start:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "WEEKLY":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent
|
||||
if not work_time.Start:
|
||||
return False
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "MONTHLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return day.day in recurrence.DayComponent
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "MONTHLY_BY_POSITION":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (day.weekday() + 1) in recurrence.WeekdayComponent and math.floor(day.day / 7) + 1 == recurrence[
|
||||
"Position"
|
||||
]
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "YEARLY_BY_DAY_OF_MONTH":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return day.month in recurrence.MonthComponent and day.day in recurrence.DayComponent
|
||||
return False # TODO
|
||||
elif recurrence.RecurrenceType == "YEARLY_BY_POSITION":
|
||||
if not recurrence.Interval and not recurrence.Occurrences:
|
||||
return (
|
||||
day.month in recurrence.MonthComponent
|
||||
and (day.weekday() + 1) in recurrence.WeekdayComponent
|
||||
and math.floor(day.day / 7) + 1 == recurrence.Position
|
||||
)
|
||||
return False # TODO
|
||||
Reference in New Issue
Block a user