I am working on a tool to split geometric entries, currently there is no working splitting for arcs so currently only lines and circles split. You can use this to generate the snapping points. Also implementing tangent construction isnt that hard but havent looked into it.
Like i said a month back kicad really needs these features. Note: i am implementing the intersections without a spatial accelerator. KiCAD has its own functions for this but so far i haven’t been able to wade through the typecasting hell of the python API to be able to use it. So dont select too many entries as the code is a O(N^2) tool but should work fine for dozens in selection.
The code would need to change a bit more to be MIT licensable but still, if you want to use the code as is currently i can post it here:
Licensed under CC BY but i will rewrite one function at some point so it can be MIT at some point
import pcbnew
from functools import wraps
from bisect import insort
from pcbnew import FromMM, ToMM, VECTOR2I, PCB_SHAPE
from math import atan2, acos, sin, cos, pi, sqrt, trunc
class SplitSelection( pcbnew.ActionPlugin ):
def defaults( self ):
self.name = "Split selection alpha 0"
self.category = "Modify PCB"
self.description = "Split selected items by eachother"
def Run( self ):
pcb = pcbnew.GetBoard()
sel = []
for d in pcb.GetDrawings():
if (d.IsSelected()):
try:
obj_shape = d.GetShape()
except AttributeError:
continue
if obj_shape is pcbnew.S_CIRCLE:
sel.append(Circle(d))
if obj_shape is pcbnew.S_SEGMENT:
sel.append(Line(d))
for i in range(len(sel)-1):
for j in range(i+1,len(sel)):
sel[i].splitBy(sel[j])
for i in sel:
i.realize(pcb)
pcbnew.Refresh()
class Circle:
def __init__( self , circle):
self.li = []
self.obj = circle
def __getitem__( self, item ):
return self.li[item]
def append( self, item ):
center = self.obj.GetCenter()
insort(self.li, item, key=lambda a: atan2(a[0]-center[0],a[1]-center[1]))
def splitBy( self, other ):
obj_shape = other.obj.GetShape()
if obj_shape is pcbnew.S_CIRCLE: #and not obj_shape is pcbnew.S_ARC:
a = circle_circle_intersection( self.obj.GetCenter(), self.obj.GetRadius(), other.obj.GetCenter(), other.obj.GetRadius() )
if obj_shape is pcbnew.S_SEGMENT:
a = circle_line_segment_intersection( self.obj.GetCenter(), self.obj.GetRadius(), other.obj.GetStart(), other.obj.GetEnd() )
if obj_shape is pcbnew.S_ARC:
a = circle_circle_intersection( self.obj.GetCenter(), self.obj.GetRadius(), other.obj.GetCenter(), other.obj.GetRadius() )
a = other.acceptPoints(a)
for result in a:
self.append(result)
other.append(result)
def realize(self, pcb):
if len(self.li)<2:
return
center = self.obj.GetCenter()
rad = self.obj.GetRadius()
for i in range(len(self.li)-1):
shape = pcbnew.PCB_SHAPE(pcb, pcbnew.S_ARC)
a = ( atan2( self.li[i][0]-center[0], self.li[i][1]-center[1] ))/2
a += ( atan2( self.li[i+1][0]-center[0], self.li[i+1][1]-center[1] ))/2
shape.SetArcGeometry(
pcbnew.VECTOR2I( int(self.li[i][0]),int(self.li[i][1]) ),
pcbnew.VECTOR2I( int(center[0]+sin(a)*rad), int(center[1]+cos(a)*rad) ),
pcbnew.VECTOR2I( int(self.li[i+1][0]),int(self.li[i+1][1]) )
)
setShapeStyles(shape, self.obj)
pcb.Add(shape)
#~ shape = lineShape( pcb, self.li[i], center)
#~ shape.SetLayer(self.obj.GetLayer())
#~ pcb.Add(shape)
shape = pcbnew.PCB_SHAPE(pcb, pcbnew.S_ARC)
a = ( atan2( self.li[-1][0]-center[0], self.li[-1][1]-center[1] ) )/2
a += ( atan2( self.li[0][0]-center[0], self.li[0][1]-center[1] ) +2*pi )/2
shape.SetArcGeometry(
pcbnew.VECTOR2I( int(self.li[-1][0]),int(self.li[-1][1]) ),
pcbnew.VECTOR2I( int(center[0]+sin(a)*rad), int(center[1]+cos(a)*rad) ),
pcbnew.VECTOR2I( int(self.li[0][0]),int(self.li[0][1]) )
)
setShapeStyles(shape, self.obj)
pcb.Add(shape)
#~ shape = lineShape( pcb, self.li[-1], center)
#~ shape.SetLayer(self.obj.GetLayer())
#~ pcb.Add(shape)
pcb.Remove(self.obj)
class Line:
def __init__( self , line):
self.li = []
self.obj = line
self.append(tuple(line.GetStart()))
self.append(tuple(line.GetEnd()))
def __getitem__( self, item ):
return self.li[item]
def append( self, item ):
insort(self.li, item)
def splitBy( self, other ):
obj_shape = other.obj.GetShape()
a=[]
if obj_shape is pcbnew.S_CIRCLE:
a=circle_line_segment_intersection(other.obj.GetCenter(), other.obj.GetRadius(), self.obj.GetStart(), self.obj.GetEnd() )
if obj_shape is pcbnew.S_SEGMENT:
a=line_line_segment_intersection(self.obj.GetStart(), self.obj.GetEnd(), other.obj.GetStart(), other.obj.GetEnd())
if obj_shape is pcbnew.S_ARC:
a=line_line_segment_intersection(self.obj.GetStart(), self.obj.GetEnd(), other.obj.GetStart(), other.obj.GetEnd())
a = other.acceptPoints(a)
for result in a:
self.append(result)
other.append(result)
def realize(self, pcb):
print(self.li)
for i in range(len(self.li)-1):
shape = lineShape( pcb, self.li[i], self.li[i+1])
setShapeStyles(shape, self.obj)
pcb.Add(shape)
pcb.Remove(self.obj)
SplitSelection().register()
def setShapeStyles(shape, parent):
shape.SetLayer(parent.GetLayer())
shape.SetWidth(parent.GetWidth())
#~ shape.SetDash(parent.GetDash())
def lineShape(pcb, a, b):
shape = pcbnew.PCB_SHAPE(pcb, pcbnew.SHAPE_T_SEGMENT)
shape.SetStart(pcbnew.VECTOR2I( int(a[0]),int(a[1]) ))
shape.SetEnd(pcbnew.VECTOR2I( int(b[0]),int(b[1]) ))
return shape
def circle_circle_intersection( c1, r1 , c2, r2 ):
s = (c1[0] - c2[0], c1[1] - c2[1])
if (s[0]==0 and s[1]==0):
return []
d = sqrt(s[0]*s[0] + s[1]*s[1])
a1 = atan2(s[1], s[0])
q = (float(d)*d+float(r2)*r2 - float(r1)*r1)/(2. * float(d) * float(r2))
if (q<-1 or q>1):
return []
a2 = [a1 + sign *acos(q) for sign in (-1, 1) ];
return [( c2[0]+r2*cos(theta), c2[1]+r2*sin(theta)) for theta in a2]
def line_line_segment_intersection( pt0, pt1, pt2, pt3 ):
s1 = (pt1[0] - pt0[0], pt1[1] - pt0[1])
s2 = (pt3[0] - pt2[0], pt3[1] - pt2[1])
dr = (-s2[0] * s1[1] + s1[0] * s2[1])
if (dr == 0):
return []
s = (-s1[1] * (pt0[0] - pt2[0]) + s1[0] * (pt0[1] - pt2[1])) / dr
t = ( s2[0] * (pt0[1] - pt2[1]) - s2[1] * (pt0[0] - pt2[0])) / dr
if (s > 0 and s < 1 and t > 0 and t < 1):
return [( pt0[0] + t*s1[0], pt0[1] + t*s1[1] )]
return []
def circle_line_segment_intersection(circle_center, circle_radius, pt1, pt2, tangent_tol=1e1):
"""
adapted from https://stackoverflow.com/questions/30844482/
"""
(p1x, p1y), (p2x, p2y), (cx, cy) = pt1, pt2, circle_center
(x1, y1), (x2, y2) = (p1x - cx, p1y - cy), (p2x - cx, p2y - cy)
dx, dy = (x2 - x1), (y2 - y1)
dr = (dx ** 2 + dy ** 2)**.5
big_d = x1 * y2 - x2 * y1
discriminant = circle_radius ** 2 * dr ** 2 - big_d ** 2
if discriminant < 0: # No intersection
return []
else: # There may be 0, 1, or 2 intersections with the segment
intersections = [
(cx + (big_d * dy + sign * (-1 if dy < 0 else 1) * dx * discriminant**.5) / dr ** 2,
cy + (-big_d * dx + sign * abs(dy) * discriminant**.5) / dr ** 2)
for sign in (1, -1) ]
fraction_along_segment = [(xi - p1x) / dx if abs(dx) > abs(dy) else (yi - p1y) / dy for xi, yi in intersections]
intersections = [pt for pt, frac in zip(intersections, fraction_along_segment) if 0 <= frac <= 1]
if len(intersections) == 2 and abs(discriminant) <= tangent_tol: # If line is tangent to circle, return just one point (as both intersections have same location)
return [intersections[0]]
else:
return intersections