diff --git a/src/bonsai/bonsai/bim/module/cad/operator.py b/src/bonsai/bonsai/bim/module/cad/operator.py index bef1d851af..5d74857822 100644 --- a/src/bonsai/bonsai/bim/module/cad/operator.py +++ b/src/bonsai/bonsai/bim/module/cad/operator.py @@ -345,9 +345,17 @@ class CadArcFrom3Points(bpy.types.Operator): class CadOffset(bpy.types.Operator): bl_idname = "bim.cad_offset" bl_label = "CAD Offset" - bl_description = "Copy selected mesh geometry at provided offset. Mesh copied based on the current viewport angle." + bl_description = ( + "Offset selected mesh geometry at provided distance, based on the current viewport angle. " + "Creates a copy by default, or moves the existing edges if Copy is disabled." + ) bl_options = {"REGISTER", "UNDO"} distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE") + copy: bpy.props.BoolProperty( + name="Copy", + description="Create a new offset copy of the geometry. If disabled, move the existing edges to the offset location", + default=True, + ) @classmethod def poll(cls, context): @@ -405,6 +413,11 @@ class CadOffset(bpy.types.Operator): rotation = Matrix.Rotation(pi / 2, 2, "Z") rotation_i = Matrix.Rotation(-pi / 2, 2, "Z") + # When not copying, the offset positions are gathered here and applied to + # the existing verts only after all loops are processed, so that the + # original coordinates are still available while computing offsets. + moved_verts = [] + # Create loops from edges loop_edges = set(edges) loops = [] @@ -517,12 +530,15 @@ class CadOffset(bpy.types.Operator): offset_length = self.distance / sqrt((1 + normals[0].dot(normals[1])) / 2) offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ (new_normal * offset_length).to_3d()) new_vert = v1.co + offset - new_verts.append(bm.verts.new(new_vert)) else: normal = (normals[0] * self.distance).to_3d() offset = mw.inverted().to_quaternion() @ (wp.to_quaternion() @ normal) new_vert = v1.co + offset + + if self.copy: new_verts.append(bm.verts.new(new_vert)) + else: + moved_verts.append((v1, new_vert)) processed_verts.add(v1.index) @@ -531,9 +547,14 @@ class CadOffset(bpy.types.Operator): v1 = v2 - [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] - if is_closed: - bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) + if self.copy: + [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)] + if is_closed: + bm.edges.new((new_verts[len(new_verts) - 1], new_verts[0])) + + # Move the existing edges to the offset location. + for vert, new_co in moved_verts: + vert.co = new_co bm.verts.index_update() bm.edges.index_update() diff --git a/src/bonsai/bonsai/bim/module/cad/prop.py b/src/bonsai/bonsai/bim/module/cad/prop.py index 7dab36df91..aee4b9d55f 100644 --- a/src/bonsai/bonsai/bim/module/cad/prop.py +++ b/src/bonsai/bonsai/bim/module/cad/prop.py @@ -27,6 +27,11 @@ class BIMCadProperties(PropertyGroup): resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1) radius: bpy.props.FloatProperty(name="Radius", default=0.1, subtype="DISTANCE") distance: bpy.props.FloatProperty(name="Distance", default=0.1, subtype="DISTANCE") + copy: bpy.props.BoolProperty( + name="Copy", + description="Create a new offset copy of the geometry. If disabled, move the existing edges to the offset location", + default=True, + ) x: bpy.props.FloatProperty(name="X", default=0.2, subtype="DISTANCE") y: bpy.props.FloatProperty(name="Y", default=0.1, subtype="DISTANCE") gable_roof_edge_angle: bpy.props.FloatProperty( @@ -37,6 +42,7 @@ class BIMCadProperties(PropertyGroup): resolution: int radius: float distance: float + copy: bool x: float y: float gable_roof_edge_angle: float diff --git a/src/bonsai/bonsai/bim/module/cad/workspace.py b/src/bonsai/bonsai/bim/module/cad/workspace.py index 24fb98ba3f..270c7f70b9 100644 --- a/src/bonsai/bonsai/bim/module/cad/workspace.py +++ b/src/bonsai/bonsai/bim/module/cad/workspace.py @@ -256,6 +256,8 @@ class CadHotkey(bpy.types.Operator): elif self.hotkey == "S_O": row = self.layout.row() row.prop(props, "distance") + row = self.layout.row() + row.prop(props, "copy") elif self.hotkey == "S_R": if tool.Geometry.is_profile_object_active(): @@ -291,7 +293,7 @@ class CadHotkey(bpy.types.Operator): bpy.ops.bim.cad_fillet(resolution=self.props.resolution, radius=self.props.radius) def hotkey_S_O(self): - bpy.ops.bim.cad_offset(distance=self.props.distance) + bpy.ops.bim.cad_offset(distance=self.props.distance, copy=self.props.copy) def hotkey_S_Q(self): obj = bpy.context.active_object