mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-26 02:07:36 +00:00
#1153 New arc from 3 points command for 2D CAD
This commit is contained in:
@@ -21,6 +21,7 @@ from . import operator, workspace
|
|||||||
|
|
||||||
classes = (
|
classes = (
|
||||||
operator.CadArcFrom2Points,
|
operator.CadArcFrom2Points,
|
||||||
|
operator.CadArcFrom3Points,
|
||||||
operator.CadFillet,
|
operator.CadFillet,
|
||||||
operator.CadMitre,
|
operator.CadMitre,
|
||||||
operator.CadTrimExtend,
|
operator.CadTrimExtend,
|
||||||
|
|||||||
@@ -140,8 +140,9 @@ class CadFillet(bpy.types.Operator):
|
|||||||
layout.prop(self, prop)
|
layout.prop(self, prop)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
if bpy.context.mode != "EDIT_MESH":
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
return {"CANCELLED"}
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
|
||||||
obj = bpy.context.active_object
|
obj = bpy.context.active_object
|
||||||
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
cursor = obj.matrix_world.inverted() @ bpy.context.scene.cursor.location
|
||||||
mesh = obj.data
|
mesh = obj.data
|
||||||
@@ -205,6 +206,7 @@ class CadFillet(bpy.types.Operator):
|
|||||||
|
|
||||||
bmesh.update_edit_mesh(mesh)
|
bmesh.update_edit_mesh(mesh)
|
||||||
mesh.update()
|
mesh.update()
|
||||||
|
bm.free()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
@@ -212,7 +214,6 @@ class CadArcFrom2Points(bpy.types.Operator):
|
|||||||
bl_idname = "bim.cad_arc_from_2_points"
|
bl_idname = "bim.cad_arc_from_2_points"
|
||||||
bl_label = "CAD Arc from 2 Points"
|
bl_label = "CAD Arc from 2 Points"
|
||||||
bl_options = {"REGISTER", "UNDO"}
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
|
||||||
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||||
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
|
should_flip: bpy.props.BoolProperty(name="Flip", description="Flip arc", default=False)
|
||||||
|
|
||||||
@@ -254,4 +255,81 @@ class CadArcFrom2Points(bpy.types.Operator):
|
|||||||
bmesh.ops.spin(bm, geom=[v], axis=axis, cent=cursor, steps=self.resolution * 4, angle=-angle)
|
bmesh.ops.spin(bm, geom=[v], axis=axis, cent=cursor, steps=self.resolution * 4, angle=-angle)
|
||||||
bmesh.update_edit_mesh(mesh)
|
bmesh.update_edit_mesh(mesh)
|
||||||
mesh.update()
|
mesh.update()
|
||||||
|
bm.free()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|
||||||
|
class CadArcFrom3Points(bpy.types.Operator):
|
||||||
|
bl_idname = "bim.cad_arc_from_3_points"
|
||||||
|
bl_label = "CAD Arc from 3 Points"
|
||||||
|
bl_options = {"REGISTER", "UNDO"}
|
||||||
|
resolution: bpy.props.IntProperty(name="Arc Resolution", min=1, default=1)
|
||||||
|
only_recalculate_center: bpy.props.BoolProperty(name="Only Recalculate Center", default=False)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
obj = context.active_object
|
||||||
|
return obj and obj.type == "MESH" and context.mode == "EDIT_MESH"
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout = self.layout
|
||||||
|
for prop in self.__class__.__annotations__.keys():
|
||||||
|
layout.prop(self, prop)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
bpy.ops.object.mode_set(mode="OBJECT")
|
||||||
|
bpy.ops.object.mode_set(mode="EDIT")
|
||||||
|
|
||||||
|
if bpy.context.mode != "EDIT_MESH":
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
obj = bpy.context.edit_object
|
||||||
|
mesh = obj.data
|
||||||
|
bm = bmesh.from_edit_mesh(mesh)
|
||||||
|
|
||||||
|
selected_verts = [v for v in bm.verts if v.select]
|
||||||
|
if len(selected_verts) != 3:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
pts = [v.co for v in selected_verts]
|
||||||
|
center = tool.Cad.generate_3PT(pts, obj, self.resolution * 4)
|
||||||
|
if not center:
|
||||||
|
return {"CANCELLED"}
|
||||||
|
|
||||||
|
bpy.context.scene.cursor.location = center
|
||||||
|
if self.only_recalculate_center:
|
||||||
|
bm.free()
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def get_distance_to_other_points(vert):
|
||||||
|
other_verts = [v for v in selected_verts if v != vert]
|
||||||
|
total_vector = mathutils.Vector((0, 0, 0))
|
||||||
|
for other_vert in other_verts:
|
||||||
|
total_vector += other_vert.co - vert.co
|
||||||
|
return total_vector.length
|
||||||
|
|
||||||
|
# For three selected points that represent a start, end, and point on an
|
||||||
|
# arc, the start and end verts can be identified by being furthest away
|
||||||
|
# from the other 2 points.
|
||||||
|
arc_end_verts = sorted(selected_verts, key=get_distance_to_other_points)[-2:]
|
||||||
|
normal = mathutils.geometry.normal(pts)
|
||||||
|
|
||||||
|
dir1 = (arc_end_verts[0].co - center).normalized()
|
||||||
|
dir2 = (arc_end_verts[1].co - center).normalized()
|
||||||
|
|
||||||
|
# Let's get the matrix that represents the coordinate system of the arc.
|
||||||
|
# This matrix allows us to get 2D vectors for calculating the signed arc angle.
|
||||||
|
z = normal
|
||||||
|
x = (arc_end_verts[0].co - center).normalized()
|
||||||
|
y = z.cross(x)
|
||||||
|
arc_matrix = mathutils.Matrix([x, y, z]).transposed().to_4x4()
|
||||||
|
|
||||||
|
dir1 = ((arc_matrix.inverted() @ arc_end_verts[0].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||||
|
dir2 = ((arc_matrix.inverted() @ arc_end_verts[1].co) - (arc_matrix.inverted() @ center)).normalized()
|
||||||
|
angle = dir1.xy.angle_signed(dir2.xy)
|
||||||
|
|
||||||
|
v = arc_end_verts[0]
|
||||||
|
bmesh.ops.spin(bm, geom=[v], axis=normal, cent=center, steps=self.resolution * 4, angle=-angle)
|
||||||
|
bmesh.update_edit_mesh(mesh)
|
||||||
|
mesh.update()
|
||||||
|
bm.free()
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class CadTool(WorkSpaceTool):
|
|||||||
# Enable Mesh Tools add-on to get this amazing tool
|
# Enable Mesh Tools add-on to get this amazing tool
|
||||||
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
|
("mesh.offset_edges", {"type": "O", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||||
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}),
|
("bim.cad_arc_from_2_points", {"type": "C", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||||
|
("bim.cad_arc_from_3_points", {"type": "V", "value": "PRESS", "shift": True}, {"properties": []}),
|
||||||
)
|
)
|
||||||
|
|
||||||
def draw_settings(context, layout, tool):
|
def draw_settings(context, layout, tool):
|
||||||
@@ -64,6 +65,10 @@ class CadTool(WorkSpaceTool):
|
|||||||
row.label(text="", icon="EVENT_SHIFT")
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
row.label(text="2-Point Arc", icon="EVENT_C")
|
row.label(text="2-Point Arc", icon="EVENT_C")
|
||||||
|
|
||||||
|
row = layout.row(align=True)
|
||||||
|
row.label(text="", icon="EVENT_SHIFT")
|
||||||
|
row.label(text="3-Point Arc", icon="EVENT_V")
|
||||||
|
|
||||||
|
|
||||||
class CadHotkey(bpy.types.Operator):
|
class CadHotkey(bpy.types.Operator):
|
||||||
bl_idname = "bim.cad_hotkey"
|
bl_idname = "bim.cad_hotkey"
|
||||||
|
|||||||
@@ -25,12 +25,15 @@
|
|||||||
#
|
#
|
||||||
# - Instead of AutoVTX, user explicitly chooses V, T, or X mode
|
# - Instead of AutoVTX, user explicitly chooses V, T, or X mode
|
||||||
# - Instead of adding edges, existing edges are extended
|
# - Instead of adding edges, existing edges are extended
|
||||||
|
# - An arc is reconstructed from 3 points instead of a full circle
|
||||||
|
# - You can now derive the center from an arc without generating geometry
|
||||||
|
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import bpy
|
import bpy
|
||||||
|
import math
|
||||||
import bmesh
|
import bmesh
|
||||||
from mathutils import Vector, geometry
|
from mathutils import Vector, Matrix, geometry
|
||||||
from mathutils.geometry import intersect_line_line as LineIntersect
|
from mathutils.geometry import intersect_line_line as LineIntersect
|
||||||
from mathutils.geometry import intersect_point_line as PtLineIntersect
|
from mathutils.geometry import intersect_point_line as PtLineIntersect
|
||||||
|
|
||||||
@@ -298,3 +301,30 @@ class Cad:
|
|||||||
elif mode == "V":
|
elif mode == "V":
|
||||||
bm = cls.perform_v(bm, point, edges[0], edges[1], (p1, p2, p3, p4), vertex_indices)
|
bm = cls.perform_v(bm, point, edges[0], edges[1], (p1, p2, p3, p4), vertex_indices)
|
||||||
return bm
|
return bm
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_3PT(cls, pts, obj, nv, mode=1):
|
||||||
|
mw = obj.matrix_world
|
||||||
|
V = Vector
|
||||||
|
nv = max(3, nv)
|
||||||
|
|
||||||
|
# construction
|
||||||
|
v1, v2, v3, v4 = V(pts[0]), V(pts[1]), V(pts[1]), V(pts[2])
|
||||||
|
edge1_mid = v1.lerp(v2, 0.5)
|
||||||
|
edge2_mid = v3.lerp(v4, 0.5)
|
||||||
|
axis = geometry.normal(v1, v2, v4)
|
||||||
|
mat_rot = Matrix.Rotation(math.radians(90.0), 4, axis)
|
||||||
|
|
||||||
|
# triangle edges
|
||||||
|
v1_ = ((v1 - edge1_mid) @ mat_rot) + edge1_mid
|
||||||
|
v2_ = ((v2 - edge1_mid) @ mat_rot) + edge1_mid
|
||||||
|
v3_ = ((v3 - edge2_mid) @ mat_rot) + edge2_mid
|
||||||
|
v4_ = ((v4 - edge2_mid) @ mat_rot) + edge2_mid
|
||||||
|
|
||||||
|
r = geometry.intersect_line_line(v1_, v2_, v3_, v4_)
|
||||||
|
if r:
|
||||||
|
p1, _ = r
|
||||||
|
cp = mw @ p1
|
||||||
|
return cp
|
||||||
|
else:
|
||||||
|
print("not on a circle")
|
||||||
|
|||||||
Reference in New Issue
Block a user