Added some MEP related type templates

3 more new types for distribution segments.
Distribution segments work the same way as beams - they come without representation but with IfcMaterialProfileSet defined with some profile. Occurences will also have ports at the start and the end of the segment (ports can be displayed in "Ports" section in "Services and Systems"). If you extend the segment or move it around - ports will move accordingly.

Small demo - https://imgur.com/a/Aq0utXa
This commit is contained in:
Andrej730
2023-07-20 17:30:28 +05:00
parent 6b276b8aed
commit 2e3cc8e73a
6 changed files with 96 additions and 99 deletions
@@ -35,93 +35,31 @@ from mathutils import Vector, Matrix
class MepGenerator:
def __init__(self, relating_type):
def __init__(self, relating_type=None):
self.relating_type = relating_type
def generate(self):
def setup_ports(self, obj):
self.file = tool.Ifc.get()
self.collection = bpy.context.view_layer.active_layer_collection.collection
if self.relating_type.is_a("IfcCableCarrierSegmentType"):
pass
elif self.relating_type.is_a("IfcCableSegmentType"):
pass
elif self.relating_type.is_a("IfcDuctSegmentType"):
dimensions = ifcopenshell.util.element.get_psets(self.relating_type).get("Pset_DuctSegmentTypeCommon") or {}
shape = dimensions.get("Shape") or dimensions.get("CrossSectionShape")
if shape == "RECTANGULAR":
self.width = dimensions.get("NominalDiameterOrWidth")
self.height = dimensions.get("NominalHeight")
self.length = 1
return self.derive_from_cursor()
elif self.relating_type.is_a("IfcPipeSegmentType"):
pass
def derive_from_cursor(self):
self.location = bpy.context.scene.cursor.location
return self.create_rectangle_segment()
def create_rectangle_segment(self):
verts = [
Vector((-self.width / 2, self.height / 2, 0)),
Vector((-self.width / 2, -self.height / 2, 0)),
Vector((-self.width / 2, self.height / 2, self.length)),
Vector((-self.width / 2, -self.height / 2, self.length)),
Vector((self.width / 2, self.height / 2, 0)),
Vector((self.width / 2, -self.height / 2, 0)),
Vector((self.width / 2, self.height / 2, self.length)),
Vector((self.width / 2, -self.height / 2, self.length)),
]
faces = [
[1, 3, 2, 0],
[4, 6, 7, 5],
[1, 0, 4, 5],
[3, 7, 6, 2],
[0, 2, 6, 4],
[1, 5, 7, 3],
]
mesh = bpy.data.meshes.new(name="Segment")
mesh.from_pydata(verts, [], faces)
ifc_classes = ifcopenshell.util.type.get_applicable_entities(self.relating_type.is_a(), self.file.schema)
ifc_class = ifc_classes[0]
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
obj.location = self.location
obj.rotation_euler[0] = math.pi / 2
obj.rotation_euler[2] = math.pi / 2
self.collection.objects.link(obj)
bpy.ops.bim.assign_class(
obj=obj.name,
ifc_class=ifc_class,
ifc_representation_class="IfcExtrudedAreaSolid/IfcRectangleProfileDef",
)
element = tool.Ifc.get_entity(obj)
representation = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
extrusion = tool.Model.get_extrusion(representation)
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
length = extrusion.Depth * si_conversion
end_port_matrix = Matrix.Translation((0, 0, length))
blenderbim.core.type.assign_type(tool.Ifc, tool.Type, element=element, type=self.relating_type)
pset = ifcopenshell.api.run("pset.add_pset", self.file, product=element, name="EPset_Parametric")
ifcopenshell.api.run("pset.edit_pset", self.file, pset=pset, properties={"Engine": "BlenderBIM.Mep"})
ports = tool.System.get_ports(element)
if not ports:
start_port_matrix = Matrix()
for mat, flow_direction in zip([start_port_matrix, end_port_matrix], ("SINK", "SOURCE")):
port = tool.Ifc.run("system.add_port", element=element)
port.FlowDirection = flow_direction
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
return
start_port_matrix = Matrix()
end_port_matrix = Matrix.Translation((0, 0, self.length))
for mat in [start_port_matrix, end_port_matrix]:
port = tool.Ifc.run("root.create_entity", ifc_class="IfcDistributionPort")
tool.Ifc.run("system.assign_port", element=element, port=port)
tool.Ifc.run("geometry.edit_object_placement", product=port, matrix=obj.matrix_world @ mat, is_si=True)
try:
obj.select_set(True)
except RuntimeError:
def msg(self, context):
txt = "The created object could not be assigned to a collection. "
txt += "Has any IfcSpatialElement been deleted?"
self.layout.label(text=txt)
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
return obj
# TODO: better way to find the port to be moved
end_port = next((p for p in ports if p.FlowDirection == "SOURCE"), None)
if not end_port:
return
tool.Model.edit_element_placement(end_port, obj.matrix_world @ end_port_matrix)
@@ -105,15 +105,16 @@ class AddConstrTypeInstance(bpy.types.Operator):
material = ifcopenshell.util.element.get_material(relating_type)
if material and material.is_a("IfcMaterialProfileSet"):
if profile.DumbProfileGenerator(relating_type).generate():
if obj := profile.DumbProfileGenerator(relating_type).generate():
if not relating_type.is_a("IfcFlowSegmentType"):
return {"FINISHED"}
mep.MepGenerator(relating_type).setup_ports(obj)
return {"FINISHED"}
elif material and material.is_a("IfcMaterialLayerSet"):
if self.generate_layered_element(ifc_class, relating_type):
tool.Blender.select_and_activate_single_object(context, context.selected_objects[-1])
return {"FINISHED"}
if relating_type.is_a("IfcFlowSegmentType") and not relating_type.RepresentationMaps:
if mep.MepGenerator(relating_type).generate():
return {"FINISHED"}
building_obj = None
if len(context.selected_objects) == 1 and context.active_object:
@@ -34,6 +34,7 @@ from mathutils import Vector, Matrix, Quaternion
from blenderbim.bim.module.geometry.helper import Helper
from blenderbim.bim.module.model.wall import DumbWallRecalculator
from blenderbim.bim.module.model.decorator import ProfileDecorator
from blenderbim.bim.module.model.mep import MepGenerator
class DumbProfileGenerator:
@@ -74,7 +75,9 @@ class DumbProfileGenerator:
obj = bpy.data.objects.new(tool.Model.generate_occurrence_name(self.relating_type, ifc_class), mesh)
matrix_world = Matrix()
if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"]:
if self.relating_type.is_a() in ["IfcBeamType", "IfcMemberType"] or self.relating_type.is_a(
"IfcFlowSegmentType"
):
matrix_world = Matrix.Rotation(pi / 2, 4, "Z") @ Matrix.Rotation(pi / 2, 4, "X") @ matrix_world
matrix_world.col[3] = self.location.to_4d()
if self.collection_obj and self.collection_obj.BIMObjectProperties.ifc_definition_id:
@@ -230,9 +233,11 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
return {"FINISHED"}
for obj in selected_objs:
tool.Geometry.clear_scale(obj)
if len(selected_objs) == 1:
joiner.join_E(context.active_object, context.scene.cursor.location)
return {"FINISHED"}
if len(selected_objs) == 2:
if self.join_type == "L":
joiner.join_L([o for o in selected_objs if o != context.active_object][0], context.active_object)
@@ -245,6 +250,7 @@ class ExtendProfile(bpy.types.Operator, tool.Ifc.Operator):
if obj == context.active_object:
continue
joiner.join_T(obj, context.active_object)
return {"FINISHED"}
@@ -486,6 +492,8 @@ class DumbProfileJoiner:
should_sync_changes_first=False,
)
tool.Geometry.record_object_materials(obj)
if element.is_a("IfcFlowSegment"):
MepGenerator().setup_ports(obj)
def join(self, profile1, profile2, connection1, connection2, is_relating=True, description="BUTT"):
element1 = tool.Ifc.get_entity(profile1)
@@ -869,9 +877,15 @@ class Rotate90(bpy.types.Operator, tool.Ifc.Operator):
elif usage == "LAYER2":
layer2_objs.append(obj)
if element.ConnectedTo or element.ConnectedFrom:
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATSTART")
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATEND")
ifcopenshell.api.run("geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATPATH")
ifcopenshell.api.run(
"geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATSTART"
)
ifcopenshell.api.run(
"geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATEND"
)
ifcopenshell.api.run(
"geometry.disconnect_path", tool.Ifc.get(), element=element, connection_type="ATPATH"
)
rotate_matrix = Matrix.Rotation(pi / 2, 4, self.axis)
obj.matrix_world @= rotate_matrix
bpy.context.view_layer.update()
@@ -992,7 +1006,7 @@ class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator):
y_axis = Vector((0, 0, 1))
# making sure z_axis != y_axis
if z_axis == y_axis:
y_axis = Vector((0,1,0))
y_axis = Vector((0, 1, 0))
x_axis = y_axis.cross(z_axis).normalized()
y_axis = z_axis.cross(x_axis).normalized()
@@ -147,7 +147,9 @@ class BIMModelProperties(PropertyGroup):
("STAIR", "Stair", "Parametric stair"),
("RAILING", "Railing", "Parametric railing"),
("ROOF", "Roof", "Parametric roof"),
("DISTRIBUTION_SEGMENT_RECTANGULAR", "Rectangular Distribution Segment", "Works similarly to Profile, has distribution ports"),
("DISTRIBUTION_SEGMENT_CIRCULAR", "Circular Distribution Segment", "Works similarly to Profile, has distribution ports"),
("DISTRIBUTION_SEGMENT_CIRCULAR_HOLLOW", "Circular Hollow Distribution Segment", "Works similarly to Profile, has distribution ports"),
),
name="Type Template",
default="MESH",
@@ -278,7 +278,7 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
axis = "AXIS3"
ifcopenshell.api.run("pset.edit_pset", ifc_file, pset=pset, properties={"LayerSetDirection": axis})
elif template == "PROFILESET":
elif template == "PROFILESET" or template.startswith("DISTRIBUTION_SEGMENT_"):
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(ifc_file)
obj = bpy.data.objects.new(name, None)
element = blenderbim.core.root.assign_class(
@@ -298,13 +298,45 @@ class AddType(bpy.types.Operator, tool.Ifc.Operator):
else:
material = self.add_default_material()
named_profiles = [p for p in ifc_file.by_type("IfcProfileDef") if p.ProfileName]
if named_profiles:
profile = named_profiles[0]
if template == "PROFILESET":
if named_profiles:
profile = named_profiles[0]
else:
size = 0.5 / unit_scale
profile = ifc_file.create_entity(
"IfcRectangleProfileDef", ProfileName="New Profile", ProfileType="AREA", XDim=size, YDim=size
)
else:
size = 0.5 / unit_scale
profile = ifc_file.create_entity(
"IfcRectangleProfileDef", ProfileName="New Profile", ProfileType="AREA", XDim=size, YDim=size
)
# NOTE: defaults dims are in meters / mm
if template == "DISTRIBUTION_SEGMENT_RECTANGULAR":
default_x_dim = 0.4 / unit_scale
default_y_dim = 0.2 / unit_scale
profile_name = f"{ifc_class}-{default_x_dim*1000}x{default_x_dim*1000}"
profile = ifc_file.create_entity(
"IfcRectangleProfileDef",
ProfileName=profile_name,
ProfileType="AREA",
XDim=default_x_dim,
YDim=default_y_dim,
)
elif template == "DISTRIBUTION_SEGMENT_CIRCULAR":
default_diameter = 0.1 / unit_scale
profile_name = f"{ifc_class}-{default_diameter*1000}"
profile = ifc_file.create_entity(
"IfcCircleProfileDef", ProfileName=profile_name, ProfileType="AREA", Radius=default_diameter / 2
)
elif template == "DISTRIBUTION_SEGMENT_CIRCULAR_HOLLOW":
default_diameter = 0.15 / unit_scale
default_thickness = 0.005 / unit_scale # TODO: double check
profile_name = f"{ifc_class}-{default_diameter*1000}x{default_thickness*1000}"
profile = ifc_file.create_entity(
"IfcCircleHollowProfileDef",
ProfileName=profile_name,
ProfileType="AREA",
Radius=default_diameter / 2,
WallThickness=default_thickness,
)
rel = ifcopenshell.api.run(
"material.assign_material", ifc_file, product=element, type="IfcMaterialProfileSet"
)
+10
View File
@@ -748,3 +748,13 @@ class Model(blenderbim.core.tool.Model):
return
pset_data["data_dict"] = json.loads(pset_data.get("Data", "[]") or "[]")
return pset_data
@classmethod
def edit_element_placement(cls, element, matrix):
"""Useful for moving objects like ports or openings -
the method will ensure it will be moved in blender scene too if it exists"""
obj = tool.Ifc.get_object(element)
if obj:
obj.matrix_world = matrix
return
tool.Ifc.run("geometry.edit_object_placement", product=element, matrix=matrix, is_si=True)