Fix CAD arc bug where sometimes arc went the wrong way, and now auto-clean verts when creating arc

This commit is contained in:
Dion Moult
2022-09-28 11:02:17 +10:00
parent 2e5ce6b862
commit 1c0fa13f39
3 changed files with 83 additions and 96 deletions
@@ -299,51 +299,38 @@ class CadArcFrom3Points(bpy.types.Operator):
mesh = obj.data mesh = obj.data
bm = bmesh.from_edit_mesh(mesh) bm = bmesh.from_edit_mesh(mesh)
selected_verts = [v for v in bm.verts if v.select] arc = [v for v in bm.verts if v.select]
if len(selected_verts) != 3: if len(arc) != 3:
return {"CANCELLED"} return {"CANCELLED"}
pts = [v.co for v in selected_verts] elif len([e for e in bm.edges if e.select]) != 2:
center = tool.Cad.get_center_of_arc(pts, obj)
if not center:
return {"CANCELLED"} return {"CANCELLED"}
bpy.context.scene.cursor.location = center sorted_arc = [None, None, None]
if self.only_recalculate_center: for v1 in arc:
return {"FINISHED"} connections = 0
for link_edge in v1.link_edges:
v2 = link_edge.other_vert(v1)
if v2 in arc:
connections += 1
if connections == 2: # Midpoint
sorted_arc[1] = v1
else:
sorted_arc[2 if sorted_arc[2] is None else 0] = v1
center = obj.matrix_world.inverted() @ center points = [tuple(v.co) for v in sorted_arc]
verts, _ = tool.Cad.create_arc_segments(points, num_verts=(self.resolution * 4) + 1, make_edges=False)
def get_distance_to_other_points(vert): bm.verts.index_update()
other_verts = [v for v in selected_verts if v != vert] bm.edges.index_update()
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 index_offset = len(bm.verts)
# arc, the start and end verts can be identified by being furthest away new_verts = [bm.verts.new(v) for v in verts]
# from the other 2 points. new_edges = [bm.edges.new((new_verts[i], new_verts[i + 1])) for i in range(len(new_verts) - 1)]
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() bm.verts.remove(sorted_arc[1])
dir2 = (arc_end_verts[1].co - center).normalized() bmesh.ops.remove_doubles(bm, verts=new_verts + [sorted_arc[0], sorted_arc[2]], dist=1e-5)
# 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) bmesh.update_edit_mesh(mesh)
mesh.update()
return {"FINISHED"} return {"FINISHED"}
@@ -913,7 +913,7 @@ class DecorationsHandler:
centroid = tool.Cad.get_center_of_arc(points) centroid = tool.Cad.get_center_of_arc(points)
if centroid: if centroid:
arc_centroids.append(tuple(centroid)) arc_centroids.append(tuple(centroid))
arc_segments.append(self.create_arc_segments(pts=points, num_verts=17, make_edges=True)) arc_segments.append(tool.Cad.create_arc_segments(pts=points, num_verts=17, make_edges=True))
batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids}) batch = batch_for_shader(self.shader, "POINTS", {"pos": arc_centroids})
self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1)) self.shader.uniform_float("color", (0.2, 0.2, 0.2, 1))
@@ -987,56 +987,3 @@ class DecorationsHandler:
listEdg.append((Vertices - 1, 0)) listEdg.append((Vertices - 1, 0))
return points, listEdg return points, listEdg
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
# This function is taken from Sverchok's generate_3PT_mode_1 function, licensed under GPL v2-or-later.
# No functional modifications have been made.
def create_arc_segments(self, pts=None, num_verts=20, make_edges=False):
"""
Arc from [start - through - end]
- call this function only if you have 3 pts,
- do your error checking before passing to it.
"""
num_verts -= 1
verts, edges = [], []
V = Vector
# 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 = mathutils.geometry.normal(v1, v2, v4)
mat_rot = mathutils.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 = mathutils.geometry.intersect_line_line(v1_, v2_, v3_, v4_)
if r:
# do arc
p1, _ = r
# find arc angle.
a = (v1 - p1).angle((v4 - p1), 0)
s = (2 * math.pi) - a
interior_angle = (v1 - v2).angle(v4 - v3, 0)
if interior_angle > 0.5 * math.pi:
s = math.pi + 2 * (0.5 * math.pi - interior_angle)
for i in range(num_verts + 1):
mat_rot = mathutils.Matrix.Rotation(((s / num_verts) * i), 4, axis)
vec = ((v4 - p1) @ mat_rot) + p1
verts.append(vec[:])
else:
# do straight line
step_size = 1 / num_verts
verts = [v1_.lerp(v4_, i * step_size)[:] for i in range(num_verts + 1)]
if make_edges:
edges = [(n, n + 1) for n in range(len(verts) - 1)]
return verts, edges
+59 -6
View File
@@ -33,9 +33,8 @@ import sys
import bpy import bpy
import math import math
import bmesh import bmesh
import mathutils.geometry
from mathutils import Vector, Matrix, geometry from mathutils import Vector, Matrix, geometry
from mathutils.geometry import intersect_line_line as LineIntersect
from mathutils.geometry import intersect_point_line as PtLineIntersect
VTX_PRECISION = 1.0e-5 VTX_PRECISION = 1.0e-5
@@ -49,7 +48,7 @@ class Cad:
> edge: tuple of 2 vectors > edge: tuple of 2 vectors
< returns: True / False if a point happens to lie on an edge < returns: True / False if a point happens to lie on an edge
""" """
pt, _percent = PtLineIntersect(p, *edge) pt, _percent = mathutils.geometry.intersect_point_line(p, *edge)
on_line = (pt - p).length < VTX_PRECISION on_line = (pt - p).length < VTX_PRECISION
return on_line and (0.0 <= _percent <= 1.0) return on_line and (0.0 <= _percent <= 1.0)
@@ -60,7 +59,7 @@ class Cad:
> edge: tuple of 2 vectors > edge: tuple of 2 vectors
< returns: a vector of the closest point on that edge < returns: a vector of the closest point on that edge
""" """
return PtLineIntersect(p, *edge)[0] return mathutils.geometry.intersect_point_line(p, *edge)[0]
@classmethod @classmethod
def edge_percent(cls, p, edge): def edge_percent(cls, p, edge):
@@ -68,7 +67,7 @@ class Cad:
> takes a point, and a edge as a tuple of 2 vectors > takes a point, and a edge as a tuple of 2 vectors
< returns the percentage between 0 and 1 of where the point lies on the edge < returns the percentage between 0 and 1 of where the point lies on the edge
""" """
return PtLineIntersect(p, *edge)[1] return mathutils.geometry.intersect_point_line(p, *edge)[1]
@classmethod @classmethod
def angle_edges(cls, edge1, edge2, degrees=False, signed=False): def angle_edges(cls, edge1, edge2, degrees=False, signed=False):
@@ -105,7 +104,7 @@ class Cad:
< returns output of intersect_line_line < returns output of intersect_line_line
""" """
[p1, p2], [p3, p4] = edge1, edge2 [p1, p2], [p3, p4] = edge1, edge2
return LineIntersect(p1, p2, p3, p4) return mathutils.geometry.intersect_line_line(p1, p2, p3, p4)
@classmethod @classmethod
def get_intersection(cls, edge1, edge2): def get_intersection(cls, edge1, edge2):
@@ -380,3 +379,57 @@ class Cad:
return cp return cp
else: else:
print("not on a circle") print("not on a circle")
# https://github.com/nortikin/sverchok/blob/master/nodes/generator/basic_3pt_arc.py
# This function is taken from Sverchok's generate_3PT_mode_1 function, licensed under GPL v2-or-later.
# No functional modifications have been made.
@classmethod
def create_arc_segments(cls, pts=None, num_verts=20, make_edges=False):
"""
Arc from [start - through - end]
- call this function only if you have 3 pts,
- do your error checking before passing to it.
"""
num_verts -= 1
verts, edges = [], []
V = Vector
# 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 = mathutils.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 = mathutils.geometry.intersect_line_line(v1_, v2_, v3_, v4_)
if r:
# do arc
p1, _ = r
# find arc angle.
a = (v1 - p1).angle((v4 - p1), 0)
s = (2 * math.pi) - a
interior_angle = (v1 - v2).angle(v4 - v3, 0)
if interior_angle > 0.5 * math.pi:
s = math.pi + 2 * (0.5 * math.pi - interior_angle)
for i in range(num_verts + 1):
mat_rot = Matrix.Rotation(((s / num_verts) * i), 4, axis)
vec = ((v4 - p1) @ mat_rot) + p1
verts.append(vec[:])
else:
# do straight line
step_size = 1 / num_verts
verts = [v1_.lerp(v4_, i * step_size)[:] for i in range(num_verts + 1)]
if make_edges:
edges = [(n, n + 1) for n in range(len(verts) - 1)]
return verts, edges