mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
Skeleton of "add fitting" operator which decides appropriate fitting type
This commit is contained in:
@@ -31,6 +31,7 @@ from . import (
|
||||
stair,
|
||||
window,
|
||||
opening,
|
||||
mep,
|
||||
pie,
|
||||
workspace,
|
||||
profile,
|
||||
@@ -105,6 +106,7 @@ classes = (
|
||||
space.GenerateSpace,
|
||||
space.GenerateSpacesFromWalls,
|
||||
space.ToggleSpaceVisibility,
|
||||
mep.FitFlowSegments,
|
||||
prop.BIMModelProperties,
|
||||
prop.BIMArrayProperties,
|
||||
prop.BIMStairProperties,
|
||||
|
||||
@@ -39,7 +39,92 @@ V = lambda *x: Vector([float(i) for i in x])
|
||||
float_is_zero = lambda f: 0.0001 >= f >= -0.0001
|
||||
|
||||
|
||||
class MEPGenerator:
|
||||
class FitFlowSegments(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.fit_flow_segments"
|
||||
bl_label = "Fit Flow Segments"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
|
||||
def _execute(self, context):
|
||||
selected_objs = []
|
||||
selected_profiles = []
|
||||
|
||||
selected_class = None
|
||||
for obj in context.selected_objects:
|
||||
element = tool.Ifc.get_entity(obj)
|
||||
if element and element.is_a("IfcFlowSegment"):
|
||||
if selected_class and not element.is_a(selected_class):
|
||||
return # The user is mixing up ducts and pipes.
|
||||
profile = tool.Model.get_flow_segment_profile(element)
|
||||
if profile:
|
||||
selected_profiles.append(profile)
|
||||
selected_objs.append(obj)
|
||||
selected_class = element.is_a()
|
||||
|
||||
total_selected_objs = len(selected_objs)
|
||||
total_profiles = len(set(selected_profiles))
|
||||
fitting_type = None
|
||||
|
||||
if total_selected_objs == 1:
|
||||
fitting_type = "OBSTRUCTION"
|
||||
elif total_selected_objs == 2:
|
||||
# Shorten the axis by the profile size to allow for fuzzy intersections
|
||||
# e.g. if two ducts touch, we want a bend, not a cross.
|
||||
|
||||
axis1 = tool.Model.get_flow_segment_axis(selected_objs[0])
|
||||
profile_size = max(selected_objs[0].dimensions.x, selected_objs[0].dimensions.y)
|
||||
offset = (axis1[1] - axis1[0]).normalized() * profile_size
|
||||
axis1 = (axis1[0] + offset, axis1[1] - offset)
|
||||
|
||||
axis2 = tool.Model.get_flow_segment_axis(selected_objs[1])
|
||||
profile_size = max(selected_objs[1].dimensions.x, selected_objs[1].dimensions.y)
|
||||
offset = (axis2[1] - axis2[0]).normalized() * profile_size
|
||||
axis2 = (axis2[0] + offset, axis2[1] - offset)
|
||||
|
||||
angle = tool.Cad.angle_edges(axis1, axis2, signed=False, degrees=True)
|
||||
is_parallel = tool.Cad.is_x(angle, (0, 180), tolerance=0.001)
|
||||
|
||||
if total_profiles == 1:
|
||||
if is_parallel:
|
||||
return
|
||||
intersect1, intersect2 = tool.Cad.intersect_edges(axis1, axis2)
|
||||
is_on_axis1 = tool.Cad.is_point_on_edge(intersect1, axis1)
|
||||
is_on_axis2 = tool.Cad.is_point_on_edge(intersect2, axis2)
|
||||
if not is_on_axis1 and not is_on_axis2:
|
||||
fitting_type = "BEND"
|
||||
elif is_on_axis1 and is_on_axis2:
|
||||
fitting_type = "CROSS"
|
||||
else:
|
||||
fitting_type = "TEE"
|
||||
elif total_profiles == 2:
|
||||
if is_parallel:
|
||||
fitting_type = "TRANSITION"
|
||||
elif total_selected_objs == 3:
|
||||
if total_profiles > 1:
|
||||
return
|
||||
|
||||
axis1 = tool.Model.get_flow_segment_axis(selected_objs[0])
|
||||
axis2 = tool.Model.get_flow_segment_axis(selected_objs[1])
|
||||
axis3 = tool.Model.get_flow_segment_axis(selected_objs[2])
|
||||
|
||||
angle12 = tool.Cad.angle_edges(axis1, axis2, signed=False, degrees=True)
|
||||
angle13 = tool.Cad.angle_edges(axis1, axis3, signed=False, degrees=True)
|
||||
angle21 = tool.Cad.angle_edges(axis2, axis1, signed=False, degrees=True)
|
||||
angle23 = tool.Cad.angle_edges(axis2, axis3, signed=False, degrees=True)
|
||||
is_parallel12 = tool.Cad.is_x(angle12, (0, 180), tolerance=0.001)
|
||||
is_parallel13 = tool.Cad.is_x(angle13, (0, 180), tolerance=0.001)
|
||||
is_parallel21 = tool.Cad.is_x(angle21, (0, 180), tolerance=0.001)
|
||||
is_parallel23 = tool.Cad.is_x(angle23, (0, 180), tolerance=0.001)
|
||||
|
||||
if not all(is_parallel12, is_parallel13, is_parallel21, is_parallel23):
|
||||
fitting_type = "WYE"
|
||||
|
||||
if not fitting_type:
|
||||
return
|
||||
|
||||
print(fitting_type)
|
||||
|
||||
|
||||
class MepGenerator:
|
||||
def __init__(self, relating_type=None):
|
||||
self.relating_type = relating_type
|
||||
|
||||
|
||||
@@ -318,10 +318,20 @@ class BimToolUI:
|
||||
op.depth = cls.props.extrusion_depth
|
||||
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
|
||||
if AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegment",
|
||||
"IfcCableSegment",
|
||||
"IfcDuctSegment",
|
||||
"IfcPipeSegment",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Add Fitting", "S_F", "")
|
||||
else:
|
||||
add_layout_hotkey_operator(cls.layout, "Edit Axis", "A_E", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Butt", "S_T", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Mitre", "S_Y", "")
|
||||
add_layout_hotkey_operator(cls.layout, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__)
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.recalculate_profile.__doc__)
|
||||
row.operator("bim.extend_profile", icon="X", text="").join_type = ""
|
||||
|
||||
@@ -358,14 +368,6 @@ class BimToolUI:
|
||||
elif AuthoringData.data["active_class"] in ("IfcSpace",):
|
||||
add_layout_hotkey_operator(cls.layout, "Regen", "S_G", bpy.ops.bim.generate_space.__doc__)
|
||||
|
||||
elif AuthoringData.data["active_class"] in (
|
||||
"IfcCableCarrierSegmentType",
|
||||
"IfcCableSegmentType",
|
||||
"IfcDuctSegmentType",
|
||||
"IfcPipeSegmentType",
|
||||
):
|
||||
add_layout_hotkey_operator(cls.layout, "Extend", "S_E", "")
|
||||
|
||||
elif (
|
||||
(RoofData.is_loaded or not RoofData.load())
|
||||
and RoofData.data["pset_data"]
|
||||
@@ -631,6 +633,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bpy.ops.bim.flip_wall()
|
||||
elif self.active_class in ("IfcWindow", "IfcWindowStandardCase", "IfcDoor", "IfcDoorStandardCase"):
|
||||
bpy.ops.bim.flip_fill()
|
||||
elif self.active_class in ("IfcDuctSegment", "IfcPipeSegment", "IfcCableCarrierSegment", "IfcCableSegment"):
|
||||
bpy.ops.bim.fit_flow_segments()
|
||||
|
||||
def hotkey_S_G(self):
|
||||
if not bpy.context.selected_objects:
|
||||
|
||||
@@ -495,6 +495,17 @@ class Model(blenderbim.core.tool.Model):
|
||||
items.append(item.FirstOperand)
|
||||
return booleans
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_axis(cls, obj):
|
||||
z_values = [v[2] for v in obj.bound_box]
|
||||
return (obj.matrix_world @ Vector((0, 0, min(z_values))), obj.matrix_world @ Vector((0, 0, max(z_values))))
|
||||
|
||||
@classmethod
|
||||
def get_flow_segment_profile(cls, element):
|
||||
material = ifcopenshell.util.element.get_material(element, should_skip_usage=True)
|
||||
if material and material.is_a("IfcMaterialProfileSet") and len(material.MaterialProfiles) == 1:
|
||||
return material.MaterialProfiles[0].Profile
|
||||
|
||||
@classmethod
|
||||
def get_usage_type(cls, element):
|
||||
material = ifcopenshell.util.element.get_material(element, should_inherit=False)
|
||||
|
||||
Reference in New Issue
Block a user