def intersect(self,b) : if b.__class__ == Line : return b.intersect(self) else : # taken from http://paulbourke.net/geometry/2circle/ if (self.st-b.st).l2()<1e-10 and (self.end-b.end).l2()<1e-10 : return [self.st,self.end] r0 = self.r r1 = b.r P0 = self.c P1 = b.c d2 = (P0-P1).l2() d = sqrt(d2) if d>r0+r1 or r0+r1<=0 or d2<(r0-r1)**2 : return [] if d2==0 and r0==r1 : return self.check_intersection( b.check_intersection( [self.st, self.end, b.st, b.end] ) ) if d == r0+r1 : return self.check_intersection( b.check_intersection( [P0 + (P1 - P0)*r0/(r0+r1)] ) ) else: a = (r0**2 - r1**2 + d2)/(2.*d) P2 = P0 + a*(P1-P0)/d h = r0**2-a**2 h = sqrt(h) if h>0 else 0. return self.check_intersection(b.check_intersection( [ P([P2.x+h*(P1.y-P0.y)/d, P2.y-h*(P1.x-P0.x)/d]), P([P2.x-h*(P1.y-P0.y)/d, P2.y+h*(P1.x-P0.x)/d]), ] ))
def run(self): s = self.options.s.split(";") node = None if len(self.selected) > 0: node = self.selected.itervalues().next() if node.tag == '{http://www.w3.org/2000/svg}path': self.path = CSP(node) else: error("Select a path or select nothing to create new path!") try: self.p = self.path.items[-1].points[-1][1] except: self.path = CSP([[[ P(self.view_center), P(self.view_center), P(self.view_center) ]]], clean=False) self.p = self.path.items[-1].points[-1][1] self.last_command = None for a in s: self.parse_command(a) try: self.p = self.path.items[-1].points[-1][1] except: pass if node == None: self.path.draw(group=self.current_layer) else: node.set("d", self.path.to_string())
def arc_to_xyij(self, a, x, y, i, j): # a=2|3 => a*2-5=-1|1 end = P(x, y) st = self.p c = st + P(i, j) r1, r2 = (st - c).l2(), (c - end).l2() if abs(r1 - r2) > 1.e-6: # check radiuses at the start/end of the arc r1, r2 = (st - c).mag(), (c - end).mag() subprocess.Popen([ 'zenity', '--warning', '--timeout=3', '--text', ('Arc from %s-%s was replaced by the line because of start and end raduises are not equal.\n' + 'r1=%s, r2=%s, st %s, end %s, center %s') % (st, end, r1, r2, st, end, c) ]) self.line_to(x, y) elif r1 < 1e-6 or r2 < 1e-6: r1, r2 = (st - c).mag(), (c - end).mag() subprocess.Popen([ 'zenity', '--warning', '--timeout=3', '--text', ('Arc from %s-%s was replaced by the line because of radius too small.\n' + 'r1=%s, r2=%s, st %s, end %s, center %s') % (st, end, r1, r2, st, end, c) ]) self.line_to(x, y) else: self.path.items.append(Arc(st, end, c, a * 2 - 5)) self.p = P(x, y) print "Arc to (%s, %s)-(%s, %s)" % (x, y, i, j)
def get_arc_param(self, x, y, a, r, i, j, l): st = self.p c = None # asume not enought params use slope. if x != None or y != None: # using two points and slope. end = P(x if x != None else self.p.x, y if y != None else self.p.y) return self.arc_on_two_points_and_slope(self.p, end, self.slope) if a != None: if l != None: r = l / a / 2 if r != None: c = self.p + self.slope.ccw() * r if i != None or j != None: c = P(i if i != None else self.p.x, j if j != None else self.p.y) if c != None: end = (self.p - c).rotate(a) + c return self.arc_on_two_points_and_slope( self.p, end, self.slope) if l != None: if i != None or j != None: c = P(i if i != None else self.p.x, j if j != None else self.p.y) r = (self.p - c).mag() if r != None: a = l / r / 2 return self.get_arc_param(None, None, a, r, None, None, None) error("To few parametersfor arc. Command: %s" % self.command)
def close(self): if not self.points[0][1].near(self.points[-1][1]): self.points[-1][2].__init__(self.points[-1][1]) self.points[0][0].__init__(self.points[0][1]) self.points.append([ P(self.points[0][1]), P(self.points[0][1]), P(self.points[0][2]) ])
def draw(self, group=None, style=None, layer=None, transform=None, num=0, reverse_angle=None, color=None, width=None): layer, group, transform, reverse_angle = gcodetools.get_preview_group( layer, group, transform) st = P(gcodetools.transform(self.st.to_list(), layer, True)) c = P(gcodetools.transform(self.c.to_list(), layer, True)) a = self.a * reverse_angle r = (st - c) a_st = ((st - c).angle() - pi / 2) % (pi * 2) + pi / 2 r = r.mag() if a > 0: a_end = a_st + a reverse = False else: a_end = a_st a_st = a_st + a reverse = True attr = { 'style': get_style("biarc", i=num, name=style, reverse=reverse, width=width, color=color), inkex.addNS('cx', 'sodipodi'): str(c.x), inkex.addNS('cy', 'sodipodi'): str(c.y), inkex.addNS('rx', 'sodipodi'): str(r), inkex.addNS('ry', 'sodipodi'): str(r), inkex.addNS('start', 'sodipodi'): str(a_st), inkex.addNS('end', 'sodipodi'): str(a_end), inkex.addNS('open', 'sodipodi'): 'true', inkex.addNS('type', 'sodipodi'): 'arc', "gcodetools": "Preview %s" % self, } if transform != []: attr["transform"] = transform inkex.etree.SubElement(group, inkex.addNS('path', 'svg'), attr)
def __init__(self,st,end): #debugger.add_debugger_to_class(self.__class__) if st.__class__ == P : st = st.to_list() if end.__class__ == P : end = end.to_list() self.st = P(st) self.end = P(end) self.l = self.length() if self.l != 0 : self.n = ((self.end-self.st)/self.l).ccw() else: self.n = [0,1]
def intersect(self,b, false_intersection = False) : if b.__class__ == Line : if self.l < 10e-8 or b.l < 10e-8 : return [] v1 = self.end - self.st v2 = b.end - b.st x = v1.x*v2.y - v2.x*v1.y if x == 0 : # lines are parallel res = [] if (self.st.x-b.st.x)*v1.y - (self.st.y-b.st.y)*v1.x == 0: # lines are the same if v1.x != 0 : if 0<=(self.st.x-b.st.x)/v2.x<=1 : res.append(self.st) if 0<=(self.end.x-b.st.x)/v2.x<=1 : res.append(self.end) if 0<=(b.st.x-self.st.x)/v1.x<=1 : res.append(b.st) if 0<=(b.end.x-b.st.x)/v1.x<=1 : res.append(b.end) else : if 0<=(self.st.y-b.st.y)/v2.y<=1 : res.append(self.st) if 0<=(self.end.y-b.st.y)/v2.y<=1 : res.append(self.end) if 0<=(b.st.y-self.st.y)/v1.y<=1 : res.append(b.st) if 0<=(b.end.y-b.st.y)/v1.y<=1 : res.append(b.end) return res else : t1 = ( v2.x*(self.st.y-b.st.y) - v2.y*(self.st.x-b.st.x) ) / x t2 = ( v1.x*(self.st.y-b.st.y) - v1.y*(self.st.x-b.st.x) ) / x if 0<=t1<=1 and 0<=t2<=1 or false_intersection : return [ self.st+v1*t1 ] else : return [] else: # taken from http://mathworld.wolfram.com/Circle-LineIntersection.html x1 = self.st.x - b.c.x x2 = self.end.x - b.c.x y1 = self.st.y - b.c.y y2 = self.end.y - b.c.y dx = x2-x1 dy = y2-y1 D = x1*y2-x2*y1 dr = dx*dx+dy*dy descr = b.r**2*dr-D*D if descr<0 : return [] if descr==0 : return self.check_intersection(b.check_intersection([ P([D*dy/dr+b.c.x,-D*dx/dr+b.c.y]) ])) sign = -1. if dy<0 else 1. descr = sqrt(descr) points = [ P( [ (D*dy+sign*dx*descr)/dr+b.c.x, (-D*dx+abs(dy)*descr)/dr+b.c.y ] ), P( [ (D*dy-sign*dx*descr)/dr+b.c.x, (-D*dx-abs(dy)*descr)/dr+b.c.y ] ) ] if false_intersection : return points else: return self.check_intersection(b.check_intersection( points ))
def bounds(self) : # first get bounds of start/end x1,y1, x2,y2 = ( min(self.st.x,self.end.x),min(self.st.y,self.end.y), max(self.st.x,self.end.x),max(self.st.y,self.end.y) ) # Then check 0,pi/2,pi and 2pi angles. if self.point_Gde_angle(self.c+P(0,self.r)) : y2 = max(y2, self.c.y+self.r) if self.point_inside_angle(self.c+P(0,-self.r)) : y1 = min(y1, self.c.y-self.r) if self.point_inside_angle(self.c+P(-self.r,0)) : x1 = min(x1, self.c.x-self.r) if self.point_inside_angle(self.c+P(self.r,0)) : x2 = max(x2, self.c.x+self.r) return x1,y1, x2,y2
def __init__(self,st,end,c,a,r=None) : # a - arc's angle, it's not defining actual angle before now, but defines direction so it's value does not mather matters only the sign. if st.__class__ == P : st = st.to_list() if end.__class__ == P : end = end.to_list() if c.__class__ == P : c = c.to_list() self.st = P(st) self.end = P(end) self.c = P(c) if r == None : self.r = (P(st)-P(c)).mag() else: self.r = r self.a = ( (self.st-self.c).angle() - (self.end-self.c).angle() ) % pi2 if a>0 : self.a -= pi2 self.a *= -1. self.cp = (self.st-self.c).rot(self.a/2)+self.c # central point of an arc self.l = self.length()
def arc_to_xyr(self, a, x, y, r, in_out=0): end = P(x, y) st = self.p c1 = (st + end) / 2 if (st - end).l2() < 1.e-10: subprocess.Popen([ 'zenity', '--warning', '--timeout=3', '--text', ('Arc from %s-%s was replaced by the line because of start/end point are the same.\n' + 'r=%s, st %s, end %s.') % (r, st, end) ]) self.line_to(x, y) return b = r**2 - (st - end).l2() / 4. if b < 0: subprocess.Popen([ 'zenity', '--warning', '--timeout=3', '--text', ('Arc from %s-%s was replaced by the line because of given radius to small.\n' + 'r=%s, st %s, end %s. Should be at least r=%s') % (st, end, r, st, end, sqrt((st - end).l2() / 4.)) ]) self.line_to(x, y) return n = (end - st).unit() n = n.cw() if a == 2 else n.ccw() c = c1 + n * sqrt(b) c = c - st if in_out > 0: a = 5 - a # swap direction 2,3 -> 3,2 self.arc_to_xyij(a, x, y, c.x, c.y)
def copy(self, st=None, end=None): res = CSPsubpath() if st == None: st = 0 if end == None: end = len(self.points) - 1 for i in range(st, end + 1): cp_ = [] for point in self.points[i]: cp_.append(P(point.x, point.y)) res.points.append(cp_) return res
def to_csp(self): # Taken from cubicsuperpath's ArcToCsp O = self.c sectors = int(abs(self.a) * 2 / pi) + 1 da = self.a / sectors v = 4 * tan(da / 4) / 3 angle = (self.st - self.c).angle() cspsubpath = CSPsubpath() for i in range(sectors + 1): c, s = cos(angle) * self.r, sin(angle) * self.r v1 = P([O.x + c - (-v) * s, O.y + s + (-v) * c]) pt = P([O.x + c, O.y + s]) v2 = P([O.x + c - v * s, O.y + s + v * c]) cspsubpath.points.append([v1, pt, v2]) angle += da cspsubpath.points[0][0] = cspsubpath.points[0][1].copy() cspsubpath.points[-1][2] = cspsubpath.points[-1][1].copy() csp = CSP() csp.items.append(cspsubpath) return csp
def point_d2(self, p): pa = [ P(self.x1, self.y1), P(self.x2, self.y1), P(self.x2, self.y2), P(self.x1, self.y2) ] mind = 1e100 maxd = 0. for pl in pa: l = (p - pl).l2() if l > maxd: maxd = l if self.point_inside(p): mind = 0 else: for i in range(len(pa)): l = Line(pa[i - 1], pa[i]).point_d2(p) if l < mind: mind = l return mind, maxd
def point_inside_angle(self,p,y=None) : # TODO need to be done faster! if y!=None : p = P(p,y) if (p-self.c).l2() != self.r**2 : # p is not on the arc, lets move it there p = self.c+(p-self.c).unit()*self.r warn( (self.cp-self.c).dot(p-self.c),self.r**2, (self.cp-self.c).dot(p-self.c)/self.r**2) try: abs( acos( (self.cp-self.c).dot(p-self.c) /self.r**2 ) ) < abs(self.a/2) except : return True return abs( acos( (self.cp-self.c).dot(p-self.c) /self.r**2 ) ) < abs(self.a/2)
def point(self, i, t): sp1, sp2 = self.cp_to_list(i), self.cp_to_list(i + 1) ax, bx, cx, dx = sp1[1][0], sp1[2][0], sp2[0][0], sp2[1][0] ay, by, cy, dy = sp1[1][1], sp1[2][1], sp2[0][1], sp2[1][1] x1, y1 = ax + (bx - ax) * t, ay + (by - ay) * t x2, y2 = bx + (cx - bx) * t, by + (cy - by) * t x3, y3 = cx + (dx - cx) * t, cy + (dy - cy) * t x4, y4 = x1 + (x2 - x1) * t, y1 + (y2 - y1) * t x5, y5 = x2 + (x3 - x2) * t, y2 + (y3 - y2) * t x, y = x4 + (x5 - x4) * t, y4 + (y5 - y4) * t return P(x, y)
def split(self, i, t=.5): sp1, sp2 = self.cp_to_list(i), self.cp_to_list(i + 1) [x1, y1], [x2, y2], [x3, y3], [x4, y4] = sp1[1], sp1[2], sp2[0], sp2[1] x12 = x1 + (x2 - x1) * t y12 = y1 + (y2 - y1) * t x23 = x2 + (x3 - x2) * t y23 = y2 + (y3 - y2) * t x34 = x3 + (x4 - x3) * t y34 = y3 + (y4 - y3) * t x1223 = x12 + (x23 - x12) * t y1223 = y12 + (y23 - y12) * t x2334 = x23 + (x34 - x23) * t y2334 = y23 + (y34 - y23) * t x = x1223 + (x2334 - x1223) * t y = y1223 + (y2334 - y1223) * t return [[P(sp1[0]), P(sp1[1]), P(x12, y12)], [P(x1223, y1223), P(x, y), P(x2334, y2334)], [P(x34, y34), P(sp2[1]), P(sp2[2])]]
def slope(self, j, t): cp1, cp2 = self.get_segment(j) if self.zerro_segment(j): return P(1., 0.) ax, ay, bx, by, cx, cy, dx, dy = self.parameterize_segment(j) slope = P(3 * ax * t * t + 2 * bx * t + cx, 3 * ay * t * t + 2 * by * t + cy) if slope.l2( ) > 1e-9: #LT changed this from 1e-20, which caused problems, same further return slope.unit() # appears than slope len = 0 (can be at start/end point if control point equals endpoint) if t == 0: # starting point slope = cp2[0] - cp1[1] if slope.l2() > 1e-9: return slope.unit() if t == 1: slope = cp2[1] - cp1[2] if slope.l2() > 1e-9: return slope.unit() # probably segment straight slope = cp2[1] - cp1[1] if slope.l2() > 1e-9: return slope.unit() # probably something went wrong return P(1., 0.)
def get_line_xy(self, x, y, a, l): x1, y1 = self.p.x, self.p.y #warn((x,y,a,l)) if x == None: if y != None: if a != None: a = tan(a) if abs(a) > 16331778728300000: return P(x1, y) if a == 0: error("Bad param a=0 for y defined line. Command: %s" % self.command) return x1 + (y - y1) / a, y elif l != None: try: x = sqrt(l**2 - (y - y1)**2) except: error("Bad params, l to small. Command: %s" % self.command) return P(x, y) else: return P(x1, y) else: if a != None and l != None: return self.p + P(cos(a), sin(a)) * l else: if y != None: return P(x, y) else: if a != None: a = tan(a) if abs(a) > 16331778728300000: error( "Bad param a=pi/2 for x defined line. Command: %s" % self.command) else: return x, a * (x - x1) + y1 elif l != None: try: y = sqrt(l**2 - (x - x1)**2) except: error("Bad params, l to small. Command: %s" % self.command) else: return P(x, y1) error("""Bad params for the line. Command: %s\n x = %s, y = %s, a = %s, l = %s """ % (self.command, x, y, a, l))
def parse_command(self, command): if command.strip() == "": return self.command = command r = re.match( "\s*([almhvALMHV]?)\s*((\s*[alxyrijdALXYRIJD]\s*\-?[\d\.]+)*)\s*", command) if not r: error("Cannot parse command: \"%s\"" % command) r = re.match("\s*([almhvALMHV])\s*([alxyrijdALXYRIJD].*)", command) if r: warn(r.groups()) t, command = r.groups() else: t = self.last_command # parse the parameters x, y, a, l, r, i, j = None, None, None, None, None, None, None try: self.slope = self.path.slope(-1, -1, 1) except: self.slope = P(1., 0.) for p in re.findall("([alxyrijALXYRIJ])\s*(\-?\d*\.?\d*)", command): #warn(p) p = list(p) if p[0] == "A": a = -float(p[1]) / 180 * pi elif p[0] == "a": a = self.slope.angle() - float(p[1]) / 180 * pi else: p[1] = float(p[1]) * self.options.units if p[0] == "x": x = self.p.x + p[1] elif p[0] == "X": x = p[1] elif p[0] == "y": y = self.p.y - p[1] elif p[0] == "Y": y = -p[1] elif p[0] == "i": i = self.p.x + p[1] elif p[0] == "I": I = p[1] elif p[0] == "j": j = self.p.y - p[1] elif p[0] == "J": J = -p[1] elif p[0] in ("r", "R"): r = p[1] elif p[0] in ("d", "D"): r = p[1] / 2 elif p[0] in ("l", "L"): l = p[1] # exec command if t in ("l", "L"): self.draw_line(x, y, a, l) if t in ("a", "A"): self.draw_arc(x, y, a, r, i, j, l) if t in ("m", "M"): self.move_to(x, y, a, l) self.last_command = t
def p(self) : return P(self.x,self.y)
def from_list(self, subpath): self.points = [] for cp in subpath: self.points.append([P(cp[0]), P(cp[1]), P(cp[2])])
def draw_start(self, x, y): self.st = P(x, y) self.p = P(x, y) self.path = LineArc()
def line_to(self, x, y): self.path.items.append(Line(self.p, [x, y])) self.p = P(x, y) print "Line to (%s, %s)" % (x, y)
def get_t_at_point(self, p, y=None) : if y!=None : p = P(p,y) if not self.point_inside_angle(p) : return -1. return abs( acos( (self.st-self.c).dot((p-self.c))/(self.r**2) )/pi ) # consuming all arcs les than 180 deg