mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-09 09:21:46 +00:00
closes #3804: Add S_K split support for profile-based elements
Extends the existing Split hotkey (⇧ K) to work on elements with IfcMaterialProfileSet usage (beams, columns, members) in addition to layered walls. Adds DumbProfileJoiner.split(), a new SplitProfile operator (bim.split_profile), and a Split button in the workspace panel for PROFILE elements. Generated with the assistance of an AI coding tool.
This commit is contained in:
@@ -104,6 +104,7 @@ classes = (
|
||||
profile.ExtendProfile,
|
||||
profile.RecalculateProfile,
|
||||
profile.Rotate90,
|
||||
profile.SplitProfile,
|
||||
profile.PatchNonParametricMepSegment,
|
||||
roof.GenerateHippedRoof,
|
||||
slab.DisableEditingExtrusionProfile,
|
||||
|
||||
@@ -849,6 +849,57 @@ class DumbProfileJoiner:
|
||||
def create_matrix(self, p: Vector, x: Vector, y: Vector, z: Vector) -> Matrix:
|
||||
return Matrix([x, y, z, p]).to_4x4().transposed()
|
||||
|
||||
def split(self, profile1: bpy.types.Object, target: Vector) -> None:
|
||||
element1 = tool.Ifc.get_entity(profile1)
|
||||
if not element1:
|
||||
return
|
||||
|
||||
if tool.Ifc.is_moved(profile1):
|
||||
bonsai.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=profile1)
|
||||
|
||||
axis1 = self.get_profile_axis(profile1)
|
||||
intersect, cut_percentage = mathutils.geometry.intersect_point_line(target, *axis1)
|
||||
if cut_percentage < 0 or cut_percentage > 1 or tool.Cad.is_x(cut_percentage, (0, 1)):
|
||||
return
|
||||
|
||||
# Duplicate the profile element
|
||||
profile2 = profile1.copy()
|
||||
profile2.data = profile2.data.copy()
|
||||
for collection in profile1.users_collection:
|
||||
collection.objects.link(profile2)
|
||||
bonsai.core.root.copy_class(tool.Ifc, tool.Collector, tool.Geometry, tool.Root, obj=profile2)
|
||||
element2 = tool.Ifc.get_entity(profile2)
|
||||
|
||||
# Transfer ATEND connection from element1 to element2
|
||||
relating_element = None
|
||||
relating_connection = None
|
||||
description = None
|
||||
for conn in list(element1.ConnectedTo):
|
||||
if conn.is_a("IfcRelConnectsPathElements") and conn.RelatingConnectionType == "ATEND":
|
||||
relating_element = conn.RelatedElement
|
||||
relating_connection = conn.RelatedConnectionType
|
||||
description = conn.Description
|
||||
bonsai.core.geometry.remove_connection(tool.Geometry, connection=conn)
|
||||
for conn in list(element1.ConnectedFrom):
|
||||
if conn.is_a("IfcRelConnectsPathElements") and conn.RelatedConnectionType == "ATEND":
|
||||
relating_element = conn.RelatingElement
|
||||
relating_connection = conn.RelatingConnectionType
|
||||
description = conn.Description
|
||||
bonsai.core.geometry.remove_connection(tool.Geometry, connection=conn)
|
||||
if relating_element:
|
||||
ifcopenshell.api.geometry.connect_path(
|
||||
tool.Ifc.get(),
|
||||
relating_element=relating_element,
|
||||
related_element=element2,
|
||||
relating_connection=relating_connection,
|
||||
related_connection="ATEND",
|
||||
description=description,
|
||||
)
|
||||
|
||||
# Recreate both profiles with split axes
|
||||
self.recreate_profile(element1, profile1, [axis1[0], intersect], [axis1[0], intersect])
|
||||
self.recreate_profile(element2, profile2, [intersect, axis1[1]], [intersect, axis1[1]])
|
||||
|
||||
def get_profile_axis(self, obj: bpy.types.Object) -> list[Vector]:
|
||||
z_values = [v[2] for v in obj.bound_box]
|
||||
return [
|
||||
@@ -857,6 +908,29 @@ class DumbProfileJoiner:
|
||||
]
|
||||
|
||||
|
||||
class SplitProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.split_profile"
|
||||
bl_label = "Split Profile"
|
||||
bl_options = {"REGISTER", "UNDO"}
|
||||
bl_description = (
|
||||
"Split selected profile element into two elements at the Blender cursor location. "
|
||||
"The cursor must be positioned on the element's axis."
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if not tool.Model.has_selected_ifc_objects():
|
||||
cls.poll_message_set("No IFC objects selected.")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _execute(self, context):
|
||||
selected_objs = tool.Model.get_selected_mesh_objects()
|
||||
for obj in selected_objs:
|
||||
DumbProfileJoiner().split(obj, context.scene.cursor.location)
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class RecalculateProfile(bpy.types.Operator, tool.Ifc.Operator):
|
||||
bl_idname = "bim.recalculate_profile"
|
||||
bl_label = "Recalculate Profile"
|
||||
|
||||
@@ -937,6 +937,14 @@ class EditObjectUI:
|
||||
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||
add_layout_hotkey_operator(row, "Mitre", "S_Y", "", ui_context)
|
||||
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||
add_layout_hotkey_operator(
|
||||
row,
|
||||
"Split",
|
||||
"S_K",
|
||||
"Split selected Element into two Elements at the cursor location\n\nHotkey: ⇧ K",
|
||||
ui_context,
|
||||
)
|
||||
row = cls.layout.row(align=True) if ui_context != "TOOL_HEADER" else row
|
||||
add_layout_hotkey_operator(row, "Rotate 90", "S_R", bpy.ops.bim.rotate_90.__doc__, ui_context)
|
||||
|
||||
else:
|
||||
@@ -1361,6 +1369,8 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator):
|
||||
return
|
||||
if self.active_material_usage == "LAYER2":
|
||||
bpy.ops.bim.split_wall()
|
||||
elif self.active_material_usage == "PROFILE":
|
||||
bpy.ops.bim.split_profile()
|
||||
|
||||
def hotkey_S_T(self):
|
||||
if not bpy.context.selected_objects:
|
||||
|
||||
Reference in New Issue
Block a user