This commit is contained in:
Andrej730
2025-03-14 10:43:47 +05:00
parent 10ecac33b8
commit 0e8810c1f5
118 changed files with 869 additions and 1203 deletions
+1 -1
View File
@@ -380,7 +380,7 @@ class BIM_PT_object_qtos(Panel):
filter_keyword=context.scene.GlobalPsetProperties.qto_filter,
)
layout = self.layout
qtoprops = context.scene.BIMQtoProperties
qtoprops = tool.Qto.get_qto_props()
row = layout.row(align=True)
row.prop(qtoprops, "qto_rule", text="")
# A bit confusing as we typically use this icon for is_null.
@@ -76,6 +76,7 @@ def get_length(o: bpy.types.Object, vg_index: Optional[int] = None) -> float:
return max(y, z)
length = 0
assert isinstance(o.data, bpy.types.Mesh)
edges = [
e
for e in o.data.edges
@@ -269,6 +270,7 @@ def get_net_perimeter(o: bpy.types.Object) -> float:
def get_gross_perimeter(o: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(o)
assert element
mesh = get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("GrossObj", mesh)
gross_perimeter = get_net_perimeter(gross_obj)
@@ -420,6 +422,7 @@ def get_gross_footprint_area(o: bpy.types.Object) -> float:
return get_net_footprint_area(o)
element = tool.Ifc.get_entity(o)
assert element
mesh = get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("GrossObj", mesh)
gross_footprint_area = get_net_footprint_area(gross_obj)
@@ -474,6 +477,7 @@ def get_gross_surface_area(o: bpy.types.Object, vg_index: Optional[int] = None)
return get_net_surface_area(o)
element = tool.Ifc.get_entity(o)
assert element
mesh = get_gross_element_mesh(element)
area = get_mesh_area(mesh)
bpy.data.meshes.remove(mesh)
@@ -506,6 +510,7 @@ def is_polygon_in_vg(polygon: bpy.types.MeshPolygon, vertices_in_vg: list[bpy.ty
def get_net_volume(o: bpy.types.Object) -> float:
assert isinstance(o.data, bpy.types.Mesh)
o_mesh = bmesh.new()
o_mesh.from_mesh(o.data)
volume = o_mesh.calc_volume()
@@ -518,6 +523,7 @@ def get_gross_volume(o: bpy.types.Object) -> float:
return get_net_volume(o)
element = tool.Ifc.get_entity(o)
assert element
mesh = get_gross_element_mesh(element)
bm = get_bmesh_from_mesh(mesh)
@@ -562,6 +568,7 @@ def get_net_weight(obj: bpy.types.Object) -> Union[float, None]:
def get_obj_mass_density(obj: bpy.types.Object) -> Union[float, None]:
entity = tool.Ifc.get_entity(obj)
assert entity
material = ifcopenshell.util.element.get_material(entity)
if material is None:
return
@@ -764,6 +771,7 @@ def get_outer_surface_area(obj: bpy.types.Object) -> float:
def get_end_area(obj: bpy.types.Object) -> float:
element = tool.Ifc.get_entity(obj)
assert element
gross_mesh = get_gross_element_mesh(element)
gross_obj = bpy.data.objects.new("MyObject", gross_mesh)
@@ -51,6 +51,7 @@ def calculate_mesh_quantity(
result = 0
edit_mode = context.active_object.mode == "EDIT"
for obj in objs:
assert isinstance(obj.data, bpy.types.Mesh)
if edit_mode:
bm = bmesh.from_edit_mesh(obj.data)
result += operation(bm)
+7 -7
View File
@@ -49,7 +49,7 @@ class CalculateEdgeLengths(bpy.types.Operator):
def execute(self, context):
result = helper.calculate_edges_lengths([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
tool.Qto.set_qto_result(result)
return {"FINISHED"}
@@ -64,7 +64,7 @@ class CalculateFaceAreas(bpy.types.Operator):
def execute(self, context):
result = helper.calculate_faces_areas([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
tool.Qto.set_qto_result(result)
return {"FINISHED"}
@@ -79,7 +79,7 @@ class CalculateObjectVolumes(bpy.types.Operator):
def execute(self, context):
result = helper.calculate_volumes([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
tool.Qto.set_qto_result(result)
return {"FINISHED"}
@@ -94,7 +94,7 @@ class CalculateFormworkArea(bpy.types.Operator):
def execute(self, context):
result = helper.calculate_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
tool.Qto.set_qto_result(result)
return {"FINISHED"}
@@ -109,7 +109,7 @@ class CalculateSideFormworkArea(bpy.types.Operator):
def execute(self, context):
result = helper.calculate_side_formwork_area([o for o in context.selected_objects if o.type == "MESH"], context)
context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
tool.Qto.set_qto_result(result)
return {"FINISHED"}
@@ -126,7 +126,7 @@ class CalculateSingleQuantity(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
import ifc5d.qto
props = context.scene.BIMQtoProperties
props = tool.Qto.get_qto_props()
elements = set()
for obj in tool.Blender.get_selected_objects(include_active=False):
element = tool.Ifc.get_entity(obj)
@@ -163,7 +163,7 @@ class PerformQuantityTakeOff(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
import ifc5d.qto
props = context.scene.BIMQtoProperties
props = tool.Qto.get_qto_props()
elements: set[ifcopenshell.entity_instance]
if context.selected_objects:
+23 -6
View File
@@ -31,12 +31,16 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Union
def get_qto_rule(self, context):
CALCULATOR_FUNCTION_ENUM_ITEMS: list[Union[tuple[str, str, str], None]] = []
def get_qto_rule(self: "BIMQtoProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
ifc_file = tool.Ifc.get()
is_ifc4x3 = ifc_file.schema == "IFC4X3"
results = []
results: list[tuple[str, str, str]] = []
for rule_id, rule in ifc5d.qto.rules.items():
if rule_id.startswith("IFC4X3") != is_ifc4x3:
continue
@@ -44,14 +48,16 @@ def get_qto_rule(self, context):
return results
def get_calculator(self, context):
results = []
def get_calculator(self: "BIMQtoProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
results: list[tuple[str, str, str]] = []
for name, calculator in ifc5d.qto.calculators.items():
results.append((name, name, calculator.__doc__))
results.append((name, name, calculator.__doc__ or ""))
return results
def get_calculator_function(self, context):
def get_calculator_function(
self: "BIMQtoProperties", context: bpy.types.Context
) -> list[Union[tuple[str, str, str], None]]:
global CALCULATOR_FUNCTION_ENUM_ITEMS
calculator = ifc5d.qto.calculators[self.calculator]
CALCULATOR_FUNCTION_ENUM_ITEMS = []
@@ -60,6 +66,8 @@ def get_calculator_function(self, context):
measure = function.measure.split("Measure")[0][3:]
if previous_measure is not None and measure != previous_measure:
CALCULATOR_FUNCTION_ENUM_ITEMS.append(None)
description = function.description
description += f"\n\nInternal function id: '{function_id}'."
CALCULATOR_FUNCTION_ENUM_ITEMS.append((function_id, f"{measure}: {function.name}", function.description))
previous_measure = measure
return CALCULATOR_FUNCTION_ENUM_ITEMS
@@ -80,3 +88,12 @@ class BIMQtoProperties(PropertyGroup):
),
default=False,
)
if TYPE_CHECKING:
qto_rule: str
calculator: str
calculator_function: str
qto_result: str
qto_name: str
prop_name: str
fallback: bool
+4 -3
View File
@@ -17,6 +17,7 @@
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import bpy
import bonsai.tool as tool
from bonsai.bim.module.qto.data import QtoData
@@ -32,7 +33,7 @@ class BIM_PT_qto(bpy.types.Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMQtoProperties
props = tool.Qto.get_qto_props()
row = layout.row()
if context.selected_objects:
@@ -57,7 +58,7 @@ class BIM_PT_qto_manual(bpy.types.Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMQtoProperties
props = tool.Qto.get_qto_props()
row = layout.row()
row.prop(props, "calculator")
@@ -83,7 +84,7 @@ class BIM_PT_qto_simple(bpy.types.Panel):
def draw(self, context):
layout = self.layout
props = context.scene.BIMQtoProperties
props = tool.Qto.get_qto_props()
row = layout.row()
row.prop(props, "qto_result", text="Results")
@@ -59,7 +59,7 @@ class LoadGroupDecorationData:
return ret
m = models[0]
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
if props.activity_type == "Action":
groups = m.LoadedBy or []
for g in groups:
@@ -128,7 +128,8 @@ class ConnectedStructuralMembersData:
if not element:
return []
results = []
props = bpy.context.active_object.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
for rel in element.ConnectsStructuralMembers or []:
condition = rel.AppliedCondition
if condition:
@@ -257,7 +258,7 @@ class StructuralLoadCasesData:
@classmethod
def applicable_structural_loads(cls):
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
results = []
for load in tool.Ifc.get().by_type("IfcStructuralLoad"):
if not load.Name or not load.is_a(props.applicable_structural_load_types):
@@ -24,6 +24,7 @@ from mathutils import Vector
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.util.attribute
import ifcopenshell.util.placement
import ifcopenshell.util.unit as ifcunit
import bonsai.tool as tool
from bonsai.bim.module.structural.shader import DecorationShader
@@ -304,7 +305,7 @@ class ShaderInfo:
populate_members_dict("surface_members", element, activity, factor)
recursive_subgroups(subgorups, rec_limit - 1, activity_type, factor=factor)
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
group_definition_id = int(props.load_group_to_show)
file = tool.Ifc.get()
groups = [file.by_id(group_definition_id)]
@@ -327,7 +328,7 @@ class ShaderInfo:
maximum = max([abs(float(i)) for i in values])
if maximum == 0:
continue
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
reference_frame = props.reference_frame
orientation = np.eye(3)
if reference_frame == "LOCAL_COORDS":
@@ -435,7 +436,7 @@ class ShaderInfo:
) -> np.ndarray:
"provides the transformation matrix to convert between reference frames"
global_or_local = activity.GlobalOrLocal
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
reference_frame = props.reference_frame
transform_matrix = np.eye(3)
if reference_frame == "LOCAL_COORDS" and global_or_local != reference_frame:
@@ -473,7 +474,7 @@ class ShaderInfo:
"mz": (np.array((1, 0, 0)), np.array((0, 1, 0))),
}
keys = ["fx", "fy", "fz", "mx", "my", "mz"]
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
reference_frame = props.reference_frame
if reference_frame == "LOCAL_COORDS":
for key in keys:
@@ -591,7 +592,7 @@ class ShaderInfo:
z_axis = x_axis.cross(y_axis).normalized()
rotation = self.get_curve_member_rotation(member)
props = bpy.context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
reference_frame = props.reference_frame
is_local = reference_frame == "LOCAL_COORDS"
x_match = abs(Vector((1, 0, 0)).dot(x_axis)) > 0.99
@@ -20,6 +20,7 @@ import bpy
import json
import ifcopenshell
import ifcopenshell.api
import ifcopenshell.api.group
import ifcopenshell.api.structural
import ifcopenshell.util.attribute
import bonsai.bim.helper
@@ -77,7 +78,7 @@ class AddStructuralMemberConnection(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
props = tool.Structural.get_object_structural_props(obj)
file = tool.Ifc.get()
related_structural_connection = file.by_id(oprops.ifc_definition_id)
relating_structural_member = tool.Ifc.get_entity(props.relating_structural_member)
@@ -101,7 +102,8 @@ class EnableEditingStructuralConnectionCondition(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.active_connects_structural_member = self.connects_structural_member
return {"FINISHED"}
@@ -113,7 +115,8 @@ class DisableEditingStructuralConnectionCondition(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.active_connects_structural_member = 0
return {"FINISHED"}
@@ -166,7 +169,8 @@ class EnableEditingStructuralBoundaryCondition(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.boundary_condition_attributes.clear()
condition = tool.Ifc.get().by_id(self.boundary_condition)
@@ -206,7 +210,8 @@ class EditStructuralBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
file = tool.Ifc.get()
connection = file.by_id(self.connection)
@@ -236,7 +241,10 @@ class DisableEditingStructuralBoundaryCondition(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.active_object.BIMStructuralProperties.active_boundary_condition = 0
obj = context.active_object
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.active_boundary_condition = 0
return {"FINISHED"}
@@ -353,7 +361,7 @@ class EnableEditingStructuralItemAxis(bpy.types.Operator):
obj = context.active_object
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
props = tool.Structural.get_object_structural_props(obj)
self.file = tool.Ifc.get()
item = self.file.by_id(oprops.ifc_definition_id)
@@ -392,7 +400,8 @@ class DisableEditingStructuralItemAxis(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.is_editing_axis = False
if props.axis_empty:
bpy.data.objects.remove(props.axis_empty)
@@ -407,7 +416,7 @@ class EditStructuralItemAxis(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object
assert obj
oprops = tool.Blender.get_object_bim_props(obj)
props = obj.BIMStructuralProperties
props = tool.Structural.get_object_structural_props(obj)
relative_matrix = props.axis_empty.matrix_world @ obj.matrix_world.inverted()
z_axis = relative_matrix.col[2][0:3]
self.file = tool.Ifc.get()
@@ -429,7 +438,7 @@ class EnableEditingStructuralConnectionCS(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
assert obj
props = obj.BIMStructuralProperties
props = tool.Structural.get_object_structural_props(obj)
self.file = tool.Ifc.get()
item = tool.Ifc.get_entity(obj)
@@ -481,7 +490,8 @@ class DisableEditingStructuralConnectionCS(bpy.types.Operator):
def execute(self, context):
obj = context.active_object
props = obj.BIMStructuralProperties
assert obj
props = tool.Structural.get_object_structural_props(obj)
props.is_editing_connection_cs = False
if props.ccs_empty:
bpy.data.objects.remove(props.ccs_empty)
@@ -498,7 +508,7 @@ class EditStructuralConnectionCS(bpy.types.Operator, tool.Ifc.Operator):
assert obj
item = tool.Ifc.get_entity(obj)
assert item
props = obj.BIMStructuralProperties
props = tool.Structural.get_object_structural_props(obj)
relative_matrix = props.ccs_empty.matrix_world @ obj.matrix_world.inverted()
x_axis = relative_matrix.col[0][0:3]
z_axis = relative_matrix.col[2][0:3]
@@ -567,13 +577,13 @@ class EditStructuralLoadCase(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
attributes = bonsai.bim.helper.export_attributes(props.load_case_attributes)
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.edit_structural_load_case",
ifcopenshell.api.structural.edit_structural_load_case(
self.file,
**{"load_case": self.file.by_id(props.active_load_case_id), "attributes": attributes},
load_case=self.file.by_id(props.active_load_case_id),
attributes=attributes,
)
bpy.ops.bim.disable_editing_structural_load_case()
return {"FINISHED"}
@@ -587,9 +597,7 @@ class RemoveStructuralLoadCase(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.remove_structural_load_case", self.file, load_case=self.file.by_id(self.load_case)
)
ifcopenshell.api.structural.remove_structural_load_case(self.file, load_case=self.file.by_id(self.load_case))
return {"FINISHED"}
@@ -600,7 +608,7 @@ class EnableEditingStructuralLoadCase(bpy.types.Operator):
load_case: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
self.props.active_load_case_id = self.load_case
self.props.load_case_editing_type = "ATTRIBUTES"
self.props.load_case_attributes.clear()
@@ -620,7 +628,8 @@ class DisableEditingStructuralLoadCase(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_load_case_id = 0
props = tool.Structural.get_structural_props()
props.active_load_case_id = 0
return {"FINISHED"}
@@ -631,9 +640,9 @@ class EnableEditingStructuralLoadCaseGroups(bpy.types.Operator):
load_case: bpy.props.IntProperty()
def execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.props.active_load_case_id = self.load_case
self.props.load_case_editing_type = "GROUPS"
props = tool.Structural.get_structural_props()
props.active_load_case_id = self.load_case
props.load_case_editing_type = "GROUPS"
return {"FINISHED"}
@@ -645,10 +654,8 @@ class AddStructuralLoadGroup(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
load_group = ifcopenshell.api.run("structural.add_structural_load_group", self.file)
ifcopenshell.api.run(
"group.assign_group", self.file, products=[load_group], group=self.file.by_id(self.load_case)
)
load_group = ifcopenshell.api.structural.add_structural_load_group(self.file)
ifcopenshell.api.group.assign_group(self.file, products=[load_group], group=self.file.by_id(self.load_case))
return {"FINISHED"}
@@ -660,9 +667,7 @@ class RemoveStructuralLoadGroup(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context):
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.remove_structural_load_group", self.file, load_group=self.file.by_id(self.load_group)
)
ifcopenshell.api.structural.remove_structural_load_group(self.file, load_group=self.file.by_id(self.load_group))
return {"FINISHED"}
@@ -674,15 +679,15 @@ class EnableEditingStructuralLoadGroupActivities(bpy.types.Operator):
def execute(self, context):
self.file = tool.Ifc.get()
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
self.props.active_load_group_id = self.load_group
self.props.load_group_editing_type = "ACTIVITY"
self.load_structural_activities()
return {"FINISHED"}
def load_structural_activities(self):
def load_structural_activities(self) -> None:
self.props.load_group_activities.clear()
for rel in tool.Ifc.get().by_id(self.load_group).IsGroupedBy:
for rel in self.file.by_id(self.load_group).IsGroupedBy:
for activity in rel.RelatedObjects:
new = self.props.load_group_activities.add()
new.ifc_definition_id = activity.id()
@@ -697,7 +702,7 @@ class AddStructuralActivity(bpy.types.Operator, tool.Ifc.Operator):
load_group: bpy.props.IntProperty()
def _execute(self, context):
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
self.file = tool.Ifc.get()
for obj in context.selected_objects:
element = tool.Ifc.get_entity(obj)
@@ -726,16 +731,13 @@ class AddStructuralActivity(bpy.types.Operator, tool.Ifc.Operator):
ifc_class = applicable_activity_class[element.is_a()]
activity = ifcopenshell.api.run(
"structural.add_structural_activity",
activity = ifcopenshell.api.structural.add_structural_activity(
self.file,
ifc_class=ifc_class,
applied_load=self.file.by_id(int(self.props.applicable_structural_loads)),
structural_member=element,
)
ifcopenshell.api.run(
"group.assign_group", self.file, products=[activity], group=self.file.by_id(self.load_group)
)
ifcopenshell.api.group.assign_group(self.file, products=[activity], group=self.file.by_id(self.load_group))
bpy.ops.bim.enable_editing_structural_load_group_activities(load_group=self.load_group)
return {"FINISHED"}
@@ -747,7 +749,7 @@ class LoadStructuralLoads(bpy.types.Operator):
def execute(self, context):
self.file = tool.Ifc.get()
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.structural_loads.clear()
loads = tool.Ifc.get().by_type("IfcStructuralLoad")
if props.filtered_structural_loads:
@@ -780,7 +782,8 @@ class DisableStructuralLoadEditingUI(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.is_editing_loads = False
props = tool.Structural.get_structural_props()
props.is_editing_loads = False
return {"FINISHED"}
@@ -806,7 +809,7 @@ class EnableEditingStructuralLoad(bpy.types.Operator):
structural_load: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.structural_load_attributes.clear()
bonsai.bim.helper.import_attributes2(
tool.Ifc.get().by_id(self.structural_load), props.structural_load_attributes
@@ -821,7 +824,8 @@ class DisableEditingStructuralLoad(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_structural_load_id = 0
props = tool.Structural.get_structural_props()
props.active_structural_load_id = 0
return {"FINISHED"}
@@ -832,12 +836,10 @@ class RemoveStructuralLoad(bpy.types.Operator, tool.Ifc.Operator):
structural_load: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.remove_structural_load",
ifcopenshell.api.structural.remove_structural_load(
self.file,
**{"structural_load": self.file.by_id(self.structural_load)},
structural_load=self.file.by_id(self.structural_load),
)
bpy.ops.bim.load_structural_loads()
return {"FINISHED"}
@@ -849,16 +851,13 @@ class EditStructuralLoad(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
attributes = bonsai.bim.helper.export_attributes(props.structural_load_attributes)
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.edit_structural_load",
ifcopenshell.api.structural.edit_structural_load(
self.file,
**{
"structural_load": self.file.by_id(props.active_structural_load_id),
"attributes": attributes,
},
structural_load=self.file.by_id(props.active_structural_load_id),
attributes=attributes,
)
bpy.ops.bim.load_structural_loads()
return {"FINISHED"}
@@ -870,7 +869,7 @@ class ToggleFilterStructuralLoads(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.filtered_structural_loads = not props.filtered_structural_loads
bpy.ops.bim.load_structural_loads()
return {"FINISHED"}
@@ -883,7 +882,7 @@ class LoadBoundaryConditions(bpy.types.Operator):
def execute(self, context):
self.file = tool.Ifc.get()
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.boundary_conditions.clear()
conditions = tool.Ifc.get().by_type("IfcBoundaryCondition")
if props.filtered_boundary_conditions:
@@ -916,7 +915,7 @@ class ToggleFilterBoundaryConditions(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.filtered_boundary_conditions = not props.filtered_boundary_conditions
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
@@ -928,7 +927,8 @@ class DisableBoundaryConditionEditingUI(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.is_editing_boundary_conditions = False
props = tool.Structural.get_structural_props()
props.is_editing_boundary_conditions = False
return {"FINISHED"}
@@ -939,8 +939,7 @@ class AddBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
ifc_class: bpy.props.StringProperty()
def _execute(self, context):
result = ifcopenshell.api.run(
"structural.add_structural_boundary_condition",
result = ifcopenshell.api.structural.add_structural_boundary_condition(
tool.Ifc.get(),
name="New Load",
ifc_class=self.ifc_class,
@@ -957,7 +956,7 @@ class EnableEditingBoundaryCondition(bpy.types.Operator):
boundary_condition: bpy.props.IntProperty()
def execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
props.boundary_condition_attributes.clear()
boundary_condition = tool.Ifc.get().by_id(self.boundary_condition)
@@ -994,7 +993,8 @@ class DisableEditingBoundaryCondition(bpy.types.Operator):
bl_options = {"REGISTER", "UNDO"}
def execute(self, context):
context.scene.BIMStructuralProperties.active_boundary_condition_id = 0
props = tool.Structural.get_structural_props()
props.active_boundary_condition_id = 0
return {"FINISHED"}
@@ -1005,12 +1005,10 @@ class RemoveBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
boundary_condition: bpy.props.IntProperty()
def _execute(self, context):
props = context.scene.BIMStructuralProperties
self.file = tool.Ifc.get()
ifcopenshell.api.run(
"structural.remove_structural_boundary_condition",
ifcopenshell.api.structural.remove_structural_boundary_condition(
self.file,
**{"boundary_condition": self.file.by_id(self.boundary_condition)},
boundary_condition=self.file.by_id(self.boundary_condition),
)
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
@@ -1022,7 +1020,7 @@ class EditBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
props = context.scene.BIMStructuralProperties
props = tool.Structural.get_structural_props()
self.file = tool.Ifc.get()
# attributes = bonsai.bim.helper.export_attributes(props.boundary_condition_attributes)
attributes = {}
@@ -1035,10 +1033,10 @@ class EditBoundaryCondition(bpy.types.Operator, tool.Ifc.Operator):
attributes[attribute.name] = {"value": attribute.bool_value, "type": attribute.enum_value}
else:
attributes[attribute.name] = {"value": attribute.float_value, "type": attribute.enum_value}
ifcopenshell.api.run(
"structural.edit_structural_boundary_condition",
ifcopenshell.api.structural.edit_structural_boundary_condition(
self.file,
**{"condition": self.file.by_id(props.active_boundary_condition_id), "attributes": attributes},
condition=self.file.by_id(props.active_boundary_condition_id),
attributes=attributes,
)
bpy.ops.bim.load_boundary_conditions()
return {"FINISHED"}
@@ -37,52 +37,62 @@ from bpy.props import (
FloatVectorProperty,
CollectionProperty,
)
from typing import TYPE_CHECKING, Union
def get_load_groups_to_show(self, context):
def get_load_groups_to_show(self: "BIMStructuralProperties", context: bpy.types.Context) -> list[tuple[str, str, str]]:
if not LoadGroupDecorationData.is_loaded:
LoadGroupDecorationData.load()
return LoadGroupDecorationData.data["load_groups_to_show"]
def update_activity_type(self, context):
def update_activity_type(self: "BIMStructuralProperties", context: bpy.types.Context) -> None:
LoadGroupDecorationData.is_loaded = False
def get_applicable_structural_load_types(self, context):
def get_applicable_structural_load_types(
self: "BIMStructuralProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not StructuralLoadCasesData.is_loaded:
StructuralLoadCasesData.load()
return StructuralLoadCasesData.data["applicable_structural_load_types"]
def updateApplicableStructuralLoadTypes(self, context):
def updateApplicableStructuralLoadTypes(self: "BIMStructuralProperties", context: bpy.types.Context) -> None:
StructuralLoadCasesData.data["applicable_structural_load_types"] = (
StructuralLoadCasesData.applicable_structural_load_types()
)
def get_applicable_structural_loads(self, context):
def get_applicable_structural_loads(
self: "BIMStructuralProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not StructuralLoadCasesData.is_loaded:
StructuralLoadCasesData.load()
return StructuralLoadCasesData.data["applicable_structural_loads"]
def get_structural_load_types(self, context):
def get_structural_load_types(
self: "BIMStructuralProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not StructuralLoadsData.is_loaded:
StructuralLoadsData.load()
return StructuralLoadsData.data["structural_load_types"]
def get_boundary_condition_types(self, context):
def get_boundary_condition_types(
self: "BIMStructuralProperties", context: bpy.types.Context
) -> list[tuple[str, str, str]]:
if not BoundaryConditionsData.is_loaded:
BoundaryConditionsData.load()
return BoundaryConditionsData.data["boundary_condition_types"]
def updateAxisAngle(self, context):
def updateAxisAngle(self: "BIMObjectStructuralProperties", context: bpy.types.Context) -> None:
if not self.axis_empty:
return
obj = context.active_object
assert obj and isinstance(obj.data, bpy.types.Mesh)
empty = self.axis_empty
x_axis = obj.data.vertices[1].co - obj.data.vertices[0].co
empty.location = obj.data.vertices[0].co
@@ -92,10 +102,11 @@ def updateAxisAngle(self, context):
empty.rotation_euler[0] = radians(self.axis_angle)
def updateConnectionCS(self, context):
def updateConnectionCS(self: "BIMObjectStructuralProperties", context: bpy.types.Context) -> None:
if not self.ccs_empty:
return
obj = context.active_object
assert obj and isinstance(obj.data, bpy.types.Mesh)
empty = self.ccs_empty
empty.location = obj.data.vertices[0].co
empty.rotation_mode = "XYZ"
@@ -108,24 +119,39 @@ class StructuralAnalysisModel(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
ifc_definition_id: int
class StructuralActivity(PropertyGroup):
name: StringProperty(name="Name")
applied_load_class: StringProperty(name="Applied Load Class")
ifc_definition_id: IntProperty(name="IFC Definition ID")
if TYPE_CHECKING:
applied_load_class: str
ifc_definition_id: int
class StructuralLoad(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
number_of_inverse_references: IntProperty(name="Number of Inverse References")
if TYPE_CHECKING:
ifc_definition_id: int
number_of_inverse_references: int
class BoundaryCondition(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
number_of_inverse_references: IntProperty(name="Number of Inverse References")
if TYPE_CHECKING:
ifc_definition_id: int
number_of_inverse_references: int
class BIMStructuralProperties(PropertyGroup):
structural_analysis_model_attributes: CollectionProperty(
@@ -187,6 +213,45 @@ class BIMStructuralProperties(PropertyGroup):
)
load_group_to_show: EnumProperty(items=get_load_groups_to_show, name="Load Groups")
if TYPE_CHECKING:
structural_analysis_model_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
is_editing: bool
structural_analysis_models: bpy.types.bpy_prop_collection_idprop[StructuralAnalysisModel]
active_structural_analysis_model_index: int
active_structural_analysis_model_id: int
load_case_editing_type: str
load_case_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_load_case_id: int
load_group_editing_type: str
active_load_group_id: int
applicable_structural_load_types: str
applicable_structural_loads: str
load_group_activities: bpy.types.bpy_prop_collection_idprop[StructuralActivity]
active_load_group_activity_index: int
structural_loads: bpy.types.bpy_prop_collection_idprop[StructuralLoad]
active_structural_load_index: int
active_structural_load_id: int
is_editing_loads: bool
structural_load_types: str
structural_load_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
filtered_structural_loads: bool
boundary_conditions: bpy.types.bpy_prop_collection_idprop[BoundaryCondition]
active_boundary_condition_index: int
active_boundary_condition_id: int
is_editing_boundary_conditions: bool
boundary_condition_types: str
boundary_condition_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
filtered_boundary_conditions: bool
show_loads: bool
update_load_repr: bool
enable_repr_auto_update: bool
reference_frame: str
activity_type: str
load_group_to_show: str
class BIMObjectStructuralProperties(PropertyGroup):
boundary_condition_attributes: CollectionProperty(name="Boundary Condition Attributes", type=Attribute)
@@ -203,3 +268,19 @@ class BIMObjectStructuralProperties(PropertyGroup):
ccs_y_angle: FloatProperty(name="Connection CS Y Angle", update=updateConnectionCS)
ccs_z_angle: FloatProperty(name="Connection CS Z Angle", update=updateConnectionCS)
ccs_empty: PointerProperty(name="CCS Empty", type=bpy.types.Object)
if TYPE_CHECKING:
boundary_condition_attributes: bpy.types.bpy_prop_collection_idprop[Attribute]
active_boundary_condition: int
active_connects_structural_member: int
relating_structural_member: Union[bpy.types.Object, None]
is_editing_axis: bool
axis_angle: float
axis_empty: Union[bpy.types.Object, None]
# relating_structural_activity: Union[bpy.types.Object, None]
is_editing_connection_cs: bool
ccs_x_angle: float
ccs_y_angle: float
ccs_z_angle: float
ccs_empty: Union[bpy.types.Object, None]
+87 -23
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.tool as tool
import bonsai.bim.helper
@@ -31,9 +32,25 @@ from bonsai.bim.module.structural.data import (
StructuralConnectionData,
BoundaryConditionsData,
)
from typing import TYPE_CHECKING, Any, Union
if TYPE_CHECKING:
from bonsai.bim.module.structural.prop import (
BIMStructuralProperties,
BIMObjectStructuralProperties,
BoundaryCondition,
StructuralLoad,
StructuralActivity,
StructuralAnalysisModel,
)
def draw_boundary_condition_ui(layout, boundary_condition, connection_id, props):
def draw_boundary_condition_ui(
layout: bpy.types.UILayout,
boundary_condition: dict[str, Any],
connection_id: int,
props: BIMObjectStructuralProperties,
) -> None:
row = layout.row(align=True)
if not boundary_condition:
row.label(text="No Boundary Condition Found", icon="CON_TRACKTO")
@@ -59,11 +76,13 @@ def draw_boundary_condition_ui(layout, boundary_condition, connection_id, props)
draw_boundary_condition_read_only_ui(layout, boundary_condition)
def draw_boundary_condition_editable_ui(layout: bpy.types.UILayout, props: bpy.types.PropertyGroup) -> None:
def draw_boundary_condition_editable_ui(
layout: bpy.types.UILayout, props: Union[BIMStructuralProperties, BIMObjectStructuralProperties]
) -> None:
draw_attributes(props.boundary_condition_attributes, layout)
def draw_boundary_condition_read_only_ui(layout, boundary_condition):
def draw_boundary_condition_read_only_ui(layout: bpy.types.UILayout, boundary_condition: dict[str, Any]) -> None:
for attribute in boundary_condition["attributes"]:
row = layout.row(align=True)
row.label(text=attribute["name"])
@@ -100,11 +119,15 @@ class BIM_PT_structural_boundary_conditions(Panel):
if not StructuralBoundaryConditionsData.is_loaded:
StructuralBoundaryConditionsData.load()
obj = context.active_object
assert obj
self.props = tool.Structural.get_object_structural_props(obj)
draw_boundary_condition_ui(
self.layout,
StructuralBoundaryConditionsData.data["boundary_condition"],
StructuralBoundaryConditionsData.data["connection_id"],
context.active_object.BIMStructuralProperties,
self.props,
)
@@ -135,7 +158,9 @@ class BIM_PT_connected_structural_members(Panel):
if not ConnectedStructuralMembersData.is_loaded:
ConnectedStructuralMembersData.load()
self.props = context.active_object.BIMStructuralProperties
obj = context.active_object
assert obj
self.props = tool.Structural.get_object_structural_props(obj)
row = self.layout.row(align=True)
row.prop(self.props, "relating_structural_member", text="", icon="CON_TRACKTO")
@@ -188,7 +213,9 @@ class BIM_PT_structural_member(Panel):
if not StructuralMemberData.is_loaded:
StructuralMemberData.load()
self.props = context.active_object.BIMStructuralProperties
obj = context.active_object
assert obj
self.props = tool.Structural.get_object_structural_props(obj)
if StructuralMemberData.data["active_object_class"] == "IfcStructuralCurveMember":
if self.props.is_editing_axis:
@@ -231,7 +258,9 @@ class BIM_PT_structural_connection(Panel):
if not StructuralConnectionData.is_loaded:
StructuralConnectionData.load()
self.props = context.active_object.BIMStructuralProperties
obj = context.active_object
assert obj
self.props = tool.Structural.get_object_structural_props(obj)
if StructuralConnectionData.data["active_object_class"] == "IfcStructuralCurveConnection":
if self.props.is_editing_axis:
@@ -281,7 +310,7 @@ class BIM_PT_structural_analysis_models(Panel):
if not StructuralAnalysisModelsData.is_loaded:
StructuralAnalysisModelsData.load()
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
row = self.layout.row(align=True)
row.label(
@@ -309,7 +338,16 @@ class BIM_PT_structural_analysis_models(Panel):
class BIM_UL_structural_analysis_models(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context: bpy.types.Context,
layout: bpy.types.UILayout,
data: BIMStructuralProperties,
item: StructuralAnalysisModel,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=item.name)
@@ -324,10 +362,10 @@ class BIM_UL_structural_analysis_models(UIList):
op = row.operator("bim.assign_structural_analysis_model", text="", icon="KEYFRAME", emboss=False)
op.structural_analysis_model = item.ifc_definition_id
if context.scene.BIMStructuralProperties.active_structural_analysis_model_id == item.ifc_definition_id:
if data.active_structural_analysis_model_id == item.ifc_definition_id:
row.operator("bim.edit_structural_analysis_model", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_structural_analysis_model", text="", icon="CANCEL")
elif context.scene.BIMStructuralProperties.active_structural_analysis_model_id:
elif data.active_structural_analysis_model_id:
op = row.operator("bim.remove_structural_analysis_model", text="", icon="X")
op.structural_analysis_model = item.ifc_definition_id
else:
@@ -354,7 +392,7 @@ class BIM_PT_structural_load_cases(Panel):
if not StructuralLoadCasesData.is_loaded:
StructuralLoadCasesData.load()
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
row = self.layout.row()
row.operator("bim.add_structural_load_case", icon="ADD")
@@ -425,7 +463,16 @@ class BIM_PT_structural_load_cases(Panel):
class BIM_UL_structural_activities(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMStructuralProperties,
item: StructuralActivity,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=item.name)
@@ -446,8 +493,7 @@ class BIM_PT_show_structural_activities(Panel):
return file and hasattr(file, "schema") and file.schema != "IFC2X3"
def draw(self, context):
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
row = self.layout.row(align=True)
row.operator(
@@ -480,7 +526,7 @@ class BIM_PT_structural_loads(Panel):
if not StructuralLoadsData.is_loaded:
StructuralLoadsData.load()
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
row = self.layout.row(align=True)
row.label(text=f"{StructuralLoadsData.data['total_loads']} Structural Loads Found", icon="ANIM_DATA")
@@ -513,16 +559,25 @@ class BIM_PT_structural_loads(Panel):
class BIM_UL_structural_loads(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMStructuralProperties,
item: StructuralLoad,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.number_of_inverse_references})")
row.label(text=StructuralLoadsData.data["load_classes"][item.ifc_definition_id])
if context.scene.BIMStructuralProperties.active_structural_load_id == item.ifc_definition_id:
if data.active_structural_load_id == item.ifc_definition_id:
row.operator("bim.edit_structural_load", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_structural_load", text="", icon="CANCEL")
elif context.scene.BIMStructuralProperties.active_structural_load_id:
elif data.active_structural_load_id:
op = row.operator("bim.remove_structural_load", text="", icon="X")
op.structural_load = item.ifc_definition_id
else:
@@ -549,7 +604,7 @@ class BIM_PT_boundary_conditions(Panel):
if not BoundaryConditionsData.is_loaded:
BoundaryConditionsData.load()
self.props = context.scene.BIMStructuralProperties
self.props = tool.Structural.get_structural_props()
row = self.layout.row(align=True)
row.label(
@@ -587,16 +642,25 @@ class BIM_PT_boundary_conditions(Panel):
class BIM_UL_boundary_conditions(UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
def draw_item(
self,
context,
layout: bpy.types.UILayout,
data: BIMStructuralProperties,
item: BoundaryCondition,
icon,
active_data,
active_propname,
):
if item:
row = layout.row(align=True)
row.label(text=f"{item.name} ({item.number_of_inverse_references})")
row.label(text=BoundaryConditionsData.data["condition_classes"][item.ifc_definition_id])
if context.scene.BIMStructuralProperties.active_boundary_condition_id == item.ifc_definition_id:
if data.active_boundary_condition_id == item.ifc_definition_id:
row.operator("bim.edit_boundary_condition", text="", icon="CHECKMARK")
row.operator("bim.disable_editing_boundary_condition", text="", icon="CANCEL")
elif context.scene.BIMStructuralProperties.active_boundary_condition_id:
elif data.active_boundary_condition_id:
op = row.operator("bim.remove_boundary_condition", text="", icon="X")
op.boundary_condition = item.ifc_definition_id
else:
@@ -54,7 +54,7 @@ class StructuralToolUI:
@classmethod
def draw(cls, context, layout):
cls.layout = layout
# cls.props = context.scene.BIMStructuralProperties
cls.props = tool.Structural.get_structural_props()
row = cls.layout.row(align=True)
if not tool.Ifc.get():
@@ -99,12 +99,12 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
return operator.description or ""
def _execute(self, context):
# self.props = context.scene.BIMStructuralProperties
# self.props = tool.Structural.get_structural_props()
getattr(self, f"hotkey_{self.hotkey}")()
def invoke(self, context, event):
# https://blender.stackexchange.com/questions/276035/how-do-i-make-operators-remember-their-property-values-when-called-from-a-hotkey
# self.props = context.scene.BIMStructuralProperties
# self.props = tool.Structural.get_structural_props()
return self.execute(context)
def draw(self, context):
+11 -2
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import bonsai.core.tool
import bonsai.bim.schema
@@ -24,12 +25,19 @@ import ifcopenshell
import ifcopenshell.util.unit
import ifcopenshell.util.element
from mathutils import Vector
from typing import Optional, Union, Literal
from typing import Optional, Union, Literal, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.qto.prop import BIMQtoProperties
QuantityTypes = Literal["Q_LENGTH", "Q_AREA", "Q_VOLUME"]
class Qto(bonsai.core.tool.Qto):
@classmethod
def get_qto_props(cls) -> BIMQtoProperties:
return bpy.context.scene.BIMQtoProperties
@classmethod
def get_radius_of_selected_vertices(cls, obj: bpy.types.Object) -> float:
selected_verts = [v.co for v in obj.data.vertices if v.select]
@@ -41,7 +49,8 @@ class Qto(bonsai.core.tool.Qto):
@classmethod
def set_qto_result(cls, result: float) -> None:
bpy.context.scene.BIMQtoProperties.qto_result = str(round(result, 3))
props = cls.get_qto_props()
props.qto_result = str(round(result, 3))
@classmethod
def get_rounded_value(cls, new_quantity: float) -> float:
+27 -10
View File
@@ -16,6 +16,7 @@
# You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
import bpy
import ifcopenshell
import ifcopenshell.util.representation
@@ -23,30 +24,46 @@ import json
import bonsai.bim.helper
import bonsai.core.tool
import bonsai.tool as tool
from typing import Union, Any
from typing import Union, Any, TYPE_CHECKING
if TYPE_CHECKING:
from bonsai.bim.module.structural.prop import BIMStructuralProperties, BIMObjectStructuralProperties
class Structural(bonsai.core.tool.Structural):
@classmethod
def get_structural_props(cls) -> BIMStructuralProperties:
return bpy.context.scene.BIMStructuralProperties
@classmethod
def get_object_structural_props(cls, obj: bpy.types.Object) -> BIMObjectStructuralProperties:
return obj.BIMStructuralProperties
@classmethod
def disable_editing_structural_analysis_model(cls) -> None:
bpy.context.scene.BIMStructuralProperties.active_structural_analysis_model_id = 0
props = cls.get_structural_props()
props.active_structural_analysis_model_id = 0
@classmethod
def disable_structural_analysis_model_editing_ui(cls) -> None:
bpy.context.scene.BIMStructuralProperties.is_editing = False
props = cls.get_structural_props()
props.is_editing = False
@classmethod
def enable_editing_structural_analysis_model(cls, model: Union[int, None]) -> None:
if model:
bpy.context.scene.BIMStructuralProperties.active_structural_analysis_model_id = model
props = cls.get_structural_props()
props.active_structural_analysis_model_id = model
@classmethod
def enable_structural_analysis_model_editing_ui(cls) -> None:
bpy.context.scene.BIMStructuralProperties.is_editing = True
props = cls.get_structural_props()
props.is_editing = True
@classmethod
def enabled_structural_analysis_model_editing_ui(cls) -> bool:
return bpy.context.scene.BIMStructuralProperties.is_editing
props = cls.get_structural_props()
return props.is_editing
@classmethod
def ensure_representation_contexts(cls) -> None:
@@ -71,7 +88,7 @@ class Structural(bonsai.core.tool.Structural):
@classmethod
def get_active_structural_analysis_model(cls) -> ifcopenshell.entity_instance:
props = bpy.context.scene.BIMStructuralProperties
props = cls.get_structural_props()
model = tool.Ifc.get().by_id(props.active_structural_analysis_model_id)
return model
@@ -120,13 +137,13 @@ class Structural(bonsai.core.tool.Structural):
@classmethod
def get_structural_analysis_model_attributes(cls) -> dict[str, Any]:
props = bpy.context.scene.BIMStructuralProperties
props = cls.get_structural_props()
attributes = bonsai.bim.helper.export_attributes(props.structural_analysis_model_attributes)
return attributes
@classmethod
def load_structural_analysis_model_attributes(cls, data: dict[str, Any]) -> None:
props = bpy.context.scene.BIMStructuralProperties
props = cls.get_structural_props()
props.structural_analysis_model_attributes.clear()
schema = tool.Ifc.schema()
for attribute in schema.declaration_by_name("IfcStructuralAnalysisModel").all_attributes():
@@ -149,7 +166,7 @@ class Structural(bonsai.core.tool.Structural):
@classmethod
def load_structural_analysis_models(cls) -> None:
models = tool.Structural.get_ifc_structural_analysis_models()
props = bpy.context.scene.BIMStructuralProperties
props = cls.get_structural_props()
props.structural_analysis_models.clear()
for ifc_definition_id, model in models.items():
new = props.structural_analysis_models.add()
+2 -1
View File
@@ -42,7 +42,8 @@ class TestGetRadiusOfSelectedVertices(test.bim.bootstrap.NewFile):
class TestSetQtoResult(test.bim.bootstrap.NewFile):
def test_run(self):
subject.set_qto_result(123.4567)
assert bpy.context.scene.BIMQtoProperties.qto_result == "123.457"
props = tool.Qto.get_qto_props()
assert props.qto_result == "123.457"
class TestGetRoundedValue(test.bim.bootstrap.NewFile):
+34 -20
View File
@@ -30,11 +30,16 @@ import ifcopenshell.util.shape
import ifcopenshell.util.representation
import ifcopenshell.util.type
import multiprocessing
from collections import namedtuple, defaultdict
from typing import Any, Literal, get_args, Union, Iterable
from collections import defaultdict
from typing import Any, Literal, get_args, Union, Iterable, NamedTuple
class Function(NamedTuple):
measure: str
name: str
description: str
Function = namedtuple("Function", ["measure", "name", "description"])
RULE_SET = Literal[
"IFC4QtoBaseQuantities",
"IFC4QtoBaseQuantitiesBlender",
@@ -161,7 +166,23 @@ class IteratorForTypes:
return True
class IfcOpenShell:
class QtoCalculator:
"""Abstract class for Qto calculators."""
functions: dict[str, Function]
@classmethod
def calculate(
cls,
ifc_file: ifcopenshell.file,
elements: set[ifcopenshell.entity_instance],
qtos: dict[str, dict[str, Union[str, None]]],
results: ResultsDict,
) -> None:
raise NotImplementedError
class IfcOpenShell(QtoCalculator):
"""Calculates Model body context geometry using the default IfcOpenShell
iterator on triangulation elements."""
@@ -221,13 +242,7 @@ class IfcOpenShell:
functions[f"net_{k}"] = Function(v.measure, f"Net {v.name}", v.description)
@classmethod
def calculate(
cls,
ifc_file: ifcopenshell.file,
elements: set[ifcopenshell.entity_instance],
qtos: dict[str, dict[str, Union[str, None]]],
results: ResultsDict,
) -> None:
def calculate(cls, ifc_file, elements, qtos, results):
formula_functions: dict[str, types.FunctionType] = {}
cls.gross_settings = ifcopenshell.geom.settings()
@@ -327,9 +342,10 @@ class IfcOpenShell:
return max([x, y, z])
class Blender:
class Blender(QtoCalculator):
"""Calculates geometry based on currently loaded Blender objects."""
# Implementations are located in bonsai.bim.module.qto.calculator.
functions = {
# IfcLengthMeasure
"get_covering_width": Function("IfcLengthMeasure", "Covering Width", ""),
@@ -372,13 +388,8 @@ class Blender:
"get_net_weight": Function("IfcMassMeasure", "Net Weight", ""),
}
@staticmethod
def calculate(
ifc_file: ifcopenshell.file,
elements: set[ifcopenshell.entity_instance],
qtos: dict[str, dict[str, Union[str, None]]],
results: ResultsDict,
) -> None:
@classmethod
def calculate(cls, ifc_file, elements, qtos, results):
import bonsai.tool as tool
import bonsai.bim.module.qto.calculator as calculator
@@ -406,4 +417,7 @@ class Blender:
results[element] = element_results
calculators = {"Blender": Blender, "IfcOpenShell": IfcOpenShell}
calculators: dict[str, type[QtoCalculator]] = {
"Blender": Blender,
"IfcOpenShell": IfcOpenShell,
}
@@ -64,13 +64,10 @@ def assign_object(
:param products: The list of parts of the aggregate, typically of IfcElement or
IfcSpatialStructureElement subclass
:type product: list[ifcopenshell.entity_instance]
:param relating_object: The whole of the aggregate, typically an
IfcElement or IfcSpatialStructureElement subclass
:type relating_object: ifcopenshell.entity_instance
:return: The IfcRelAggregate relationship instance
or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -86,16 +83,10 @@ def assign_object(
# The site has a building
ifcopenshell.api.aggregate.assign_object(model, products=[subelement], relating_object=element)
"""
settings = {
"products": products,
"relating_object": relating_object,
}
if not settings["products"]:
if not products:
return
products = set(settings["products"])
relating_object = settings["relating_object"]
products_set = set(products)
is_decomposed_by = next((i for i in relating_object.IsDecomposedBy if i.is_a("IfcRelAggregates")), None)
previous_aggregates_rels: set[ifcopenshell.entity_instance] = set()
@@ -103,7 +94,7 @@ def assign_object(
products_with_aggregates: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for product in products:
for product in products_set:
product_rel = next(iter(product.Decomposes), None)
if product_rel is None:
@@ -129,7 +120,7 @@ def assign_object(
# unassign elements from previous aggregates
for decomposes in previous_aggregates_rels:
related_objects = set(decomposes.RelatedObjects) - products
related_objects = set(decomposes.RelatedObjects) - products_set
if related_objects:
decomposes.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, **{"element": decomposes})
@@ -141,7 +132,7 @@ def assign_object(
# assign elements to a new aggregate
if is_decomposed_by:
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products)
is_decomposed_by.RelatedObjects = list(set(is_decomposed_by.RelatedObjects) | products_set)
ifcopenshell.api.owner.update_owner_history(file, **{"element": is_decomposed_by})
else:
is_decomposed_by = file.create_entity(
@@ -149,7 +140,7 @@ def assign_object(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": list(products),
"RelatedObjects": list(products_set),
"RelatingObject": relating_object,
}
)
@@ -23,9 +23,7 @@ def copy_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instanc
"""Copies a space boundary
:param boundary: The IfcRelSpaceBoundary you want to copy.
:type boundary: ifcopenshell.entity_instance
:return: Duplicate of the IfcRelSpaceBoundary
:rtype: ifcopenshell.entity_instance
Example:
@@ -36,9 +34,7 @@ def copy_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_instanc
# And now we have two
boundary_copy = ifcopenshell.api.boundary.copy_boundary(model, boundary=boundary)
"""
settings = {"boundary": boundary}
result = ifcopenshell.util.element.copy(file, settings["boundary"])
result = ifcopenshell.util.element.copy(file, boundary)
if result.ConnectionGeometry:
result.ConnectionGeometry = ifcopenshell.util.element.copy_deep(file, result.ConnectionGeometry)
return result
@@ -27,9 +27,7 @@ def remove_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_insta
boundary and its connection geometry is removed.
:param boundary: The IfcRelSpaceBoundary you want to remove.
:type boundary: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -40,13 +38,11 @@ def remove_boundary(file: ifcopenshell.file, boundary: ifcopenshell.entity_insta
# Let's remove it!
ifcopenshell.api.boundary.remove_boundary(model, boundary=boundary)
"""
settings = {"boundary": boundary}
geometry = settings["boundary"].ConnectionGeometry
geometry = boundary.ConnectionGeometry
if geometry:
settings["boundary"].ConnectionGeometry = None
boundary.ConnectionGeometry = None
ifcopenshell.util.element.remove_deep2(file, geometry)
history = settings["boundary"].OwnerHistory
file.remove(settings["boundary"])
history = boundary.OwnerHistory
file.remove(boundary)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -28,9 +28,7 @@ def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance)
to meet the objective of the constraint.
:param objective: The IfcObjective that this metric is a benchmark of.
:type objective: ifcopenshell.entity_instance
:return: The newly created IfcMetric entity
:rtype: ifcopenshell.entity_instance
Example:
@@ -40,10 +38,6 @@ def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance)
metric = ifcopenshell.api.constraint.add_metric(model,
objective=objective)
"""
settings = {
"objective": objective,
}
metric = file.create_entity(
"IfcMetric",
**{
@@ -52,8 +46,9 @@ def add_metric(file: ifcopenshell.file, objective: ifcopenshell.entity_instance)
"Benchmark": "EQUALTO",
},
)
if settings["objective"]:
benchmark_values = list(settings["objective"].BenchmarkValues or [])
if objective:
benchmark_values: list[ifcopenshell.entity_instance]
benchmark_values = list(objective.BenchmarkValues or [])
benchmark_values.append(metric)
settings["objective"].BenchmarkValues = benchmark_values
objective.BenchmarkValues = benchmark_values
return metric
@@ -29,7 +29,6 @@ def add_objective(file: ifcopenshell.file) -> ifcopenshell.entity_instance:
quantities. See ifcopenshell.api.constraint.add_metric for more information.
:return: The newly created IfcObjective entity
:rtype: ifcopenshell.entity_instance
Example:
@@ -42,8 +41,6 @@ def add_objective(file: ifcopenshell.file) -> ifcopenshell.entity_instance:
# Note: the objective right now is purely qualitative and for
# information purposes. You may wish to add quantiative metrics.
"""
settings = {}
return file.create_entity(
"IfcObjective", **{"Name": "Unnamed", "ConstraintGrade": "NOTDEFINED", "ObjectiveQualifier": "NOTDEFINED"}
)
@@ -41,9 +41,7 @@ def remove_constraint(file: ifcopenshell.file, constraint: ifcopenshell.entity_i
ifcopenshell.api.constraint.remove_constraint(model,
constraint=objective)
"""
settings = {"constraint": constraint}
file.remove(settings["constraint"])
file.remove(constraint)
for rel in file.by_type("IfcRelAssociatesConstraint"):
if not rel.RelatingConstraint:
history = rel.OwnerHistory
@@ -29,9 +29,7 @@ def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instanc
removed. If a context is removed, then any subcontexts are also removed.
:param context: The IfcGeometricRepresentationContext entity to remove
:type context: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -46,22 +44,20 @@ def remove_context(file: ifcopenshell.file, context: ifcopenshell.entity_instanc
# Let's just get rid of it completely
ifcopenshell.api.context.remove_context(model, context=body)
"""
settings = {"context": context}
for subcontext in settings["context"].HasSubContexts:
for subcontext in context.HasSubContexts:
ifcopenshell.api.context.remove_context(file, context=subcontext)
if getattr(settings["context"], "ParentContext", None):
new = settings["context"].ParentContext
for inverse in file.get_inverse(settings["context"]):
if getattr(context, "ParentContext", None):
new = context.ParentContext
for inverse in file.get_inverse(context):
if inverse.is_a("IfcCoordinateOperation"):
inverse.SourceCRS = inverse.TargetCRS
ifcopenshell.util.element.remove_deep(file, inverse)
else:
ifcopenshell.util.element.replace_attribute(inverse, settings["context"], new)
file.remove(settings["context"])
ifcopenshell.util.element.replace_attribute(inverse, context, new)
file.remove(context)
else:
representations_in_context = settings["context"].RepresentationsInContext
file.remove(settings["context"])
representations_in_context = context.RepresentationsInContext
file.remove(context)
for element in representations_in_context:
ifcopenshell.api.geometry.remove_representation(file, representation=element)
@@ -42,12 +42,9 @@ def assign_control(
:param relating_control: The IfcControl entity that is creating the
control or constraint
:type relating_control: ifcopenshell.entity_instance
:param related_object: The IfcObjectDefinition that is being controlled
:type related_object: ifcopenshell.entity_instance
:return: The newly created IfcRelAssignsToControl. If relationship already
existed before and wasn't changed then returns None.
:rtype: ifcopenshell.entity_instance, None
Example:
@@ -72,25 +69,20 @@ def assign_control(
ifcopenshell.api.control.assign_control(model,
relating_control=cost_item, related_object=wall)
"""
settings = {
"relating_control": relating_control,
"related_object": related_object,
}
if settings["related_object"].HasAssignments:
for assignment in settings["related_object"].HasAssignments:
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl == settings["relating_control"]:
if related_object.HasAssignments:
for assignment in related_object.HasAssignments:
if assignment.is_a("IfcRelAssignsToControl") and assignment.RelatingControl == relating_control:
return
controls = None
if settings["relating_control"].Controls:
controls = settings["relating_control"].Controls[0]
if relating_control.Controls:
controls = relating_control.Controls[0]
if controls:
if settings["related_object"] in controls.RelatedObjects:
if related_object in controls.RelatedObjects:
return
related_objects = set(controls.RelatedObjects)
related_objects.add(settings["related_object"])
related_objects.add(related_object)
controls.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, **{"element": controls})
else:
@@ -99,8 +91,8 @@ def assign_control(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingControl": settings["relating_control"],
"RelatedObjects": [related_object],
"RelatingControl": relating_control,
},
)
return controls
@@ -31,12 +31,9 @@ def unassign_control(
:param relating_control: The IfcControl entity that is creating the
control or constraint
:type relating_control: ifcopenshell.entity_instance
:param related_object: The IfcObjectDefinition that is being controlled
:type related_object: ifcopenshell.entity_instance
:return: If the control still is related to other objects, the
IfcRelAssignsToControl is returned, otherwise None.
:rtype: ifcopenshell.entity_instance, None
Example:
@@ -54,14 +51,8 @@ def unassign_control(
ifcopenshell.api.control.unassign_control(model,
relating_control=cost_item, related_object=wall)
"""
settings = {
"relating_control": relating_control,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != settings["relating_control"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToControl") or rel.RelatingControl != relating_control:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -70,7 +61,7 @@ def unassign_control(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
return rel
@@ -54,12 +54,9 @@ def add_cost_item_quantity(
using another API call.
:param cost_item: The IfcCostItem to add the quantity to
:type cost_item: ifcopenshell.entity_instance
:param ifc_class: The type of quantity to add
:type ifc_class: str, optional
:return: The newly created quantity entity, chosen from the ifc_class
parameter
:rtype: ifcopenshell.entity_instance
Example:
@@ -76,20 +73,18 @@ def add_cost_item_quantity(
ifcopenshell.api.cost.add_cost_item_quantity(model,
cost_item=item, ifc_class="IfcQuantityCount")
"""
settings = {"cost_item": cost_item, "ifc_class": ifc_class}
quantity = file.create_entity(settings["ifc_class"], Name="Unnamed")
quantity = file.create_entity(ifc_class, Name="Unnamed")
# 3 IfcPhysicalSimpleQuantity Value
# This is a bold assumption
# https://forums.buildingsmart.org/t/how-does-a-cost-item-know-that-it-is-counting-a-controlled-product/3564
if settings["ifc_class"] == "IfcQuantityCount":
if ifc_class == "IfcQuantityCount":
count = 0
for rel in settings["cost_item"].Controls:
for rel in cost_item.Controls:
count += len(rel.RelatedObjects)
quantity[3] = count
else:
quantity[3] = 0.0
quantities = list(settings["cost_item"].CostQuantities or [])
quantities = list(cost_item.CostQuantities or [])
quantities.append(quantity)
settings["cost_item"].CostQuantities = quantities
cost_item.CostQuantities = quantities
return quantity
@@ -39,13 +39,10 @@ def add_cost_schedule(
managing any cost items.
:param name: The name of the cost schedule.
:type name: str, optional
:param predefined_type: The predefined type of the cost schedule, chosen
from a valid type in the IFC documentation for
IfcCostScheduleTypeEnum
:type predefined_type: str, optional
:return: The newly created IfcCostSchedule entity
:rtype: ifcopenshell.entity_instance
Example:
@@ -55,13 +52,11 @@ def add_cost_schedule(
# Now that we have a cost schedule, we may add cost items to it
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
"""
settings = {"name": name, "predefined_type": predefined_type}
cost_schedule = ifcopenshell.api.root.create_entity(
file,
ifc_class="IfcCostSchedule",
predefined_type=settings["predefined_type"],
name=settings["name"],
predefined_type=predefined_type,
name=name,
)
if file.schema == "IFC2X3":
cost_schedule.UpdateDate = createIfcDateAndTime(file, datetime.now())
@@ -46,9 +46,7 @@ def add_cost_value(file: ifcopenshell.file, parent: ifcopenshell.entity_instance
:param parent: A parent IfcCostItem, if specifying a price directly to a
cost item, or a top-level price component. Alternatively, this can
be set to a IfcCostValue, if specifying price subcomponents.
:type parent: ifcopenshell.entity_instance
:return: The newly created IfcCostValue
:rtype: ifcopenshell.entity_instance
Example:
@@ -91,19 +89,17 @@ def add_cost_value(file: ifcopenshell.file, parent: ifcopenshell.entity_instance
ifcopenshell.api.cost.edit_cost_value(model,
cost_value=subvalue2, attributes={"AppliedValue": 3.0})
"""
settings = {"parent": parent}
value = file.create_entity("IfcCostValue")
if settings["parent"].is_a("IfcCostItem"):
values = list(settings["parent"].CostValues or [])
if parent.is_a("IfcCostItem"):
values = list(parent.CostValues or [])
values.append(value)
settings["parent"].CostValues = values
elif settings["parent"].is_a("IfcConstructionResource"):
values = list(settings["parent"].BaseCosts or [])
parent.CostValues = values
elif parent.is_a("IfcConstructionResource"):
values = list(parent.BaseCosts or [])
values.append(value)
settings["parent"].BaseCosts = values
elif settings["parent"].is_a("IfcCostValue"):
values = list(settings["parent"].Components or [])
parent.BaseCosts = values
elif parent.is_a("IfcCostValue"):
values = list(parent.Components or [])
values.append(value)
settings["parent"].Components = values
parent.Components = values
return value
@@ -36,11 +36,8 @@ def assign_cost_value(
rates as a "template" to quickly populate your rates from.
:param cost_item: The IfcCostItem that you want to copy the values to
:type cost_item: ifcopenshell.entity_instance
:param cost_rate: The IfcCostItem that you want to copy the values from
:type cost_rate: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -61,16 +58,14 @@ def assign_cost_value(
# Now the cost item has the same rate as the one from the schedule of rate's item
ifcopenshell.api.cost.assign_cost_value(model, cost_item=item, cost_rate=rate)
"""
settings = {"cost_item": cost_item, "cost_rate": cost_rate}
if settings["cost_item"].CostValues:
if cost_item.CostValues:
[
ifcopenshell.api.cost.remove_cost_value(
file,
parent=settings["cost_item"],
parent=cost_item,
cost_value=cost_value,
)
for cost_value in settings["cost_item"].CostValues
for cost_value in cost_item.CostValues
]
# This is an assumption, and not part of the official IFC documentation
settings["cost_item"].CostValues = settings["cost_rate"].CostValues
cost_item.CostValues = cost_rate.CostValues
@@ -83,13 +83,11 @@ def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcop
# (42 * 200) + 50000 = 58400 is our calculated cost
ifcopenshell.api.cost.calculate_cost_item_resource_value(model, cost_item=item)
"""
settings = {"cost_item": cost_item}
for cost_value in settings["cost_item"].CostValues or []:
ifcopenshell.api.cost.remove_cost_value(file, parent=settings["cost_item"], cost_value=cost_value)
for cost_value in cost_item.CostValues or []:
ifcopenshell.api.cost.remove_cost_value(file, parent=cost_item, cost_value=cost_value)
resources = []
for rel in settings["cost_item"].Controls or []:
for rel in cost_item.Controls or []:
for related_object in rel.RelatedObjects:
if related_object.is_a("IfcConstructionResource"):
resources.append(related_object)
@@ -112,6 +110,6 @@ def calculate_cost_item_resource_value(file: ifcopenshell.file, cost_item: ifcop
if unit and "day" in unit:
quantity = quantity / 8 # Assume 8 hour working day - TODO implement resource calendar
formula = "{}*{}".format(cost, quantity)
cost_value = ifcopenshell.api.cost.add_cost_value(file, parent=settings["cost_item"])
cost_value = ifcopenshell.api.cost.add_cost_value(file, parent=cost_item)
cost_value.Name = resource.Name
ifcopenshell.api.cost.edit_cost_value_formula(file, cost_value=cost_value, formula=formula)
@@ -29,9 +29,7 @@ def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_ins
retained.
:param cost_item: The IfcCostItem entity you want to remove
:type cost_item: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -41,15 +39,13 @@ def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_ins
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.remove_cost_item(model, cost_item=item)
"""
settings = {"cost_item": cost_item}
# TODO: do a deep purge
for inverse in file.get_inverse(settings["cost_item"]):
for inverse in file.get_inverse(cost_item):
if inverse.is_a("IfcRelNests"):
if inverse.RelatingObject == settings["cost_item"]:
if inverse.RelatingObject == cost_item:
for related_object in inverse.RelatedObjects:
ifcopenshell.api.cost.remove_cost_item(file, cost_item=related_object)
elif inverse.RelatedObjects == (settings["cost_item"],):
elif inverse.RelatedObjects == (cost_item,):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
@@ -59,7 +55,7 @@ def remove_cost_item(file: ifcopenshell.file, cost_item: ifcopenshell.entity_ins
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["cost_item"].OwnerHistory
file.remove(settings["cost_item"])
history = cost_item.OwnerHistory
file.remove(cost_item)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -40,17 +40,15 @@ def remove_cost_schedule(file: ifcopenshell.file, cost_schedule: ifcopenshell.en
item = ifcopenshell.api.cost.add_cost_item(model, cost_schedule=schedule)
ifcopenshell.api.cost.remove_cost_schedule(model, cost_schedule=schedule)
"""
settings = {"cost_schedule": cost_schedule}
# TODO: do a deep purge
for inverse in file.get_inverse(settings["cost_schedule"]):
for inverse in file.get_inverse(cost_schedule):
if inverse.is_a("IfcRelAssignsToControl"):
[
ifcopenshell.api.cost.remove_cost_item(file, cost_item=related_object)
for related_object in inverse.RelatedObjects
if related_object.is_a("IfcCostItem")
]
history = settings["cost_schedule"].OwnerHistory
file.remove(settings["cost_schedule"])
history = cost_schedule.OwnerHistory
file.remove(cost_schedule)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -38,9 +38,7 @@ def add_reference(file: ifcopenshell.file, information: ifcopenshell.entity_inst
:param information: The IfcDocumentInformation that the reference will
be created for
:type information: ifcopenshell.entity_instance
:return: The newly created IfcDocumentReference entity
:rtype: ifcopenshell.entity_instance
Example:
@@ -63,13 +61,11 @@ def add_reference(file: ifcopenshell.file, information: ifcopenshell.entity_inst
ifcopenshell.api.document.edit_reference(model,
reference=reference2, attributes={"Identification": "2.1.15"})
"""
settings = {"information": information}
if file.schema == "IFC2X3":
reference = file.create_entity("IfcDocumentReference", ItemReference="X")
if settings["information"]:
references = list(settings["information"].DocumentReferences or [])
if information:
references = list(information.DocumentReferences or [])
references.append(reference)
settings["information"].DocumentReferences = references
information.DocumentReferences = references
return reference
return file.create_entity("IfcDocumentReference", ReferencedDocument=settings["information"], Identification="X")
return file.create_entity("IfcDocumentReference", ReferencedDocument=information, Identification="X")
@@ -30,12 +30,9 @@ def unassign_document(
:param product: The list of objects that the document reference or information is
related to.
:type product: list[ifcopenshell.entity_instance]
:param document: The IfcDocumentReference (typically) or in rare cases
the IfcDocumentInformation that is associated with the product
:type document: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -54,27 +51,21 @@ def unassign_document(
# Now let's change our mind and remove the association
ifcopenshell.api.document.unassign_document(model, products=[storey], document=reference)
"""
settings = {
"products": products,
"document": document,
}
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
# NOTE: reuses code from `library.un assign_reference`
reference_rels: set[ifcopenshell.entity_instance] = set()
products = set(settings["products"])
for product in products:
products_set = set(products)
for product in products_set:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == settings["document"]
rel for rel in reference_rels if rel.is_a("IfcRelAssociatesDocument") and rel.RelatingDocument == document
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
related_objects = set(rel.RelatedObjects) - products_set
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -46,12 +46,9 @@ def assign_product(
in 3D.
:param relating_product: The IfcProduct the object is related to
:type relating_product: ifcopenshell.entity_instance
:param related_object: The object (typically IfcAnnotation) that the
product is related to
:type related_object: ifcopenshell.entity_instance
:return: The created IfcRelAssignsToProduct relationship
:rtype: ifcopenshell.entity_instance
Example:
@@ -62,42 +59,36 @@ def assign_product(
ifcopenshell.api.drawing.assign_product(model,
relating_product=furniture, related_object=annotation)
"""
settings = {
"relating_product": relating_product,
"related_object": related_object,
}
is_grid_axis = settings["relating_product"].is_a("IfcGridAxis")
is_grid_axis = relating_product.is_a("IfcGridAxis")
if is_grid_axis:
if settings["related_object"].HasAssignments:
for rel in settings["related_object"].HasAssignments:
if rel.is_a("IfcRelAssignsToProduct") and rel.Name == settings["relating_product"].AxisTag:
if related_object.HasAssignments:
for rel in related_object.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct") and rel.Name == relating_product.AxisTag:
return
elif settings["related_object"].HasAssignments:
for rel in settings["related_object"].HasAssignments:
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct == settings["relating_product"]:
elif related_object.HasAssignments:
for rel in related_object.HasAssignments:
if rel.is_a("IfcRelAssignsToProduct") and rel.RelatingProduct == relating_product:
return
referenced_by = None
if is_grid_axis:
axis = settings["relating_product"]
axis = relating_product
grid = None
for attribute in ("PartOfW", "PartOfV", "PartOfU"):
if getattr(axis, attribute, None):
grid = getattr(axis, attribute)[0]
settings["relating_product"] = grid
for rel in grid.ReferencedBy:
if rel.Name == axis.AxisTag:
referenced_by = rel
break
elif settings["relating_product"].ReferencedBy:
referenced_by = settings["relating_product"].ReferencedBy[0]
elif relating_product.ReferencedBy:
referenced_by = relating_product.ReferencedBy[0]
if referenced_by:
related_objects = list(referenced_by.RelatedObjects)
related_objects.append(settings["related_object"])
related_objects.append(related_object)
referenced_by.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": referenced_by})
else:
@@ -106,8 +97,8 @@ def assign_product(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingProduct": settings["relating_product"],
"RelatedObjects": [related_object],
"RelatingProduct": relating_product,
},
)
@@ -34,12 +34,9 @@ def unassign_product(
object later or leave the annotation as a "dumb" annotation.
:param relating_product: The IfcProduct the object is related to
:type relating_product: ifcopenshell.entity_instance
:param related_object: The object (typically IfcAnnotation) that the
product is related to
:type related_object: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -54,13 +51,8 @@ def unassign_product(
ifcopenshell.api.drawing.unassign_product(model,
relating_product=furniture, related_object=annotation)
"""
settings = {
"relating_product": relating_product,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != settings["relating_product"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != relating_product:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -69,6 +61,6 @@ def unassign_product(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, element=rel)
@@ -33,11 +33,8 @@ def add_filling(
filled.
:param opening: The IfcOpeningElement to fill with the element.
:type opening: ifcopenshell.entity_instance
:param element: The IfcElement to be inserted into the opening.
:type element: ifcopenshell.entity_instance
:return: The new IfcRelFillsElement relationship
:rtype: ifcopenshell.entity_instance
Example:
@@ -102,12 +99,10 @@ def add_filling(
# The door will now fill the opening.
ifcopenshell.api.feature.add_filling(model, opening=opening, element=door)
"""
settings = {"opening": opening, "element": element}
fills_voids = settings["element"].FillsVoids
fills_voids = element.FillsVoids
if fills_voids:
if fills_voids[0].RelatingOpeningElement == settings["opening"]:
if fills_voids[0].RelatingOpeningElement == opening:
return fills_voids[0]
history = fills_voids[0].OwnerHistory
file.remove(fills_voids[0])
@@ -117,6 +112,6 @@ def add_filling(
return file.create_entity(
"IfcRelFillsElement",
GlobalId=ifcopenshell.guid.new(),
RelatingOpeningElement=settings["opening"],
RelatedBuildingElement=settings["element"],
RelatingOpeningElement=opening,
RelatedBuildingElement=element,
)
@@ -28,9 +28,7 @@ def remove_filling(file: ifcopenshell.file, element: ifcopenshell.entity_instanc
fills the opening.
:param element: The element filling an opening.
:type element: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -52,10 +50,8 @@ def remove_filling(file: ifcopenshell.file, element: ifcopenshell.entity_instanc
# Not anymore!
ifcopenshell.api.feature.remove_filling(model, element=door)
"""
settings = {"element": element}
for rel in file.by_type("IfcRelFillsElement"):
if rel.RelatedBuildingElement == settings["element"]:
if rel.RelatedBuildingElement == element:
history = rel.OwnerHistory
file.remove(rel)
if history:
@@ -56,13 +56,10 @@ def add_axis_representation(
:param context: The IfcGeometricRepresentationContext that the
representation is part of. This must be either a
Model/Axis/GRAPH_VIEW (3D) or Plan/Axis/GRAPH_VIEW (2D).
:type context: ifcopenshell.entity_instance
:param axis: The axis, as a list of two coordinates, the coordinates
being either a list of 2 or 3 float coordinates depending on whether
the axis is 2D or 3D.
:type axis: list[list[float]]
:return: The newly created IfcShapeRepresentation entity
:rtype: ifcopenshell.entity_instance
Example:
@@ -29,20 +29,14 @@ def connect_element(
related_element: ifcopenshell.entity_instance,
description: Optional[str] = None,
) -> ifcopenshell.entity_instance:
settings = {
"relating_element": relating_element,
"related_element": related_element,
"description": description,
}
incompatible_connections = []
for rel in settings["relating_element"].ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == settings["related_element"]:
for rel in relating_element.ConnectedFrom:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatingElement == related_element:
incompatible_connections.append(rel)
for rel in settings["related_element"].ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["relating_element"]:
for rel in related_element.ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == relating_element:
incompatible_connections.append(rel)
if incompatible_connections:
@@ -52,15 +46,15 @@ def connect_element(
if history:
ifcopenshell.util.element.remove_deep2(file, history)
for rel in settings["relating_element"].ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == settings["related_element"]:
rel.Description = settings["description"]
for rel in relating_element.ConnectedTo:
if rel.is_a() == "IfcRelConnectsElements" and rel.RelatedElement == related_element:
rel.Description = description
return rel
return file.createIfcRelConnectsElements(
ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
Description=settings["description"],
RelatingElement=settings["relating_element"],
RelatedElement=settings["related_element"],
Description=description,
RelatingElement=relating_element,
RelatedElement=related_element,
)
@@ -17,7 +17,6 @@
# along with IfcOpenShell. If not, see <http://www.gnu.org/licenses/>.
import ifcopenshell
from typing import Any
def map_representation(
@@ -25,15 +24,14 @@ def map_representation(
) -> ifcopenshell.entity_instance:
usecase = Usecase()
usecase.file = file
usecase.settings = {"representation": representation}
return usecase.execute()
return usecase.execute(representation)
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self) -> ifcopenshell.entity_instance:
def execute(self, representation: ifcopenshell.entity_instance) -> ifcopenshell.entity_instance:
self.representation = representation
mapping_source = self.get_mapping_source()
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
@@ -46,15 +44,15 @@ class Usecase:
return self.file.create_entity(
"IfcShapeRepresentation",
**{
"ContextOfItems": self.settings["representation"].ContextOfItems,
"RepresentationIdentifier": self.settings["representation"].RepresentationIdentifier,
"ContextOfItems": representation.ContextOfItems,
"RepresentationIdentifier": representation.RepresentationIdentifier,
"RepresentationType": "MappedRepresentation",
"Items": [mapped_item],
}
)
def get_mapping_source(self) -> ifcopenshell.entity_instance:
for inverse in self.file.get_inverse(self.settings["representation"]):
for inverse in self.file.get_inverse(self.representation):
if inverse.is_a("IfcRepresentationMap"):
return inverse
zero = self.file.createIfcCartesianPoint((0.0, 0.0, 0.0))
@@ -62,5 +60,5 @@ class Usecase:
z_axis = self.file.createIfcDirection((0.0, 0.0, 1.0))
mapping_origin = self.file.createIfcAxis2Placement3D(zero, z_axis, x_axis)
return self.file.createIfcRepresentationMap(
MappingOrigin=mapping_origin, MappedRepresentation=self.settings["representation"]
MappingOrigin=mapping_origin, MappedRepresentation=self.representation
)
@@ -35,12 +35,9 @@ def add_group(
or structural load groups, which group together loads for structural
analysis, or inventories, which are groups of assets.
:param Name: The name of the group. Defaults to "Unnamed"
:type Name: str, optional
:param name: The name of the group. Defaults to "Unnamed"
:param description: The description of the purpose of the group.
:type description: str, optional
:return: The newly created IfcGroup
:rtype: ifcopenshell.entity_instance
Example:
@@ -48,17 +45,11 @@ def add_group(
ifcopenshell.api.group.add_group(model, name="Unit 1A")
"""
settings = {
"name": name or "Unnamed",
"description": description,
}
return file.create_entity(
"IfcGroup",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"Name": settings["name"],
"Description": settings["description"],
}
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
Name=name,
Description=description,
)
@@ -39,9 +39,7 @@ def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -
group = ifcopenshell.api.group.add_group(model, name="Unit 1A")
ifcopenshell.api.group.remove_group(model, group=group)
"""
settings = {"group": group}
for inverse_id in [i.id() for i in file.get_inverse(settings["group"])]:
for inverse_id in [i.id() for i in file.get_inverse(group)]:
try:
inverse = file.by_id(inverse_id)
except:
@@ -49,11 +47,11 @@ def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -
if inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.pset.remove_pset(
file,
product=settings["group"],
product=group,
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssignsToGroup"):
if inverse.RelatingGroup == settings["group"]:
if inverse.RelatingGroup == group:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
@@ -63,7 +61,7 @@ def remove_group(file: ifcopenshell.file, group: ifcopenshell.entity_instance) -
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["group"].OwnerHistory
file.remove(settings["group"])
history = group.OwnerHistory
file.remove(group)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -50,14 +50,10 @@ def add_reference(file: ifcopenshell.file, library: ifcopenshell.entity_instance
ifcopenshell.api.library.edit_reference(model,
reference=reference, attributes={"Identification": "http://example.org/digitaltwin#AHU01"})
"""
settings = {
"library": library,
}
if file.schema == "IFC2X3":
reference = file.createIfcLibraryReference()
references = list(settings["library"].LibraryReference or [])
references = list(library.LibraryReference or [])
references.append(reference)
settings["library"].LibraryReference = references
library.LibraryReference = references
return reference
return file.createIfcLibraryReference(ReferencedLibrary=settings["library"])
return file.createIfcLibraryReference(ReferencedLibrary=library)
@@ -33,14 +33,11 @@ def assign_reference(
detail about how references work.
:param products: The list of IfcProducts you want to associate with the reference
:type products: list[ifcopenshell.entity_instance]
:param reference: The IfcLibraryReference you want the product to be
associated with.
:type reference: ifcopenshell.entity_instance
:return: The IfcRelAssociatesLibrary relationship entity
or `None` if `products` was an empty list or all products were
already assigned to the `reference`.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -60,38 +57,33 @@ def assign_reference(
# And now assign the IFC model's AHU with its Brickschema counterpart
ifcopenshell.api.library.assign_reference(model, reference=reference, products=[ahu])
"""
settings = {
"products": products,
"reference": reference,
}
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
referenced_elements = ifcopenshell.util.element.get_referenced_elements(settings["reference"])
products: set[ifcopenshell.entity_instance] = set(settings["products"])
products = products - referenced_elements
referenced_elements = ifcopenshell.util.element.get_referenced_elements(reference)
products_set: set[ifcopenshell.entity_instance] = set(products)
products_set = products_set - referenced_elements
if not products:
if not products_set:
return
if file.schema == "IFC2X3":
rel = next(
(r for r in file.by_type("IfcRelAssociatesLibrary") if r.RelatingLibrary == settings["reference"]),
(r for r in file.by_type("IfcRelAssociatesLibrary") if r.RelatingLibrary == reference),
None,
)
else:
rel = next(iter(settings["reference"].LibraryRefForObjects), None)
rel = next(iter(reference.LibraryRefForObjects), None)
if not rel:
return file.create_entity(
"IfcRelAssociatesLibrary",
GlobalId=ifcopenshell.guid.new(),
OwnerHistory=ifcopenshell.api.owner.create_owner_history(file),
RelatedObjects=list(products),
RelatingLibrary=settings["reference"],
RelatedObjects=list(products_set),
RelatingLibrary=reference,
)
related_objects = set(rel.RelatedObjects) | products
related_objects = set(rel.RelatedObjects) | products_set
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, element=rel)
return rel
@@ -31,11 +31,8 @@ def unassign_reference(
If the product isn't assigned to the reference, nothing will happen.
:param reference: The IfcLibraryReference to unassign from
:type reference: ifcopenshell.entity_instance
:param products: A list of IfcProduct elements to unassign from the reference
:type products: list[ifcopenshell.entity_instance]
:return: None
:rtype: None
Example:
@@ -58,24 +55,19 @@ def unassign_reference(
# Let's change our mind and unassign it.
ifcopenshell.api.library.unassign_reference(model, reference=reference, products=[ahu])
"""
settings = {"reference": reference, "products": products}
# TODO: do we need to support non-ifcroot elements like we do in classification.add_reference?
reference_rels: set[ifcopenshell.entity_instance] = set()
products = set(settings["products"])
for product in products:
products_set = set(products)
for product in products_set:
reference_rels.update(product.HasAssociations)
reference_rels = {
rel
for rel in reference_rels
if rel.is_a("IfcRelAssociatesLibrary") and rel.RelatingLibrary == settings["reference"]
rel for rel in reference_rels if rel.is_a("IfcRelAssociatesLibrary") and rel.RelatingLibrary == reference
}
for rel in reference_rels:
related_objects = set(rel.RelatedObjects) - products
related_objects = set(rel.RelatedObjects) - products_set
if related_objects:
rel.RelatedObjects = list(related_objects)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -40,11 +40,8 @@ def add_list_item(
:param material_list: The IfcMaterialList the material should be added
to.
:type material_list: ifcopenshell.entity_instance
:param material: The IfcMaterial to add to the list
:type material: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -80,8 +77,6 @@ def add_list_item(
# aluminium and glass.
ifcopenshell.api.material.assign_material(model, products=[window_type], material=material_set)
"""
settings = {"material_list": material_list, "material": material}
materials = list(settings["material_list"].Materials or [])
materials.append(settings["material"])
settings["material_list"].Materials = materials
materials = list(material_list.Materials or [])
materials.append(material)
material_list.Materials = materials
@@ -58,13 +58,9 @@ def add_material(
:param name: The name of the material, typically tagged in a finishes
drawing or schedule.
:type name: str, optional
:param category: The category of the material.
:type category: str, optional
:param description: A description of the material.
:type description: str, optional
:return: The newly created IfcMaterial
:rtype: ifcopenshell.entity_instance
Example:
@@ -81,11 +77,9 @@ def add_material(
# "Style" has been specified.
ifcopenshell.api.material.assign_material(model, products=[concrete_bench], material=concrete)
"""
settings = {"name": name or "Unnamed", "category": category, "description": description}
material = file.create_entity("IfcMaterial", **{"Name": settings["name"] or "Unnamed"})
if settings["category"]:
material.Category = settings["category"]
if settings["description"]:
material.Description = settings["description"]
material = file.create_entity("IfcMaterial", **{"Name": name or "Unnamed"})
if category:
material.Category = category
if description:
material.Description = description
return material
@@ -68,14 +68,11 @@ def add_material_set(
:param name: The name of the material set, which may be purely
descriptive or annotated in drawings. Defaults to "Unnamed".
:type name: str, optional
:param set_type: What type of set you want to create, chosen from
IfcMaterialLayerSet, IfcMaterialProfileSet,
IfcMaterialConstituentSet, or IfcMaterialList. Defaults to
IfcMaterialConstituentSet.
:type set_type: str, optional
:return: The newly created material set element
:rtype: ifcopenshell.entity_instance
Example:
@@ -113,10 +110,8 @@ def add_material_set(
# Great! Let's assign our material set to our wall type.
ifcopenshell.api.material.assign_material(model, products=[wall_type], material=material_set)
"""
settings = {"name": name or "Unnamed", "set_type": set_type}
if settings["set_type"] == "IfcMaterialLayerSet":
return file.create_entity("IfcMaterialLayerSet", LayerSetName=settings["name"] or "Unnamed")
elif settings["set_type"] == "IfcMaterialList":
if set_type == "IfcMaterialLayerSet":
return file.create_entity("IfcMaterialLayerSet", LayerSetName=name or "Unnamed")
elif set_type == "IfcMaterialList":
return file.create_entity("IfcMaterialList")
return file.create_entity(settings["set_type"], Name=settings["name"] or "Unnamed")
return file.create_entity(set_type, Name=name or "Unnamed")
@@ -29,9 +29,7 @@ def remove_material(file: ifcopenshell.file, material: ifcopenshell.entity_insta
take care of this situation themselves.
:param material: The IfcMaterial entity you want to remove
:type material: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -43,10 +41,8 @@ def remove_material(file: ifcopenshell.file, material: ifcopenshell.entity_insta
# ... and remove it
ifcopenshell.api.material.remove_material(model, material=aluminium)
"""
settings = {"material": material}
inverse_elements = file.get_inverse(settings["material"])
file.remove(settings["material"])
inverse_elements = file.get_inverse(material)
file.remove(material)
# TODO: Right now, we we choose only to delete set items (e.g. a layer) but not the material set
# This can lead to invalid material sets, but we assume the user will deal with it
for inverse in inverse_elements:
@@ -29,9 +29,7 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i
:param material: The IfcMaterialLayerSet, IfcMaterialConstituentSet,
IfcMaterialProfileSet entity you want to remove.
:type material: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -55,20 +53,18 @@ def remove_material_set(file: ifcopenshell.file, material: ifcopenshell.entity_i
ifcopenshell.api.material.remove_material_set(model, material=material_set)
"""
settings = {"material": material}
inverse_elements = file.get_inverse(settings["material"])
if settings["material"].is_a("IfcMaterialLayerSet"):
set_items = settings["material"].MaterialLayers or []
elif settings["material"].is_a("IfcMaterialProfileSet"):
set_items = settings["material"].MaterialProfiles or []
elif settings["material"].is_a("IfcMaterialConstituentSet"):
set_items = settings["material"].MaterialConstituents or []
elif settings["material"].is_a("IfcMaterialList"):
inverse_elements = file.get_inverse(material)
if material.is_a("IfcMaterialLayerSet"):
set_items = material.MaterialLayers or []
elif material.is_a("IfcMaterialProfileSet"):
set_items = material.MaterialProfiles or []
elif material.is_a("IfcMaterialConstituentSet"):
set_items = material.MaterialConstituents or []
elif material.is_a("IfcMaterialList"):
set_items = []
for set_item in set_items:
file.remove(set_item)
file.remove(settings["material"])
file.remove(material)
for inverse in inverse_elements:
if inverse.is_a("IfcRelAssociatesMaterial"):
history = inverse.OwnerHistory
@@ -49,11 +49,8 @@ def add_actor(
IfcPerson if it is a sole individual, or an IfcPersonAndOrganization
if a specific person is liable within an organisation and must be
legally nominated.
:type actor: ifcopenshell.entity_instance
:param ifc_class: Either "IfcActor" or "IfcOccupant".
:type ifc_class: str, optional
:return: The newly created IfcActor or IfcOccupant
:rtype: ifcopenshell.entity_instance
Example:
@@ -67,8 +64,7 @@ def add_actor(
# Assign that organisation to a newly created actor
actor = ifcopenshell.api.owner.add_actor(model, actor=organisation)
"""
settings = {"actor": actor, "ifc_class": ifc_class or "IfcActor"}
actor = ifcopenshell.api.root.create_entity(file, ifc_class=settings["ifc_class"])
actor.TheActor = settings["actor"]
return actor
ifc_class = ifc_class or "IfcActor"
actor_ = ifcopenshell.api.root.create_entity(file, ifc_class=ifc_class)
actor_.TheActor = actor
return actor_
@@ -39,12 +39,9 @@ def add_address(
:param assigned_object: The IfcOrganization or IfcPerson the contact
address belongs to.
:type assigned_object: ifcopenshell.entity_instance
:param ifc_class: Either IfcPostalAddress or IfcTelecomAddress. Defaults
to IfcPostalAddress.
:type ifc_class: str, optional
:return: The new IfcPostalAddress or IfcTelecomAddress
:rtype: ifcopenshell.entity_instance
Example:
@@ -67,10 +64,8 @@ def add_address(
"ElectronicMailAddresses": ["bobthebuilder@example.com"],
"WWWHomePageURL": "https://thinkmoult.com"})
"""
settings = {"assigned_object": assigned_object, "ifc_class": ifc_class}
address = file.create_entity(settings["ifc_class"], "OFFICE")
addresses = list(settings["assigned_object"].Addresses) if settings["assigned_object"].Addresses else []
address = file.create_entity(ifc_class, "OFFICE")
addresses = list(assigned_object.Addresses) if assigned_object.Addresses else []
addresses.append(address)
settings["assigned_object"].Addresses = addresses
assigned_object.Addresses = addresses
return address
@@ -31,11 +31,8 @@ def add_organisation(
Sometimes used in drawing naming schemes. Otherise used as a
canonicalised way of computers to identify the organisation. Like
their stock name.
:type identification: str, optional
:param name: The legal name of the organisation
:type name: str, optional
:return: The newly created IfcOrganization
:rtype: ifcopenshell.entity_instance
Example:
@@ -44,11 +41,9 @@ def add_organisation(
organisation = ifcopenshell.api.owner.add_organisation(model,
identification="AWB", name="Architects Without Ballpens")
"""
settings = {"identification": identification, "name": name}
data = {"Name": settings["name"]}
data = {"Name": name}
if file.schema == "IFC2X3":
data["Id"] = settings["identification"]
data["Id"] = identification
else:
data["Identification"] = settings["identification"]
data["Identification"] = identification
return file.create_entity("IfcOrganization", **data)
@@ -31,13 +31,9 @@ def add_person(
:param identification: The computer readable unique identification of
the person. For example, their username in a CDE or alias.
:type identification: str, optional
:param family_name: The family name
:type family_name: str, optional
:param given_name: The given name
:type given_name: str, optional
:return: The newly created IfcPerson
:rtype: ifcopenshell.entity_instance
Example:
@@ -46,15 +42,9 @@ def add_person(
ifcopenshell.api.owner.add_person(model,
identification="bobthebuilder", family_name="Thebuilder", given_name="Bob")
"""
settings = {
"identification": identification,
"family_name": family_name,
"given_name": given_name,
}
data = {"FamilyName": settings["family_name"], "GivenName": settings["given_name"]}
data = {"FamilyName": family_name, "GivenName": given_name}
if file.schema == "IFC2X3":
data["Id"] = settings["identification"]
data["Id"] = identification
else:
data["Identification"] = settings["identification"]
data["Identification"] = identification
return file.create_entity("IfcPerson", **data)
@@ -30,11 +30,8 @@ def add_person_and_organisation(
:param person: The IfcPerson being the representative of the
organisation.
:type person: ifcopenshell.entity_instance
:param organisation: The IfcOrganization it
:type organisation: ifcopenshell.entity_instance
:return: The newly created IfcPersonAndOrganization
:rtype: ifcopenshell.entity_instance
Example:
@@ -48,6 +45,4 @@ def add_person_and_organisation(
ifcopenshell.api.owner.add_person_and_organisation(model,
person=person, organisation=organisation)
"""
settings = {"person": person, "organisation": organisation}
return file.createIfcPersonAndOrganization(settings["person"], settings["organisation"])
return file.create_entity("IfcPersonAndOrganization", person, organisation)
@@ -43,11 +43,8 @@ def assign_actor(
ifcopenshell.api.resource.assign_resource.
:param relating_actor: The IfcActor who is responsible for the object.
:type relating_actor: ifcopenshell.entity_instance
:param related_object: The object the actor is responsible for.
:type related_object: ifcopenshell.entity_instance
:return: The newly created IfcRelAssignsToActor relationship.
:rtype: ifcopenshell.entity_instance
Example:
@@ -74,24 +71,19 @@ def assign_actor(
ifcopenshell.api.owner.assign_actor(model,
relating_actor=manufacturer, related_object=pump_type)
"""
settings = {
"relating_actor": relating_actor,
"related_object": related_object,
}
if settings["related_object"].HasAssignments:
for rel in settings["related_object"].HasAssignments:
if rel.is_a("IfcRelAssignsToActor") and rel.RelatingActor == settings["relating_actor"]:
if related_object.HasAssignments:
for rel in related_object.HasAssignments:
if rel.is_a("IfcRelAssignsToActor") and rel.RelatingActor == relating_actor:
return rel
rel = None
if settings["relating_actor"].IsActingUpon:
rel = settings["relating_actor"].IsActingUpon[0]
if relating_actor.IsActingUpon:
rel = relating_actor.IsActingUpon[0]
if rel:
related_objects = list(rel.RelatedObjects)
related_objects.append(settings["related_object"])
related_objects.append(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
else:
@@ -100,8 +92,8 @@ def assign_actor(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingActor": settings["relating_actor"],
"RelatedObjects": [related_object],
"RelatingActor": relating_actor,
}
)
return rel
@@ -24,9 +24,7 @@ def remove_actor(file: ifcopenshell.file, actor: ifcopenshell.entity_instance) -
"""Removes an actor
:param actor: The IfcActor to remove.
:type actor: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -44,9 +42,7 @@ def remove_actor(file: ifcopenshell.file, actor: ifcopenshell.entity_instance) -
# Actually we need ballpens on this project
ifcopenshell.api.owner.remove_actor(model, actor=actor)
"""
settings = {"actor": actor}
history = settings["actor"].OwnerHistory
file.remove(settings["actor"])
history = actor.OwnerHistory
file.remove(actor)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -25,9 +25,7 @@ def remove_address(file: ifcopenshell.file, address: ifcopenshell.entity_instanc
relationship removed.
:param address: The IfcAddress to remove.
:type address: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -40,10 +38,8 @@ def remove_address(file: ifcopenshell.file, address: ifcopenshell.entity_instanc
# Change our mind and delete it
ifcopenshell.api.owner.remove_address(model, address=address)
"""
settings = {"address": address}
for inverse in file.get_inverse(settings["address"]):
for inverse in file.get_inverse(address):
if inverse.is_a() in ("IfcOrganization", "IfcPerson"):
if inverse.Addresses == (settings["address"],):
if inverse.Addresses == (address,):
inverse.Addresses = None
file.remove(settings["address"])
file.remove(address)
@@ -25,9 +25,7 @@ def remove_application(file: ifcopenshell.file, application: ifcopenshell.entity
Check whether or not the application is used anywhere prior to removal.
:param address: The IfcApplication to remove.
:type address: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -36,6 +34,4 @@ def remove_application(file: ifcopenshell.file, application: ifcopenshell.entity
application = ifcopenshell.api.owner.add_application(model)
ifcopenshell.api.owner.remove_address(model, application=application)
"""
settings = {"application": application}
file.remove(settings["application"])
file.remove(application)
@@ -28,9 +28,7 @@ def remove_person_and_organisation(
the "person and organisation" group.
:param person_and_organisation: The IfcPersonAndOrganization to remove.
:type person_and_organisation: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -46,17 +44,15 @@ def remove_person_and_organisation(
ifcopenshell.api.owner.remove_person_and_organisation(model, person_and_organisation=user)
"""
settings = {"person_and_organisation": person_and_organisation}
for inverse in file.get_inverse(settings["person_and_organisation"]):
for inverse in file.get_inverse(person_and_organisation):
if inverse.is_a("IfcDocumentInformation"):
if inverse.Editors == (settings["person_and_organisation"],):
if inverse.Editors == (person_and_organisation,):
inverse.Editors = None
elif inverse.is_a("IfcActor"):
ifcopenshell.api.root.remove_product(file, product=inverse)
elif inverse.is_a("IfcResourceLevelRelationship"):
if inverse.RelatedResourceObjects == (settings["person_and_organisation"],):
if inverse.RelatedResourceObjects == (person_and_organisation,):
file.remove(inverse)
elif inverse.is_a("IfcOwnerHistory"):
file.remove(inverse)
file.remove(settings["person_and_organisation"])
file.remove(person_and_organisation)
@@ -25,9 +25,7 @@ def remove_role(file: ifcopenshell.file, role: ifcopenshell.entity_instance) ->
leave some of them without roles.
:param role: The IfcActorRole to remove.
:type role: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -40,13 +38,11 @@ def remove_role(file: ifcopenshell.file, role: ifcopenshell.entity_instance) ->
# After running this, the organisation will have no role again
ifcopenshell.api.owner.remove_role(model, role=role)
"""
settings = {"role": role}
for inverse in file.get_inverse(settings["role"]):
for inverse in file.get_inverse(role):
if inverse.is_a() in ("IfcOrganization", "IfcPerson", "IfcPersonAndOrganization"):
if inverse.Roles == (settings["role"],):
if inverse.Roles == (role,):
inverse.Roles = None
elif inverse.is_a("IfcResourceLevelRelationship") and not inverse.is_a("IfcOrganizationRelationship"):
if inverse.RelatedResourceObjects == (settings["organisation"],):
if inverse.RelatedResourceObjects == (organisation,):
file.remove(inverse)
file.remove(settings["role"])
file.remove(role)
@@ -29,11 +29,8 @@ def unassign_actor(
This means that the actor is no longer responsible for the object.
:param relating_actor: The IfcActor who is responsible for the object.
:type relating_actor: ifcopenshell.entity_instance
:param related_object: The object the actor is responsible for.
:type related_object: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -55,13 +52,8 @@ def unassign_actor(
ifcopenshell.api.owner.unassign_actor(model,
relating_actor=manufacturer, related_object=pump_type)
"""
settings = {
"relating_actor": relating_actor,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToActor") or rel.RelatingActor != settings["relating_actor"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToActor") or rel.RelatingActor != relating_actor:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -70,6 +62,6 @@ def unassign_actor(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -59,9 +59,6 @@ def update_owner_history(
# API calls either.
ifcopenshell.api.attribute.edit_attributes(model, product=space, attributes={"Name": "Lobby"})
"""
settings = {"element": element}
element = settings["element"]
if not element.is_a("IfcRoot"):
return
user = ifcopenshell.api.owner.settings.get_user(file)
@@ -30,9 +30,7 @@ def add_parameterized_profile(file: ifcopenshell.file, ifc_class: str) -> ifcope
:param ifc_class: The subclass of IfcParameterizedProfileDef that you'd
like to create.
:type ifc_class: str
:return: The newly created element depending on the specified ifc_class.
:rtype: ifcopenshell.entity_instance
Example:
@@ -42,6 +40,4 @@ def add_parameterized_profile(file: ifcopenshell.file, ifc_class: str) -> ifcope
ifc_class="IfcCircleProfileDef")
circle.Radius = 1.
"""
settings = {"ifc_class": ifc_class}
return file.create_entity(settings["ifc_class"])
return file.create_entity(ifc_class)
@@ -35,11 +35,10 @@ def remove_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instanc
circle = 1.
ifcopenshell.api.profile.remove_profile(model, profile=circle)
"""
settings = {"profile": profile}
is_ifc2x3 = file.schema == "IFC2X3"
subelements = set()
for attribute in settings["profile"]:
for attribute in profile:
if isinstance(attribute, ifcopenshell.entity_instance):
subelements.add(attribute)
@@ -56,6 +55,6 @@ def remove_profile(file: ifcopenshell.file, profile: ifcopenshell.entity_instanc
for pset in profile_psets:
ifcopenshell.api.pset.remove_pset(file, product=profile, pset=pset)
file.remove(settings["profile"])
file.remove(profile)
for subelement in subelements:
ifcopenshell.util.element.remove_deep2(file, subelement)
@@ -50,9 +50,7 @@ def create_file(version: ifcopenshell.util.schema.IFC_SCHEMA = "IFC4") -> ifcope
# ... and off we go!
"""
settings = {"version": version}
file = ifcopenshell.file(schema=settings["version"])
file = ifcopenshell.file(schema=version)
file.wrapped_data.header.file_name.name = "/dev/null" # Hehehe
file.wrapped_data.header.file_name.time_stamp = (
datetime.datetime.utcnow().replace(tzinfo=datetime.timezone.utc).astimezone().replace(microsecond=0).isoformat()
@@ -28,11 +28,8 @@ def remove_pset(
All properties that are part of this property set are also removed.
:param product: The IfcObject to remove the property set from.
:type product: ifcopenshell.entity_instance
:param pset: The IfcPropertySet or IfcElementQuantity to remove.
:type pset: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -45,27 +42,25 @@ def remove_pset(
# Remove it!
ifcopenshell.api.pset.remove_pset(model, product=wall_type, pset=pset)
"""
settings = {"product": product, "pset": pset}
to_purge = []
should_remove_pset = True
for inverse in file.get_inverse(settings["pset"]):
for inverse in file.get_inverse(pset):
if inverse.is_a("IfcRelDefinesByProperties"):
if not inverse.RelatedObjects or len(inverse.RelatedObjects) == 1:
to_purge.append(inverse)
else:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["product"])
related_objects.remove(product)
inverse.RelatedObjects = related_objects
should_remove_pset = False
if should_remove_pset:
properties = [] # Predefined psets have no properties
if settings["pset"].is_a("IfcPropertySet"):
properties = settings["pset"].HasProperties or []
elif settings["pset"].is_a("IfcQuantitySet"):
properties = settings["pset"].Quantities or []
elif settings["pset"].is_a() in ("IfcMaterialProperties", "IfcProfileProperties"):
properties = settings["pset"].Properties or []
if pset.is_a("IfcPropertySet"):
properties = pset.HasProperties or []
elif pset.is_a("IfcQuantitySet"):
properties = pset.Quantities or []
elif pset.is_a() in ("IfcMaterialProperties", "IfcProfileProperties"):
properties = pset.Properties or []
for prop in properties:
if file.get_total_inverses(prop) != 1:
continue
@@ -75,8 +70,8 @@ def remove_pset(
file.remove(enumeration)
file.remove(prop)
# IfcMaterialProperties and IfcProfileProperties don't have OwnerHistory
history = getattr(settings["pset"], "OwnerHistory", None)
file.remove(settings["pset"])
history = getattr(pset, "OwnerHistory", None)
file.remove(pset)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
for element in to_purge:
@@ -71,19 +71,15 @@ def add_pset_template(
overridden by occurrences, and is applicable to everything.
:param name: The name of the property set
:type name: str,optional
:param template_type: Choose from one of PSET_TYPEDRIVENONLY,
PSET_TYPEDRIVENOVERRIDE, PSET_OCCURRENCEDRIVEN,
PSET_PERFORMANCEDRIVEN, QTO_TYPEDRIVENONLY, QTO_TYPEDRIVENOVERRIDE,
QTO_OCCURRENCEDRIVEN, NOTDEFINED
:type template_type: str,optional
:param applicable_entity: The entity that this template is allowed to be
applied to. For example, IfcWall means that the property set may be
assigned to walls only. IfcTypeObject, the default, means that the
property set may be assigned to any type.
:type applicable_entity: str,optional
:return: The newly created IfcPropertySetTemplate
:rtype: ifcopenshell.entity_instance
Example:
@@ -99,12 +95,10 @@ def add_pset_template(
name="HighVoltage", description="Whether there is a risk of high voltage.",
primary_measure_type="IfcBoolean")
"""
settings = {"name": name, "template_type": template_type, "applicable_entity": applicable_entity}
return file.create_entity(
"IfcPropertySetTemplate",
GlobalId=ifcopenshell.guid.new(),
Name=settings["name"],
TemplateType=settings["template_type"],
ApplicableEntity=settings["applicable_entity"],
Name=name,
TemplateType=template_type,
ApplicableEntity=applicable_entity,
)
@@ -27,9 +27,7 @@ def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.en
templates.
:param prop_template: The IfcSimplePropertyTemplate to remove.
:type prop_template: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -44,13 +42,11 @@ def remove_prop_template(file: ifcopenshell.file, prop_template: ifcopenshell.en
# Let's remove the second one.
ifcopenshell.api.pset_template.remove_prop_template(model, prop_template=prop2)
"""
settings = {"prop_template": prop_template}
for inverse in file.get_inverse(settings["prop_template"]):
for inverse in file.get_inverse(prop_template):
if len(inverse.HasPropertyTemplates) == 1:
inverse.HasPropertyTemplates = []
else:
has_property_templates = list(inverse.HasPropertyTemplates)
has_property_templates.remove(settings["prop_template"])
has_property_templates.remove(prop_template)
inverse.HasPropertyTemplates = has_property_templates
ifcopenshell.util.element.remove_deep(file, settings["prop_template"])
ifcopenshell.util.element.remove_deep(file, prop_template)
@@ -26,9 +26,7 @@ def remove_pset_template(file: ifcopenshell.file, pset_template: ifcopenshell.en
along with it.
:param pset_template: The IfcPropertySetTemplate to remove.
:type pset_template: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -40,6 +38,4 @@ def remove_pset_template(file: ifcopenshell.file, pset_template: ifcopenshell.en
# Let's remove the template.
ifcopenshell.api.pset_template.remove_pset_template(model, pset_template=template)
"""
settings = {"pset_template": pset_template}
ifcopenshell.util.element.remove_deep(file, settings["pset_template"])
ifcopenshell.util.element.remove_deep(file, pset_template)
@@ -51,20 +51,15 @@ def add_resource(
:param parent_resource: If this is a child resource (typically to a crew
resource), then nominate the parent IfcConstructionResource here.
:type parent_resource: ifcopenshell.entity_instance, optional
:param ifc_class: The class of resource chosen from
IfcConstructionEquipmentResource, IfcConstructionMaterialResource,
IfcConstructionProductResource, IfcCrewResource, IfcLaborResource,
or IfcSubContractResource.
:type ifc_class: str,optional
:param name: The name of the resource
:type name: str,optional
:param predefined_type: Consult the IFC documentation for the valid
predefined types for each type of resource class.
:type predefined_type: str,optional
:return: The newly created resource depending on the nominated IFC
class.
:rtype: ifcopenshell.entity_instance
Example:
@@ -76,25 +71,17 @@ def add_resource(
# Add some labour to our crew.
ifcopenshell.api.resource.add_resource(model, parent_resource=crew, ifc_class="IfcLaborResource")
"""
settings = {
"parent_resource": parent_resource,
"ifc_class": ifc_class,
"name": name,
"predefined_type": predefined_type,
}
resource = ifcopenshell.api.root.create_entity(
file,
ifc_class=settings["ifc_class"],
predefined_type=settings["predefined_type"],
name=settings["name"] or "Unnamed",
ifc_class=ifc_class,
predefined_type=predefined_type,
name=name or "Unnamed",
)
# TODO: this is an ambiguity by buildingSMART: Can we nest an IfcCrewResource under an IfcCrewResource ?
# https://forums.buildingsmart.org/t/what-are-allowed-to-be-root-level-construction-resources/3550
if settings["parent_resource"]:
ifcopenshell.api.nest.assign_object(
file, related_objects=[resource], relating_object=settings["parent_resource"]
)
if parent_resource:
ifcopenshell.api.nest.assign_object(file, related_objects=[resource], relating_object=parent_resource)
elif file.schema != "IFC2X3":
context = file.by_type("IfcContext")[0]
ifcopenshell.api.project.assign_declaration(file, definitions=[resource], relating_context=context)
@@ -42,12 +42,9 @@ def assign_resource(
(e.g. if the resource is a labour resource).
:param relating_resource: The IfcResource to assign the object to.
:type relating_resource: ifcopenshell.entity_instance
:param related_object: The IfcProduct or IfcActor to assign to the
object.
:type related_object: ifcopenshell.entity_instance
:return: The newly created IfcRelAssignsToResource
:rtype: ifcopenshell.entity_instance
Example:
@@ -82,26 +79,18 @@ def assign_resource(
# This means that UCO is now our crane operator.
ifcopenshell.api.resource.assign_resource(model, relating_resource=crane, related_object=actor)
"""
settings = {
"relating_resource": relating_resource,
"related_object": related_object,
}
if settings["related_object"].HasAssignments:
for assignment in settings["related_object"].HasAssignments:
if (
assignment.is_a("IfclRelAssignsToResource")
and assignment.RelatingResource == settings["relating_resource"]
):
if related_object.HasAssignments:
for assignment in related_object.HasAssignments:
if assignment.is_a("IfclRelAssignsToResource") and assignment.RelatingResource == relating_resource:
return assignment
resource_of = None
if settings["relating_resource"].ResourceOf:
resource_of = settings["relating_resource"].ResourceOf[0]
if relating_resource.ResourceOf:
resource_of = relating_resource.ResourceOf[0]
if resource_of:
related_objects = list(resource_of.RelatedObjects)
related_objects.append(settings["related_object"])
related_objects.append(related_object)
resource_of.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": resource_of})
else:
@@ -110,8 +99,8 @@ def assign_resource(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingResource": settings["relating_resource"],
"RelatedObjects": [related_object],
"RelatingResource": relating_resource,
}
)
return resource_of
@@ -25,15 +25,18 @@ import ifcopenshell.util.resource
def calculate_resource_usage(file: ifcopenshell.file, resource: ifcopenshell.entity_instance) -> None:
"""Calculates the number of resources required to perform scheduled work on a task."""
settings = {"resource": resource}
"""Calculates the number of resources required to perform scheduled work on a task.
if ifcopenshell.util.constraint.is_attribute_locked(settings["resource"], "Usage.ScheduleUsage"):
:param resource: The IfcConstructionResource to calculate the usage for.
:return: None
"""
if ifcopenshell.util.constraint.is_attribute_locked(resource, "Usage.ScheduleUsage"):
return
if not settings["resource"].Usage or not settings["resource"].Usage.ScheduleWork:
if not resource.Usage or not resource.Usage.ScheduleWork:
return
task = ifcopenshell.util.resource.get_task_assignments(settings["resource"])
task = ifcopenshell.util.resource.get_task_assignments(resource)
if not task or not task.TaskTime:
return
@@ -46,7 +49,7 @@ def calculate_resource_usage(file: ifcopenshell.file, resource: ifcopenshell.ent
seconds = task_duration.days * hours_per_day * 60 * 60
seconds += task_duration.seconds
person_hours = ifcopenshell.util.date.ifc2datetime(settings["resource"].Usage.ScheduleWork)
person_hours = ifcopenshell.util.date.ifc2datetime(resource.Usage.ScheduleWork)
required_resources = person_hours.total_seconds() / seconds
settings["resource"].Usage.ScheduleUsage = float(required_resources)
resource.Usage.ScheduleUsage = float(required_resources)
@@ -22,6 +22,9 @@ import ifcopenshell.util.element
def remove_resource_quantity(file: ifcopenshell.file, resource: ifcopenshell.entity_instance) -> None:
"""Removes the base quantity of a resource
:param resource: The IfcConstructionResource to remove the quantity from.
:return: None
Example:
.. code:: python
@@ -41,9 +44,7 @@ def remove_resource_quantity(file: ifcopenshell.file, resource: ifcopenshell.ent
# let's clean up our mess and remove the quantity.
ifcopenshell.api.resource.remove_resource_quantity(model, resource=labour)
"""
settings = {"resource": resource}
old_quantity = settings["resource"].BaseQuantity
settings["resource"].BaseQuantity = None
old_quantity = resource.BaseQuantity
resource.BaseQuantity = None
if old_quantity:
ifcopenshell.util.element.remove_deep(file, old_quantity)
@@ -29,12 +29,9 @@ def unassign_resource(
"""Removes the relationship between a resource and object
:param relating_resource: The IfcResource to assign the object to.
:type relating_resource: ifcopenshell.entity_instance
:param related_object: The IfcProduct or IfcActor to assign to the
object.
:type related_object: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -60,13 +57,8 @@ def unassign_resource(
ifcopenshell.api.resource.unassign_resource(model,
relating_resource=crane, related_object=product)
"""
settings = {
"relating_resource": relating_resource,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToResource") or rel.RelatingResource != settings["relating_resource"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToResource") or rel.RelatingResource != relating_resource:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -75,6 +67,6 @@ def unassign_resource(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -71,48 +71,44 @@ def create_entity(
"""
usecase = Usecase()
usecase.file = file
usecase.settings = {
"ifc_class": ifc_class,
"predefined_type": predefined_type,
"name": name,
}
return usecase.execute()
return usecase.execute(ifc_class, predefined_type, name)
class Usecase:
file: ifcopenshell.file
settings: dict[str, Any]
def execute(self):
def execute(
self, ifc_class: str, predefined_type: Optional[str] = None, name: Optional[str] = None
) -> ifcopenshell.entity_instance:
element = self.file.create_entity(
self.settings["ifc_class"],
ifc_class,
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(self.file),
}
)
element.Name = self.settings["name"] or None
if self.settings["predefined_type"]:
element.Name = name or None
if predefined_type:
if hasattr(element, "PredefinedType"):
try:
element.PredefinedType = self.settings["predefined_type"]
element.PredefinedType = predefined_type
except:
element.PredefinedType = "USERDEFINED"
if hasattr(element, "ObjectType"):
element.ObjectType = self.settings["predefined_type"]
element.ObjectType = predefined_type
elif hasattr(element, "ElementType"):
element.ElementType = self.settings["predefined_type"]
element.ElementType = predefined_type
elif hasattr(element, "ProcessType"):
element.ProcessType = self.settings["predefined_type"]
element.ProcessType = predefined_type
elif hasattr(element, "ObjectType"):
element.ObjectType = self.settings["predefined_type"]
element.ObjectType = predefined_type
if self.file.schema == "IFC2X3":
self.handle_2x3_defaults(element)
else:
self.handle_4_defaults(element)
return element
def handle_2x3_defaults(self, element):
def handle_2x3_defaults(self, element: ifcopenshell.entity_instance) -> None:
if element.is_a("IfcElementType"):
if hasattr(element, "PredefinedType") and not element.PredefinedType:
element.PredefinedType = "NOTDEFINED"
@@ -129,7 +125,7 @@ class Usecase:
element.ParameterTakesPrecedence = False
element.Sizeable = False
def handle_4_defaults(self, element):
def handle_4_defaults(self, element: ifcopenshell.entity_instance) -> None:
if element.is_a("IfcElementType"):
if hasattr(element, "PredefinedType") and not element.PredefinedType:
element.PredefinedType = "NOTDEFINED"
@@ -69,23 +69,16 @@ def add_task(
:param work_schedule: The work schedule to group the task in, if the
task is to be a top-level or root task. This is mutually exclusive
with the parent_task parameter.
:type work_schedule: ifcopenshell.entity_instance, optional
:param parent_task: The parent task, if the task is to be a subtask or
child task. This is mutually exclusive with the work_schedule
parameter.
:type parent_task: ifcopenshell.entity_instance, optioanl
:param name: The name of the task.
:type name: str,optional
:param description: The description of the task.
:type description: str,optional
:param identification: The identification code of the task.
:type identification: str,optional
:param predefined_type: The predefined type of the task. Common ones
include CONSTRUCTION, DEMOLITION, or MAINTENANCE. Consultant the
IFC documentation for IfcTaskTypeEnum for more information.
:type predefined_type: str
:return: The newly created IfcTask
:rtype: ifcopenshell.entity_instance
Example:
@@ -139,39 +132,28 @@ def add_task(
ifcopenshell.api.sequence.add_task(model, parent_task=cleaning, identification="3",
description="Setup the water pressure by tapping to a water supply and connecting to a ...")
"""
settings = {
"work_schedule": work_schedule,
"parent_task": parent_task,
"name": name,
"description": description,
"identification": identification,
"predefined_type": predefined_type,
}
task = ifcopenshell.api.root.create_entity(
file, ifc_class="IfcTask", name=settings["name"], predefined_type=settings["predefined_type"]
)
if settings["description"]:
task.Description = settings["description"]
if settings["identification"]:
task.Identification = settings["identification"]
task = ifcopenshell.api.root.create_entity(file, ifc_class="IfcTask", name=name, predefined_type=predefined_type)
if description:
task.Description = description
if identification:
task.Identification = identification
task.IsMilestone = False
if settings["work_schedule"]:
if work_schedule:
file.create_entity(
"IfcRelAssignsToControl",
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [task],
"RelatingControl": settings["work_schedule"],
"RelatingControl": work_schedule,
}
)
elif settings["parent_task"]:
elif parent_task:
rel = ifcopenshell.api.nest.assign_object(
file,
related_objects=[task],
relating_object=settings["parent_task"],
relating_object=parent_task,
)
if file.schema != "IFC2X3" and settings["parent_task"].Identification:
task.Identification = settings["parent_task"].Identification + "." + str(len(rel.RelatedObjects))
if file.schema != "IFC2X3" and parent_task.Identification:
task.Identification = parent_task.Identification + "." + str(len(rel.RelatedObjects))
return task
@@ -44,17 +44,13 @@ def add_time_period(
:param recurrence_pattern: The IfcRecurrencePattern to add the time
period to. See ifcopenshell.api.sequence.assign_recurrence_pattern.
:type recurrence_pattern: ifcopenshell.entity_instance
:param start_time: The start time of the time period, in a format
compatible with IfcTime, such as an ISO format time string or a
datetime.time object.
:type start_time: str,datetime.time
:param end_time: The end time of the time period, in a format
compatible with IfcTime, such as an ISO format time string or a
datetime.time object.
:type end_time: str,datetime.time
:return: The newly created IfcTimePeriod
:rtype: ifcopenshell.entity_instance
Example:
@@ -81,18 +77,12 @@ def add_time_period(
ifcopenshell.api.sequence.add_time_period(model,
recurrence_pattern=pattern, start_time="13:00", end_time="17:00")
"""
settings = {
"recurrence_pattern": recurrence_pattern,
"start_time": start_time,
"end_time": end_time,
}
time_period = file.create_entity("IfcTimePeriod")
time_period.StartTime = ifcopenshell.util.date.datetime2ifc(settings["start_time"], "IfcTime")
time_period.EndTime = ifcopenshell.util.date.datetime2ifc(settings["end_time"], "IfcTime")
time_periods = list(settings["recurrence_pattern"].TimePeriods or [])
time_period.StartTime = ifcopenshell.util.date.datetime2ifc(start_time, "IfcTime")
time_period.EndTime = ifcopenshell.util.date.datetime2ifc(end_time, "IfcTime")
time_periods = list(recurrence_pattern.TimePeriods or [])
time_periods.append(time_period)
settings["recurrence_pattern"].TimePeriods = time_periods
recurrence_pattern.TimePeriods = time_periods
ifcopenshell.util.sequence.is_working_day.cache_clear()
ifcopenshell.util.sequence.is_calendar_applicable.cache_clear()
@@ -38,12 +38,10 @@ def add_work_calendar(
:param name: The name of the calendar. Typically something like
"5 Day Working Week" or "24/7".
:type name: str, optional
:param predefined_type: The type of calendar, typically used to more
specifically define shifts, such as FIRSTSHIFT, SECONDSHIFT, or
THIRDSHIFT. Leave as NOTDEFINED for basic calendar usage.
:return: The newly created IfcWorkCalendar
:rtype: ifcopenshell.entity_instance
Example:
@@ -79,13 +77,11 @@ def add_work_calendar(
# this calendar by default (though you can override them).
ifcopenshell.api.control.assign_control(model, relating_control=calendar, related_object=task)
"""
settings = {"name": name, "predefined_type": predefined_type}
work_calendar = ifcopenshell.api.root.create_entity(
file,
ifc_class="IfcWorkCalendar",
predefined_type=settings["predefined_type"],
name=settings["name"],
predefined_type=predefined_type,
name=name,
)
context = file.by_type("IfcContext")[0]
ifcopenshell.api.project.assign_declaration(
@@ -41,15 +41,11 @@ def add_work_plan(
:param name: The name of the work plan. Recommended to be "Maintenance"
or "Construction" for the two main purposes.
:type name: str, optional
:param predefined_type: The type of work plan, used for baselining.
Leave as "NOTDEFINED" if unsure.
:type predefined_type: str
:param start_time: The earliest start time when the schedules grouped
within the work plan are relevant.
:type start_time: str,datetime.time
:return: The newly created IfcWorkPlan
:rtype: ifcopenshell.entity_instance
Example:
@@ -62,23 +58,18 @@ def add_work_plan(
schedule = ifcopenshell.api.sequence.add_work_schedule(model,
name="Construction Schedule A", work_plan=work_plan)
"""
settings = {
"name": name,
"predefined_type": predefined_type,
"start_time": start_time or datetime.now(),
}
start_time = start_time or datetime.now()
work_plan = ifcopenshell.api.root.create_entity(
file,
ifc_class="IfcWorkPlan",
predefined_type=settings["predefined_type"],
name=settings["name"],
predefined_type=predefined_type,
name=name,
)
work_plan.CreationDate = ifcopenshell.util.date.datetime2ifc(datetime.now(), "IfcDateTime")
user = ifcopenshell.api.owner.settings.get_user(file)
if user:
work_plan.Creators = [user.ThePerson]
work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(settings["start_time"], "IfcDateTime")
work_plan.StartTime = ifcopenshell.util.date.datetime2ifc(start_time, "IfcDateTime")
context = file.by_type("IfcContext")[0]
ifcopenshell.api.project.assign_declaration(
@@ -77,19 +77,12 @@ def add_work_schedule(
construction = ifcopenshell.api.sequence.add_task(model,
work_schedule=schedule, name="Construction", identification="C")
"""
settings = {
"name": name,
"predefined_type": predefined_type,
"object_type": object_type,
"start_time": start_time or datetime.now(),
"work_plan": work_plan,
}
start_time = start_time or datetime.now()
work_schedule = ifcopenshell.api.root.create_entity(
file,
ifc_class="IfcWorkSchedule",
predefined_type=settings["predefined_type"],
name=settings["name"],
predefined_type=predefined_type,
name=name,
)
if file.schema == "IFC2X3":
work_schedule.CreationDate = createIfcDateAndTime(file, datetime.now())
@@ -99,17 +92,17 @@ def add_work_schedule(
if user:
work_schedule.Creators = [user.ThePerson]
if file.schema == "IFC2X3":
work_schedule.StartTime = createIfcDateAndTime(file, settings["start_time"])
work_schedule.StartTime = createIfcDateAndTime(file, start_time)
else:
work_schedule.StartTime = ifcopenshell.util.date.datetime2ifc(settings["start_time"], "IfcDateTime")
if settings["object_type"]:
work_schedule.ObjectType = settings["object_type"]
if settings["work_plan"]:
work_schedule.StartTime = ifcopenshell.util.date.datetime2ifc(start_time, "IfcDateTime")
if object_type:
work_schedule.ObjectType = object_type
if work_plan:
ifcopenshell.api.aggregate.assign_object(
file,
**{
"products": [work_schedule],
"relating_object": settings["work_plan"],
"relating_object": work_plan,
}
)
elif file.schema != "IFC2X3":
@@ -36,12 +36,9 @@ def add_work_time(
:param work_calendar: The IfcWorkCalendar to add the work or holiday
time definition to.
:type work_calendar: ifcopenshell.entity_instance
:param time_type: Either WorkingTimes or ExceptionTimes, depending on
what you want to define.
:type time_type: str
:return: The newly created IfcWorkTime
:rtype: ifcopenshell.entity_instance
Example:
@@ -74,15 +71,13 @@ def add_work_time(
ifcopenshell.api.sequence.edit_recurrence_pattern(model,
recurrence_pattern=pattern, attributes={"DayComponent": [1], "MonthComponent": [1]})
"""
settings = {"work_calendar": work_calendar, "time_type": time_type}
work_time = file.create_entity("IfcWorkTime")
if settings["time_type"] == "WorkingTimes":
working_times = list(settings["work_calendar"].WorkingTimes or [])
if time_type == "WorkingTimes":
working_times = list(work_calendar.WorkingTimes or [])
working_times.append(work_time)
settings["work_calendar"].WorkingTimes = working_times
elif settings["time_type"] == "ExceptionTimes":
exception_times = list(settings["work_calendar"].ExceptionTimes or [])
work_calendar.WorkingTimes = working_times
elif time_type == "ExceptionTimes":
exception_times = list(work_calendar.ExceptionTimes or [])
exception_times.append(work_time)
settings["work_calendar"].ExceptionTimes = exception_times
work_calendar.ExceptionTimes = exception_times
return work_time
@@ -65,12 +65,9 @@ def assign_process(
:param relating_process: The IfcProcess (typically IfcTask) that the
input, control, or resource is related to.
:type relating_process: ifcopenshell.entity_instance
:param related_object: The IfcProduct (for input), IfcCostItem (for
control) or IfcConstructionResource (for resource).
:type related_object: ifcopenshell.entity_instance
:return: The newly created IfcRelAssignsToProcess relationship
:rtype: ifcopenshell.entity_instance
Example:
@@ -91,23 +88,18 @@ def assign_process(
# Let's demolish that wall!
ifcopenshell.api.sequence.assign_process(model, relating_process=task, related_object=wall)
"""
settings = {
"relating_process": relating_process,
"related_object": related_object,
}
if settings["related_object"].HasAssignments:
for assignment in settings["related_object"].HasAssignments:
if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess == settings["relating_process"]:
if related_object.HasAssignments:
for assignment in related_object.HasAssignments:
if assignment.is_a("IfcRelAssignsToProcess") and assignment.RelatingProcess == relating_process:
return
operates_on = None
if settings["relating_process"].OperatesOn:
operates_on = settings["relating_process"].OperatesOn[0]
if relating_process.OperatesOn:
operates_on = relating_process.OperatesOn[0]
if operates_on:
related_objects = list(operates_on.RelatedObjects)
related_objects.append(settings["related_object"])
related_objects.append(related_object)
operates_on.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": operates_on})
else:
@@ -116,8 +108,8 @@ def assign_process(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingProcess": settings["relating_process"],
"RelatedObjects": [related_object],
"RelatingProcess": relating_process,
}
)
return operates_on
@@ -41,12 +41,9 @@ def assign_product(
:param relating_product: The IfcProduct that was constructed as a result
of the task.
:type relating_product: ifcopenshell.entity_instance
:param related_object: The IfcProcess (typically IfcTask) of the
construction task.
:type related_object: ifcopenshell.entity_instance
:return: The newly created IfcRelAssignsToProduct relationship
:rtype: ifcopenshell.entity_instance
Example:
@@ -67,23 +64,18 @@ def assign_product(
# Let's construct that wall!
ifcopenshell.api.sequence.assign_product(model, relating_product=wall, related_object=task)
"""
settings = {
"relating_product": relating_product,
"related_object": related_object,
}
if settings["related_object"].HasAssignments:
for assignment in settings["related_object"].HasAssignments:
if assignment.is_a("IfcRelAssignsToProduct") and assignment.RelatingProduct == settings["relating_product"]:
if related_object.HasAssignments:
for assignment in related_object.HasAssignments:
if assignment.is_a("IfcRelAssignsToProduct") and assignment.RelatingProduct == relating_product:
return assignment
referenced_by = None
if settings["relating_product"].ReferencedBy:
referenced_by = settings["relating_product"].ReferencedBy[0]
if relating_product.ReferencedBy:
referenced_by = relating_product.ReferencedBy[0]
if referenced_by:
related_objects = list(referenced_by.RelatedObjects)
related_objects.append(settings["related_object"])
related_objects.append(related_object)
referenced_by.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, **{"element": referenced_by})
else:
@@ -92,8 +84,8 @@ def assign_product(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedObjects": [settings["related_object"]],
"RelatingProduct": settings["relating_product"],
"RelatedObjects": [related_object],
"RelatingProduct": relating_product,
}
)
return referenced_by
@@ -19,11 +19,12 @@
import ifcopenshell
import ifcopenshell.api.project
import ifcopenshell.api.aggregate
from typing import Union
def assign_work_plan(
file: ifcopenshell.file, work_schedule: ifcopenshell.entity_instance, work_plan: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
) -> Union[ifcopenshell.entity_instance, None]:
"""Assigns a work schedule to a work plan
Typically, work schedules would be assigned to a work plan at creation.
@@ -31,11 +32,8 @@ def assign_work_plan(
:param work_schedule: The IfcWorkSchedule that will be assigned to the
work plan.
:type work_schedule: ifcopenshell.entity_instance
:param work_plan: The IfcWorkPlan for the schedule to be assigned to.
:type work_plan: ifcopenshell.entity_instance
:return: The IfcRelAggregates relationship
:rtype: ifcopenshell.entity_instance
Example:
@@ -50,20 +48,16 @@ def assign_work_plan(
# ... you can assign the work plan afterwards.
ifcopenshell.api.sequence.assign_work_plan(work_schedule=schedule, work_plan=work_plan)
"""
settings = {"work_schedule": work_schedule, "work_plan": work_plan}
# TODO: this is an ambiguity by buildingSMART
# See https://forums.buildingsmart.org/t/is-the-ifcworkschedule-project-declaration-mutually-exclusive-to-aggregation-within-a-relating-ifcworkplan/3510
ifcopenshell.api.project.unassign_declaration(
file,
definitions=[settings["work_schedule"]],
definitions=[work_schedule],
relating_context=file.by_type("IfcContext")[0],
)
rel_aggregates = ifcopenshell.api.aggregate.assign_object(
file,
**{
"products": [settings["work_schedule"]],
"relating_object": settings["work_plan"],
}
products=[work_schedule],
relating_object=work_plan,
)
return rel_aggregates
@@ -21,6 +21,7 @@ import ifcopenshell.guid
import ifcopenshell.api.nest
import ifcopenshell.api.owner
import ifcopenshell.api.sequence
import ifcopenshell.util.date
import ifcopenshell.util.element
import ifcopenshell.util.sequence
from typing import Union, Any
@@ -155,7 +156,9 @@ class Usecase:
duration_type=inverse.TimeLag.DurationType,
)
def create_object_reference(self, relating_object, related_object):
def create_object_reference(
self, relating_object: ifcopenshell.entity_instance, related_object: ifcopenshell.entity_instance
) -> ifcopenshell.entity_instance:
referenced_by = None
if relating_object.Declares:
referenced_by = relating_object.Declares[0]
@@ -31,9 +31,7 @@ def remove_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance) ->
sequences or controls are also removed.
:param task: The IfcTask to remove.
:type task: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -56,76 +54,74 @@ def remove_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance) ->
# just fix it on site.
ifcopenshell.api.sequence.remove_task(model, task=design)
"""
settings = {"task": task}
# TODO: do a deep purge
ifcopenshell.api.project.unassign_declaration(
file,
definitions=[settings["task"]],
definitions=[task],
relating_context=file.by_type("IfcContext")[0],
)
if task_time := settings["task"].TaskTime:
if task_time := task.TaskTime:
if task_time.is_a("IfcTaskTimeRecurring"):
ifcopenshell.api.sequence.unassign_recurrence_pattern(file, task_time.Recurrence)
file.remove(task_time)
# Handle IfcRelNests.
if rels := settings["task"].IsNestedBy:
if rels := task.IsNestedBy:
subtasks = rels[0].RelatedObjects
# Use batching for optimization.
ifcopenshell.api.nest.unassign_object(file, subtasks)
for task_ in subtasks:
ifcopenshell.api.sequence.remove_task(file, task=task_)
if settings["task"].Nests:
ifcopenshell.api.nest.unassign_object(file, [settings["task"]])
if task.Nests:
ifcopenshell.api.nest.unassign_object(file, [task])
for inverse in file.get_inverse(settings["task"]):
for inverse in file.get_inverse(task):
if inverse.is_a("IfcRelSequence"):
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToControl"):
if inverse.RelatingControl == settings["task"] or len(inverse.RelatedObjects) == 1:
if inverse.RelatingControl == task or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
else:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["task"])
related_objects.remove(task)
inverse.RelatedObjects = related_objects
elif inverse.is_a("IfcRelDefinesByProperties"):
ifcopenshell.api.pset.remove_pset(
file,
product=settings["task"],
product=task,
pset=inverse.RelatingPropertyDefinition,
)
elif inverse.is_a("IfcRelAssignsToProcess"):
if inverse.RelatingProcess == settings["task"] or len(inverse.RelatedObjects) == 1:
if inverse.RelatingProcess == task or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
elif inverse.is_a("IfcRelAssignsToProduct"):
if inverse.RelatingProduct == settings["task"] or len(inverse.RelatedObjects) == 1:
if inverse.RelatingProduct == task or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
else:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["task"])
related_objects.remove(task)
inverse.RelatedObjects = related_objects
elif inverse.is_a("IfcRelAssignsToObject"):
if inverse.RelatingObject == settings["task"] or len(inverse.RelatedObjects) == 1:
if inverse.RelatingObject == task or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
else:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["task"])
related_objects.remove(task)
inverse.RelatedObjects = related_objects
elif inverse.is_a("IfcRelAssignsToProcess"):
history = inverse.OwnerHistory
@@ -133,7 +129,7 @@ def remove_task(file: ifcopenshell.file, task: ifcopenshell.entity_instance) ->
if history:
ifcopenshell.util.element.remove_deep2(file, history)
history = settings["task"].OwnerHistory
file.remove(settings["task"])
history = task.OwnerHistory
file.remove(task)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -23,9 +23,7 @@ def remove_time_period(file: ifcopenshell.file, time_period: ifcopenshell.entity
"""Removes a time period
:param time_period: The IfcTimePeriod to remove.
:type time_period: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -55,6 +53,4 @@ def remove_time_period(file: ifcopenshell.file, time_period: ifcopenshell.entity
# Let's take the afternoon off!
ifcopenshell.api.sequence.remove_time_period(model, time_period=afternoon)
"""
settings = {"time_period": time_period}
file.remove(settings["time_period"])
file.remove(time_period)
@@ -30,9 +30,7 @@ def remove_work_calendar(file: ifcopenshell.file, work_calendar: ifcopenshell.en
calendar.
:param work_calendar: The IfcWorkCalendar to remove
:type work_calendar: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -44,32 +42,30 @@ def remove_work_calendar(file: ifcopenshell.file, work_calendar: ifcopenshell.en
# And remove it immediately
ifcopenshell.api.sequence.remove_work_calendar(model, work_calendar=calendar)
"""
settings = {"work_calendar": work_calendar}
# TODO: do a deep purge
ifcopenshell.api.project.unassign_declaration(
file,
definitions=[settings["work_calendar"]],
definitions=[work_calendar],
relating_context=file.by_type("IfcContext")[0],
)
if settings["work_calendar"].Controls:
for rel in settings["work_calendar"].Controls:
if work_calendar.Controls:
for rel in work_calendar.Controls:
for related_object in rel.RelatedObjects:
ifcopenshell.api.control.unassign_control(
file,
relating_control=settings["work_calendar"],
relating_control=work_calendar,
related_object=related_object,
)
# Currently in API work times are created already attached
# to the work calendar, so they are never reused.
for working_time in settings["work_calendar"].WorkingTimes or []:
for working_time in work_calendar.WorkingTimes or []:
ifcopenshell.api.sequence.remove_work_time(file, work_time=working_time)
for exception_time in settings["work_calendar"].ExceptionTimes or []:
for exception_time in work_calendar.ExceptionTimes or []:
ifcopenshell.api.sequence.remove_work_time(file, work_time=exception_time)
history = settings["work_calendar"].OwnerHistory
file.remove(settings["work_calendar"])
history = work_calendar.OwnerHistory
file.remove(work_calendar)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -29,9 +29,7 @@ def remove_work_plan(file: ifcopenshell.file, work_plan: ifcopenshell.entity_ins
removed.
:param work_plan: The IfcWorkPlan to remove.
:type work_plan: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -43,11 +41,9 @@ def remove_work_plan(file: ifcopenshell.file, work_plan: ifcopenshell.entity_ins
# And remove it immediately
ifcopenshell.api.sequence.remove_work_plan(model, work_plan=work_plan)
"""
settings = {"work_plan": work_plan}
ifcopenshell.api.project.unassign_declaration(
file,
definitions=[settings["work_plan"]],
definitions=[work_plan],
relating_context=file.by_type("IfcContext")[0],
)
@@ -55,7 +51,7 @@ def remove_work_plan(file: ifcopenshell.file, work_plan: ifcopenshell.entity_ins
if related_objects:
ifcopenshell.api.aggregate.unassign_object(file, related_objects)
history = settings["work_plan"].OwnerHistory
file.remove(settings["work_plan"])
history = work_plan.OwnerHistory
file.remove(work_plan)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -47,32 +47,30 @@ def remove_work_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.en
# And remove it immediately
ifcopenshell.api.sequence.remove_work_schedule(model, work_schedule=schedule)
"""
settings = {"work_schedule": work_schedule}
# TODO: do a deep purge
ifcopenshell.api.project.unassign_declaration(
file, definitions=[settings["work_schedule"]], relating_context=file.by_type("IfcContext")[0]
file, definitions=[work_schedule], relating_context=file.by_type("IfcContext")[0]
)
if settings["work_schedule"].Declares:
for rel in settings["work_schedule"].Declares:
for work_schedule in rel.RelatedObjects:
ifcopenshell.api.sequence.remove_work_schedule(file, work_schedule=work_schedule)
if work_schedule.Declares:
for rel in work_schedule.Declares:
for work_schedule_ in rel.RelatedObjects:
ifcopenshell.api.sequence.remove_work_schedule(file, work_schedule=work_schedule_)
# Unassign from work plans.
if settings["work_schedule"].Decomposes:
ifcopenshell.api.aggregate.unassign_object(file, [settings["work_schedule"]])
if work_schedule.Decomposes:
ifcopenshell.api.aggregate.unassign_object(file, [work_schedule])
for inverse in file.get_inverse(settings["work_schedule"]):
for inverse in file.get_inverse(work_schedule):
if inverse.is_a("IfcRelDefinesByObject"):
if inverse.RelatingObject == settings["work_schedule"] or len(inverse.RelatedObjects) == 1:
if inverse.RelatingObject == work_schedule or len(inverse.RelatedObjects) == 1:
history = inverse.OwnerHistory
file.remove(inverse)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
else:
related_objects = list(inverse.RelatedObjects)
related_objects.remove(settings["work_schedule"])
related_objects.remove(work_schedule)
inverse.RelatedObjects = related_objects
elif inverse.is_a("IfcRelAssignsToControl"):
[
@@ -81,7 +79,7 @@ def remove_work_schedule(file: ifcopenshell.file, work_schedule: ifcopenshell.en
if related_object.is_a("IfcTask")
]
history = settings["work_schedule"].OwnerHistory
file.remove(settings["work_schedule"])
history = work_schedule.OwnerHistory
file.remove(work_schedule)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
@@ -31,11 +31,8 @@ def unassign_process(
See ifcopenshell.api.sequence.assign_process for details.
:param relating_process: The IfcTask in the relationship.
:type relating_process: ifcopenshell.entity_instance
:param related_object: The related object.
:type related_object: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -59,13 +56,8 @@ def unassign_process(
# Change our mind.
ifcopenshell.api.sequence.unassign_process(model, relating_process=task, related_object=wall)
"""
settings = {
"relating_process": relating_process,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProcess") or rel.RelatingProcess != settings["relating_process"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProcess") or rel.RelatingProcess != relating_process:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -74,7 +66,7 @@ def unassign_process(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, element=rel)
return rel
@@ -31,11 +31,8 @@ def unassign_product(
See ifcopenshell.api.sequence.assign_product for details.
:param relating_product: The IfcProduct in the relationship.
:type relating_product: ifcopenshell.entity_instance
:param related_object: The IfcTask in the relationship.
:type related_object: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -59,13 +56,8 @@ def unassign_product(
# Change our mind.
ifcopenshell.api.sequence.unassign_product(relating_product=wall, related_object=task)
"""
settings = {
"relating_product": relating_product,
"related_object": related_object,
}
for rel in settings["related_object"].HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != settings["relating_product"]:
for rel in related_object.HasAssignments or []:
if not rel.is_a("IfcRelAssignsToProduct") or rel.RelatingProduct != relating_product:
continue
if len(rel.RelatedObjects) == 1:
history = rel.OwnerHistory
@@ -74,7 +66,7 @@ def unassign_product(
ifcopenshell.util.element.remove_deep2(file, history)
return
related_objects = list(rel.RelatedObjects)
related_objects.remove(settings["related_object"])
related_objects.remove(related_object)
rel.RelatedObjects = related_objects
ifcopenshell.api.owner.update_owner_history(file, element=rel)
return rel
@@ -28,9 +28,7 @@ def unassign_recurrence_pattern(file: ifcopenshell.file, recurrence_pattern: ifc
or replace IfcTaskTimeRecurring with IfcTaskTime).
:param recurrence_pattern: The IfcRecurrencePattern to remove.
:type recurrence_pattern: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -50,8 +48,6 @@ def unassign_recurrence_pattern(file: ifcopenshell.file, recurrence_pattern: ifc
# Change our mind, let's just maintain it whenever we feel like it.
ifcopenshell.api.sequence.unassign_recurrence_pattern(recurrence_pattern=pattern)
"""
settings = {"recurrence_pattern": recurrence_pattern}
for time_period in settings["recurrence_pattern"].TimePeriods or []:
for time_period in recurrence_pattern.TimePeriods or []:
file.remove(time_period)
file.remove(settings["recurrence_pattern"])
file.remove(recurrence_pattern)
@@ -29,11 +29,8 @@ def unassign_sequence(
"""Removes a sequence relationship between tasks
:param relating_process: The previous / predecessor task.
:type relating_process: ifcopenshell.entity_instance
:param related_process: The next / successor task.
:type related_process: ifcopenshell.entity_instance
:return: None
:rtype: None
Example:
@@ -60,15 +57,10 @@ def unassign_sequence(
ifcopenshell.api.sequence.unassign_sequence(model,
relating_process=zone1, related_process=zone2)
"""
settings = {
"relating_process": relating_process,
"related_process": related_process,
}
for rel in settings["related_process"].IsSuccessorFrom or []:
if rel.RelatingProcess == settings["relating_process"]:
for rel in related_process.IsSuccessorFrom or []:
if rel.RelatingProcess == relating_process:
history = rel.OwnerHistory
file.remove(rel)
if history:
ifcopenshell.util.element.remove_deep2(file, history)
ifcopenshell.api.sequence.cascade_schedule(file, task=settings["related_process"])
ifcopenshell.api.sequence.cascade_schedule(file, task=related_process)
@@ -69,13 +69,11 @@ def assign_container(
previous aggregation, containment, or nesting relationships it may have.
:param products: A list of physical IfcElements existing in the space.
:type products: list[ifcopenshell.entity_instance]
:param relating_structure: The IfcSpatialStructureElement element, such
as IfcBuilding, IfcBuildingStorey, or IfcSpace that the element
exists in.
:return: The IfcRelContainedInSpatialStructure relationship instance
or `None` if `products` was empty list.
:rtype: Union[ifcopenshell.entity_instance, None]
Example:
@@ -103,16 +101,10 @@ def assign_container(
ifcopenshell.api.spatial.assign_container(model, products=[wall], relating_structure=storey)
ifcopenshell.api.spatial.assign_container(model, products=[furniture], relating_structure=space)
"""
settings = {
"products": products,
"relating_structure": relating_structure,
}
if not settings["products"]:
if not products:
return
products = set(settings["products"])
relating_structure = settings["relating_structure"]
products_set = set(products)
structure_rel = next(iter(relating_structure.ContainsElements), None)
previous_containers_rels: set[ifcopenshell.entity_instance] = set()
@@ -120,7 +112,7 @@ def assign_container(
products_with_containers: list[ifcopenshell.entity_instance] = []
# check if there is anything to change
for product in products:
for product in products_set:
product_rel = next(iter(product.ContainedInStructure), None)
if product_rel is None:
@@ -144,7 +136,7 @@ def assign_container(
# unassign elements from previous containers
for rel in previous_containers_rels:
related_elements = set(rel.RelatedElements) - products
related_elements = set(rel.RelatedElements) - products_set
if related_elements:
rel.RelatedElements = list(related_elements)
ifcopenshell.api.owner.update_owner_history(file, **{"element": rel})
@@ -156,7 +148,7 @@ def assign_container(
# assign elements to a new container
if structure_rel:
structure_rel.RelatedElements = list(set(structure_rel.RelatedElements) | products)
structure_rel.RelatedElements = list(set(structure_rel.RelatedElements) | products_set)
ifcopenshell.api.owner.update_owner_history(file, **{"element": structure_rel})
else:
structure_rel = file.create_entity(
@@ -164,8 +156,8 @@ def assign_container(
**{
"GlobalId": ifcopenshell.guid.new(),
"OwnerHistory": ifcopenshell.api.owner.create_owner_history(file),
"RelatedElements": list(products),
"RelatingStructure": settings["relating_structure"],
"RelatedElements": list(products_set),
"RelatingStructure": relating_structure,
}
)
@@ -25,9 +25,7 @@ def unassign_container(file: ifcopenshell.file, products: list[ifcopenshell.enti
"""Unassigns a container from products.
:param product: A list of IfcProducts to remove the containment from.
:type product: list[ifcopenshell.entity_instance]
:return: None
:rtype: None
Example:
@@ -54,15 +52,11 @@ def unassign_container(file: ifcopenshell.file, products: list[ifcopenshell.enti
# Not anymore!
ifcopenshell.api.spatial.unassign_container(model, products=[wall])
"""
settings = {
"products": products,
}
products = set(settings["products"])
rels = set(rel for product in products if (rel := next(iter(product.ContainedInStructure), None)))
products_set = set(products)
rels = set(rel for product in products_set if (rel := next(iter(product.ContainedInStructure), None)))
for rel in rels:
related_elements = set(rel.RelatedElements) - products
related_elements = set(rel.RelatedElements) - products_set
if related_elements:
rel.RelatedElements = list(related_elements)
ifcopenshell.api.owner.update_owner_history(file, element=rel)
@@ -27,7 +27,7 @@ def add_structural_activity(
ifc_class: str = "IfcStructuralPlanarAction",
predefined_type: str = "CONST",
global_or_local: Literal["GLOBAL_COORDS", "LOCAL_COORDS"] = "GLOBAL_COORDS",
) -> None:
) -> ifcopenshell.entity_instance:
"""Adds a new structural activity
A structural activity is either a structural action or a reaction. It
@@ -38,42 +38,29 @@ def add_structural_activity(
a structural member.
:param ifc_class: Choose from any subtype of IfcStructuralActivity.
:type ifc_class: str
:param predefined_type: View the IFC documentation for what valid
predefined types may be chosen.
:type predefined_type: str
:param global_or_local: The location coordinates of the load is always
defined locally relative to the structural member the activity is
assigned to. However, the directions of the applied load may either
be specified globally or locally depending on how this argument is
set. Choose from GLOBAL_COORDS or LOCAL_COORDS.
:type global_or_local: str
:param applied_load: The IfcStructuralLoad that is applied in this
activity.
:type applied_load: ifcopenshell.entity_instance
:param structural_member: The IfcStructuralMember that the load is
applied to.
:type structural_member: ifcopenshell.entity_instance
:return: The newly created entity based on the ifc_class
:rtype: ifcopenshell.entity_instance
"""
settings = {
"ifc_class": ifc_class,
"predefined_type": predefined_type,
"global_or_local": global_or_local,
"applied_load": applied_load,
"structural_member": structural_member,
}
activity = ifcopenshell.api.root.create_entity(
file,
ifc_class=settings["ifc_class"],
predefined_type=settings["predefined_type"],
ifc_class=ifc_class,
predefined_type=predefined_type,
)
activity.AppliedLoad = settings["applied_load"]
activity.GlobalOrLocal = settings["global_or_local"]
activity.AppliedLoad = applied_load
activity.GlobalOrLocal = global_or_local
rel = ifcopenshell.api.root.create_entity(file, ifc_class="IfcRelConnectsStructuralActivity")
rel.RelatingElement = settings["structural_member"]
rel.RelatingElement = structural_member
rel.RelatedStructuralActivity = activity
return activity
@@ -32,18 +32,14 @@ def add_structural_boundary_condition(
edge condition, and surface connections will have a face condition.
:param name: The name of the boundary condition.
:type name: str,optional
:param connection: The IfcStructuralConnection to apply the boundary
condition to. This will determine the type of condition that is
created. If no connection is supplied, an orphan boundary condition
will be created using the ifc_class that you specify.
:type connection: ifcopenshell.entity_instance,optional
:param ifc_class: The class of IfcBoundaryCondition to create, only
relevant if you do not specify a connection and want to create an
orphaned boundary condition.
:type ifc_class: str,optional
:return: The newly created IfcBoundaryCondition
:rtype: ifcopenshell.entity_instance
Example:
@@ -51,14 +47,12 @@ def add_structural_boundary_condition(
ifcopenshell.api.structural.add_structural_boundary_condition(model, connection=connection)
"""
settings = {"name": name, "connection": connection, "ifc_class": ifc_class}
if settings["connection"]:
if connection:
# assign boundary condition to a connection
if settings["connection"].is_a("IfcRelConnectsStructuralMember"):
related_connection = settings["connection"].RelatedStructuralConnection
if connection.is_a("IfcRelConnectsStructuralMember"):
related_connection = connection.RelatedStructuralConnection
else:
related_connection = settings["connection"]
related_connection = connection
if related_connection.is_a("IfcStructuralPointConnection"):
boundary_class = "IfcBoundaryNodeCondition"
@@ -67,9 +61,9 @@ def add_structural_boundary_condition(
elif related_connection.is_a("IfcStructuralSurfaceConnection"):
boundary_class = "IfcBoundaryFaceCondition"
condition = file.create_entity(boundary_class, Name=settings["name"])
settings["connection"].AppliedCondition = condition
condition = file.create_entity(boundary_class, Name=name)
connection.AppliedCondition = condition
return condition
else:
# add an orphan boundary condition
return file.create_entity(settings["ifc_class"], Name=settings["name"])
return file.create_entity(ifc_class, Name=name)

Some files were not shown because too many files have changed in this diff Show More