""" (C) Copyright 2013 Rob Watson rmawatson [at] hotmail.com and others. All rights reserved. This program and the accompanying materials are made available under the terms of the GNU Lesser General Public License (LGPL) version 2.1 which accompanies this distribution, and is available at http://www.gnu.org/licenses/lgpl-2.1.html This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. Contributors: Rob Watson ( rmawatson [at] hotmail ) """ from PyQt4.QtCore import * from PyQt4.QtGui import * import platform,sys,os,re,math from math import * from dxfgrabber import * from dxfgrabber.entities import * import threading import Eaglepy class Line(object): def __init__(self,p1,p2): self.points = [p1,p2] def draw(self,color=(1.0,1.0,1.0,1.0)): glBegin(GL_LINES) glColor4f(*color) glVertex2f(self.points[0].x,self.points[0].y) glVertex2f(self.points[1].x,self.points[1].y) glEnd() def normalized(self): return Line(self.points[0],self.points[0] + (self.points[1] - self.points[0]).normalized()) def toLength(self,value): line = self.normalized() line.points[0] -= self.points[0] line.points[1] -= self.points[0] line *= value return Line(self.points[0],line.points[1] + self.points[0]) def rotate(self,theta): return Line(self.points[0],self.points[0] + Point( (self.points[1] - self.points[0]).x * math.cos(theta) - (self.points[1] - self.points[0]).y * math.sin(theta), (self.points[1] - self.points[0]).x * math.sin(theta) + (self.points[1] - self.points[0]).y * math.cos(theta) )) def asScr(self,wirewidth): point1Str = str(self.points[0].x) + " " + str(self.points[0].y) point2Str = str(self.points[1].x) + " " + str(self.points[1].y) return "WIRE " + str(wirewidth) + " (" + point1Str + ") (" + point2Str + ");" def __mul__(self, value): return Line(self.points[0]*value,self.points[1]*value) class Point(object): def __init__(self,x,y): self.x = x self.y = y def draw( self,color=(1.0,1.0,1.0,1.0) ): glPointSize(6) glBegin(GL_POINTS) glColor4f(*color) glVertex2f(self.x, self.y) glEnd() def __eq__(self,other): return (self.x == other.x) & (self.y == other.y) def __mul__(self, value): return Point(self.x*value,self.y*value) def __rmul__(self, value): return self.__mul__(value) def __add__(self,other): return Point(self.x+other.x,self.y+other.y) def __sub__(self,other): return Point(self.x-other.x,self.y-other.y) def __div__(self,value): return Point(self.x/value,self.y/value) def interpolate(self,other,value): p = Point(self.x,self.y) return p + value*(other-self) def distance(self,other): return abs(math.sqrt(math.pow(other.x - self.x,2) + math.pow(other.y - self.y,2))) def length(self): return abs(sqrt(self.x*self.x + self.y*self.y)) def normalized(self): length = self.length() return Point(self.x/length,self.y/length) def __repr__(self): return str(self) def __str__(self): return "(" + str(self.x) + ", " + str(self.y) + ")" def rotate(self,theta,around=None): if not around: around = Point(0,0) return Point( (self - around).x * math.cos(theta) - (self - around).y * math.sin(theta), (self - around).x * math.sin(theta) + (self - around).y * math.cos(theta) ) class BezierCurve(object): def solveQuadratic(self,a,b,c): result = math.pow(b,2)- 4*a*c if result < 0+sys.float_info.epsilon: return () sqroot = math.sqrt( math.pow(b,2) - 4*a*c) return ( (-b-sqroot)/(2*a), (-b+sqroot)/(2*a) ) @classmethod def Construct(cls,p1,p2,p3,p4): COLINEAR_VALUE = 0.0001 #Check for colinear if (p1 == p2 and p3 == p4) or (abs((p1.x*(p2.y-p4.y) + p2.x *(p4.y-p1.y) + p4.x*(p1.y-p2.y))) < COLINEAR_VALUE and \ abs((p1.x*(p3.y-p4.y) + p3.x *(p4.y-p1.y) + p4.x*(p1.y-p3.y))) < COLINEAR_VALUE): return Line(p1,p4) elif p1 == p2: return QuadraticBezierCurve(p2,p3,p4) elif p3 == p4: return QuadraticBezierCurve(p1,p2,p3) else: return CubicBezierCurve(p1,p2,p3,p4) def draw(self,color=(1.0,1.0,1.0,1.0)): glBegin(GL_LINE_STRIP) for i in range(101): glColor4f(*color) point = self.evaluate(i/100.0) glVertex2f(point.x, point.y) glEnd() class QuadraticBezierCurve(BezierCurve): def __init__(self,p1,p2,p3): self.points = [p1,p2,p3] def evaluate(self,value): return (1-value)*((1-value)*self.points[0] +\ value*self.points[1]) + \ value*((1-value)*self.points[1]+ value*self.points[2]) def inflection(self): return [] def moveTo(self,point): moveBy = (self.points[0] - point ) transformedPoints = [] for point in self.points: transformedPoints.append(point - moveBy) return QuadraticBezierCurve(*transformedPoints) def rotate(self,theta,around=None): if not around: around = self.points[0] rotatedPoints = [] for point in self.points: rotatedPoints.append(point.rotate(theta,around)) return QuadraticBezierCurve(*rotatedPoints) def split(self,value): p1,p2,p3 = self.points r1 = p1 r2 = p1+value*(p2-p1) r3 = self.evaluate(value) s1 = r3 s3 = p3 s2 = p3 + (1-value)*(p2-p3) return (BezierCurve.Construct(r1,r2,r3,r3),BezierCurve.Construct(s1,s2,s3,s3)) def findRoots(self,a,b,c): result = self.solveQuadratic((a-2*b+c),(2*b-2*a),a) if not result: return [] return [root for root in result if root >= 0 and root <= 1] def intersect(self,line,onLine=False): theta = -((math.pi/2)-atan2( (line.points[1] - line.points[0]).x, (line.points[1] - line.points[0]).y )) rotatedLine = Line(Point(0,0),line.points[1].rotate(theta,line.points[0]) ) rotatedBezier = QuadraticBezierCurve( self.points[0].rotate(theta,line.points[0]), self.points[1].rotate(theta,line.points[0]), self.points[2].rotate(theta,line.points[0]) ) roots = rotatedBezier.findRoots(*[p.y for p in rotatedBezier.points]) if (onLine): for root in list(roots): point = rotatedBezier.evaluate(root) if min(rotatedLine.points[0].x-sys.float_info.epsilon,rotatedLine.points[1].x-sys.float_info.epsilon) > point.x or max(rotatedLine.points[0].x+sys.float_info.epsilon,rotatedLine.points[1].x+sys.float_info.epsilon) < point.x: roots.remove(root) elif min(rotatedLine.points[0].y-sys.float_info.epsilon,rotatedLine.points[1].y-sys.float_info.epsilon) > point.y or max(rotatedLine.points[0].y+sys.float_info.epsilon,rotatedLine.points[1].y+sys.float_info.epsilon) < point.y: roots.remove(root) result = [] for root in roots: result.append((root,self.evaluate(root),rotatedBezier.evaluate(root))) result.sort(key=lambda item:item[2].x) result.reverse() return result def draw(self,color=(1.0,1.0,1.0,1.0)): self.points[0].draw(RED) self.points[2].draw(RED) BezierCurve.draw(self,color) class CubicBezierCurve(BezierCurve): def __init__(self,p1,p2,p3,p4): self.points = [p1,p2,p3,p4] self.inflectionpnt = None def moveTo(self,point): moveBy = (self.points[0] - point ) transformedPoints = [] for point in self.points: transformedPoints.append(point - moveBy) return CubicBezierCurve(*transformedPoints) def rotate(self,theta,around=None): if not around: around = self.points[0] rotatedPoints = [] for point in self.points: rotatedPoints.append(point.rotate(theta,around)) return CubicBezierCurve(*rotatedPoints) def findRoots(self,a,b,c,d): #First derivative roots (starting points for the Cubic Root Solver. interestValues = [0.0,1.0] quadraticRoots = self.solveQuadratic( (3*d-9*c-3*a+9*b),(6*a-12*b+6*c),(3*b-3*a) ) result = [] if quadraticRoots: result = [root for root in quadraticRoots if root >= 0 and root <= 1] interestValues += result interestValues.append((2*b-a-c)/(3*b-3*c-a+d)) extraValues = [] for index,value in enumerate(interestValues): slope = math.pow((1-value ),3)*a + 3*math.pow((1-value ),2)*value*b + 3*(1-value )*math.pow(value ,2)*c+math.pow(value ,3)*d if slope == 0: interestValues[index] = value - sys.float_info.epsilon extraValues.append( value + sys.float_info.epsilon) interestValues += extraValues roots = [] for startValue in interestValues: value = startValue for index in range(10): nm = math.pow((1-value),3)*a + 3*math.pow((1-value),2)*value*b + 3*(1-value)*math.pow(value,2)*c+math.pow(value,3)*d dn = (3*d-9*c-3*a+9*b)*math.pow(value,2) + (6*a-12*b+6*c)*value + (3*b-3*a) if dn == 0: continue nextValue = value - nm/dn if abs(value - nextValue) < sys.float_info.epsilon: exists = False for existingValue in roots: if abs(existingValue - nextValue) < sys.float_info.epsilon: exists = True break if not exists: roots.append(nextValue) break value = nextValue return roots def intersect(self,line,onLine=False): theta = -((math.pi/2)-atan2( (line.points[1] - line.points[0]).x, (line.points[1] - line.points[0]).y )) rotatedLine = Line(Point(0,0),line.points[1].rotate(theta,line.points[0]) ) rotatedBezier = CubicBezierCurve( self.points[0].rotate(theta,line.points[0]), self.points[1].rotate(theta,line.points[0]), self.points[2].rotate(theta,line.points[0]), self.points[3].rotate(theta,line.points[0])) roots = rotatedBezier.findRoots(*[p.y for p in rotatedBezier.points]) self.lastRotBez = rotatedBezier self.lastRotLine = rotatedLine if (onLine): for root in list(roots): point = rotatedBezier.evaluate(root) if min(rotatedLine.points[0].x-sys.float_info.epsilon,rotatedLine.points[1].x-sys.float_info.epsilon) > point.x or max(rotatedLine.points[0].x+sys.float_info.epsilon,rotatedLine.points[1].x+sys.float_info.epsilon) < point.x: roots.remove(root) elif min(rotatedLine.points[0].y-sys.float_info.epsilon,rotatedLine.points[1].y-sys.float_info.epsilon) > point.y or max(rotatedLine.points[0].y+sys.float_info.epsilon,rotatedLine.points[1].y+sys.float_info.epsilon) < point.y: roots.remove(root) result = [] for root in roots: if (root > 0 and root < 1): result.append((root,self.evaluate(root),rotatedBezier.evaluate(root))) result.sort(key=lambda item:item[2].x) result.reverse() return result def evaluate(self,value): return ( math.pow((1.0-value),3) * self.points[0]) + \ (3*math.pow((1-value),2)*value * self.points[1]) + \ (3*(1-value)*math.pow(value,2) * self.points[2]) + \ (math.pow(value,3) * self.points[3]) def inflection(self): P1,C1,C2,P2 = self.points a = C1 - P1 b = C2 - C1 - a c = P2 - C2 - a -2.0*b a1 = b.x*c.y - b.y*c.x b1 = a.x*c.y - a.y*c.x c1 = a.x*b.y - a.y*b.x value = math.pow(b1,2) - (4*a1*c1) self.inflectionpnts = [] if value >0+sys.float_info.epsilon: ta = ( -b1 - math.sqrt( math.pow(b1,2.0) - 4.0*a1*c1 ) )/(2.0*a1) tb = ( -b1 + math.sqrt( math.pow(b1,2.0) - 4.0*a1*c1 ) )/(2.0*a1) if ta < 1.0 and ta > 0.0: self.inflectionpnts.append(ta) elif tb < 1.0 and tb > 0.0: self.inflectionpnts.append(tb) for index,inflpnt in enumerate(list(self.inflectionpnts)): if inflpnt > (1-0.0000000000001): self.inflectionpnts.remove(inflpnt) elif inflpnt < 0.0000000000001: self.inflectionpnts.remove(inflpnt) return self.inflectionpnts def draw(self,color=(1.0,1.0,1.0,1.0)): self.points[0].draw(RED) self.points[3].draw(RED) Line(self.points[0],self.points[1]).draw(GREEN) Line(self.points[2],self.points[3]).draw(GREEN) if self.inflectionpnt: self.evaluate(self.inflectionpnt).draw() BezierCurve.draw(self,color) def split(self,value): p1,p2,p3,p4 = self.points r1 = p1 r2 = p1+value*(p2-p1) r3 = r2 + value*( (p2 + value*(p3-p2) ) -r2) r4 = self.evaluate(value) s1 = r4 s4 = p4 s3 = p3 + value*(p4-p3) s2 = (s3+((1-value)*((p2 + value*(p3-p2))-s3))) return (CubicBezierCurve(r1,r2,r3,r4),CubicBezierCurve(s1,s2,s3,s4)) class Arc(object): def asScr(self,wireWidth): angleStr = str(-(self.angle * 180/math.pi)) angleStr = ("+" + angleStr) if not angleStr.startswith("-") else angleStr point1Str = str(self.points[0].x) + " " + str(self.points[0].y) point2Str = str(self.points[1].x) + " " + str(self.points[1].y) return "WIRE " + str(wireWidth) + " (" + point1Str + ") " + angleStr + " (" + point2Str + ");" def values(self): return (self.points[0],self.points[1],self.angle) def moveTo(self,point): moveBy = (self.points[0] - point ) transformedPoints = [] for point in self.points: transformedPoints.append(point - moveBy) return Arc(*transformedPoints,angle=self.angle) def rotate(self,theta,around=None): if not around: around = self.points[0] rotatedPoints = [] for point in self.points: rotatedPoints.append(point.rotate(theta,around)) return Arc(*rotatedPoints,angle=self.angle) def dot(self,p1,p2): return (p1.x*p2.x) + (p1.y*p2.y) def __init__(self,p1=None,p2=None,angle=None,tangent=None): if not p1 or not p2: return self.points = [p1,p2] self.angle = angle self.tangent = tangent self.chord = p2- p1 self.recalculate() def recalculate(self): p1,p2 = self.points tangentangle = None if not self.tangent: self.tangent = Point( self.chord.x*cos(self.angle/2) - self.chord.y*sin(self.angle/2), self.chord.x*sin(self.angle/2) + self.chord.y*cos(self.angle/2) ) self.tangent = self.tangent.normalized() dotprod = self.dot(self.tangent,self.chord) dotprod = min(max(dotprod,-0.9999999),0.9999999) tangentangle = acos(dotprod) if tangentangle > math.pi/2: self.chord = p1 -p2 self.center = None self.radius = None dot = lambda p1,p2: (p1.x*p2.x) + (p1.y*p2.y) if (self.angle): self.halfangle = self.angle/2 elif(self.tangent): self.halfangle = math.acos(dot(self.tangent,self.chord) / (self.tangent.length() * self.chord.length())) self.angle = self.halfangle*2 else: raise Exception("Angle or Tangent required") halfChord = self.chord.length()/2 self.radius = halfChord/math.sin(self.halfangle) if tangentangle < math.pi/2: self.center = (Point(self.tangent.y , -self.tangent.x ) * self.radius) else: self.center = (Point(-self.tangent.y , self.tangent.x ) * self.radius) self.center += self.points[1] if tangentangle > math.pi/2 else self.points[0] #self.center.draw(GREEN) self.offsetLine = self.points[0] - self.center self.offsetAngle = math.pi/2 - math.atan2(self.offsetLine.x,self.offsetLine.y) def draw(self,color=(1.0,1.0,1.0,1.0)): degtorad = lambda deg: deg*(math.pi/180) radtodeg = lambda rad: rad*(180/math.pi) glPointSize(5.0); self.points[0].draw(BLUE) self.points[1].draw(BLUE) glBegin(GL_LINE_STRIP) glColor4f(*color) for i in range( int(radtodeg(self.angle*10)) ): glVertex2f((cos(self.offsetAngle-degtorad((float(i)/10)) )*self.radius)+self.center.x,(sin( self.offsetAngle-degtorad( (float(i)/10) ))*self.radius)+self.center.y); glEnd() def incenterIntersect(self,bezier): intersectLine = Line(self.center,self.offsetLine+self.center) intersectLine = intersectLine.toLength(self.radius) intersectLine = intersectLine.rotate(-self.angle) intersection = bezier.intersect(intersectLine) return intersection[0] def deviation(self,bezier): DEVIATION_INCREMENT = 50.0 intersectLine = Line(self.center,self.offsetLine+self.center) intersectLine = intersectLine.toLength(self.radius) lastRotBez = None lastRotLine = None maxAngle = self.angle newLine = intersectLine maxDeviation = (0,(None,None,None)) for angle in range(0,-int(maxAngle*DEVIATION_INCREMENT)+1,-1): currentAngle = angle/DEVIATION_INCREMENT intersection = bezier.intersect(newLine) if intersection: deviation = newLine.points[1].distance(intersection[0][1]) #newLine.draw() if deviation > maxDeviation[0] and intersection[0][0] > 0+0.001 and intersection[0][0] < 1-0.001: lastRotBez = bezier.lastRotBez if hasattr(bezier,"lastRotBez") else None lastRotLine = bezier.lastRotLine if hasattr(bezier,"lastRotLine") else None maxDeviation = (deviation,(currentAngle,intersection[0][1],intersection[0][0])) newLine = intersectLine.rotate(currentAngle) else: continue """ if lastRotBez: lastRotBez.draw() if lastRotLine: lastRotLine.draw() if (lastRotBez): for point in lastRotBez.points: point.draw(GREEN) print "ROOTS",lastRotBez.findRoots(*[point.y for point in lastRotBez.points]) lastRotBez.evaluate(0.9287).draw() """ #print "MAX ANGLE",maxDeviation[1][0] #print "VALUE",maxDeviation[1][2] #maxDeviation[1][1].draw() return maxDeviation class Biarc(object): def __init__(self,bezier): self.bezier = bezier self.calcBezier = bezier.moveTo(Point(0,0)) self.offsetAngle = -atan2(self.calcBezier.points[-1].y,self.calcBezier.points[-1].x) self.calcBezier = self.calcBezier.rotate(self.offsetAngle) self.orderReversed = False if self.calcBezier.points[1].y < 0 : self.orderReversed = True self.calcBezier.points = list(reversed(self.calcBezier.points)) if self.calcBezier.points[1].y < 0: self.offsetAngle -= math.pi self.calcBezier = self.calcBezier.rotate(math.pi) if len(self.bezier.points) == 3: self.isCubic = False self.intersection = self.bezier.points[1] else: self.intersection = None self.calcTangentIntersection() self.calcIncenter() self.calculateArcs() def calcTangentIntersection(self): p1,p2,p3,p4 = self.calcBezier.points xn = (p1.x*p2.y - p1.y*p2.x)*(p3.x - p4.x) - (p1.x - p2.x)*(p3.x*p4.y - p3.y*p4.x) yn = (p1.x*p2.y - p1.y*p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x*p4.y - p3.y*p4.x) dn = (p1.x - p2.x)*(p3.y - p4.y) - (p1.y - p2.y)*(p3.x - p4.x) if not dn: self.intersection = None return self.intersection = Point(xn/dn,yn/dn) def incenterIntersect(self): return self.arc1.incenterIntersect(self.calcBezier) def deviation(self): if not self.arc1 and self.arc2: return -1 return max(self.arc1.deviation(self.calcBezier), self.arc2.deviation(self.calcBezier),key=lambda item:item[0]) def calcIncenter(self): if not self.intersection: self.incenter = None return if len(self.calcBezier.points) == 3: p1,p2,p3 = self.calcBezier.points triPnt1 = p1.interpolate(p3,0.5) triPnt2 = p1.interpolate(p2,0.5) triPnt3 = p2.interpolate(p3,0.5) #triPnt1.draw() #triPnt2.draw() #triPnt3.draw() triLen1 = triPnt1.distance(triPnt3) triLen2 = triPnt1.distance(triPnt2) triLen3 = triPnt2.distance(triPnt3) #Line(triPnt1,triPnt2).draw() #Line(triPnt2,triPnt3).draw() #Line(triPnt3,triPnt1).draw() self.incenter = Point( (triLen1*p1.x + triLen2*p2.x + triLen3 *p3.x)/ (triLen1+triLen2+triLen3), (triLen1*p1.y + triLen2*p2.y + triLen3 *p3.y)/ (triLen1+triLen2+triLen3) ) """ self.incenter = Point( (triLen1*triPnt1.x + triLen2*triPnt2.x + triLen3 *triPnt3.x)/ (triLen1+triLen2+triLen3), (triLen1*triPnt1.y + triLen2*triPnt2.y + triLen3 *triPnt3.y)/ (triLen1+triLen2+triLen3) ) """ elif len(self.calcBezier.points) == 4: p1,p2,p3,p4 = self.calcBezier.points triPnt1 =p1.interpolate(p4,0.5) triPnt2 = p1.interpolate(self.intersection,0.5) triPnt3 = self.intersection.interpolate(p4,0.5) triLen1 = self.intersection.distance(p4) triLen2 = p4.distance(p1) triLen3 = p1.distance(self.intersection) """ self.incenter = Point( (triLen1*triPnt1.x + triLen2*triPnt2.x + triLen3 *triPnt3.x)/ (triLen1+triLen2+triLen3), (triLen1*triPnt1.y + triLen2*triPnt2.y + triLen3 *triPnt3.y)/ (triLen1+triLen2+triLen3) ) """ self.incenter = Point( (triLen1*p1.x + triLen2*self.intersection.x + triLen3 *p4.x)/ (triLen1+triLen2+triLen3), (triLen1*p1.y + triLen2*self.intersection.y + triLen3 *p4.y)/ (triLen1+triLen2+triLen3) ) def draw(self,color=(1.0,1.0,1.0,1.0)): if not self.intersection: return if self.arc1: drawArc = Arc(self.calcBezier.points[0],self.incenter,self.arc1.angle) drawArc = drawArc.rotate(-self.offsetAngle,self.calcBezier.points[0]) drawArc = drawArc.moveTo((self.bezier.points[-1] if self.orderReversed else self.bezier.points[0])) drawArc.draw(RED) if self.arc2: drawArc = Arc(self.incenter,self.calcBezier.points[-1],self.arc2.angle) drawArc = drawArc.rotate(-self.offsetAngle,self.calcBezier.points[0]) drawArc = drawArc.moveTo((self.bezier.points[-1] if self.orderReversed else self.bezier.points[0]) + self.incenter.rotate(-self.offsetAngle)) drawArc.draw(RED) def calculateArcs(self): if not self.intersection: self.arc1 = self.arc2 = None return if len(self.calcBezier.points) == 3: p1,p2,p3= self.calcBezier.points tangent1 = p2 - p1 tangent2 = p2 - p3 self.arc1 = Arc(p1,self.incenter,tangent=tangent1) self.arc2 = Arc(self.incenter,p3,tangent=tangent2) if len(self.calcBezier.points) == 4: p1,p2,p3,p4 = self.calcBezier.points tangent1 = self.intersection - p1 tangent2 = self.intersection - p4 self.arc1 = Arc(p1,self.incenter,tangent=tangent1) self.arc2 = Arc(self.incenter,p4,tangent=tangent2) if (self.arc1.angle) > math.pi: self.arc1 = None self.arc2 = Arc(self.incenter,p4,tangent=tangent2) if (self.arc2.angle) > math.pi: self.arc2 = None def angle(self): return self.arc1.angle + self.arc2.angle def arcs(self): resultArcs = [] if self.arc1: resultArc = Arc(self.calcBezier.points[0],self.incenter,self.arc1.angle) resultArc = resultArc.rotate(-self.offsetAngle,self.calcBezier.points[0]) resultArc = resultArc.moveTo((self.bezier.points[-1] if self.orderReversed else self.bezier.points[0])) resultArcs.append(resultArc) if self.arc2: resultArc = Arc(self.incenter,self.calcBezier.points[-1],self.arc2.angle) resultArc = resultArc.rotate(-self.offsetAngle,self.calcBezier.points[0]) resultArc = resultArc.moveTo((self.bezier.points[-1] if self.orderReversed else self.bezier.points[0]) + self.incenter.rotate(-self.offsetAngle)) resultArcs.append(resultArc) return resultArcs class DXFImportDialog(QDialog): LABEL_WIDTH = 75 RIGHT_SPACING_WIDTH = 0 LAYER_ENUMERATION_COMPLETE_EVENT = QEvent.User+1 def __init__(self): QDialog.__init__(self) self.setWindowIcon(QIcon(QPixmap(":/eagleimages/eaglepy.png"))) self.setWindowTitle("Eagle DXF Importer") self.setGeometry(0,0,600,500) self.centerToWidget() self.setLayout(QVBoxLayout()) self.filePathLayout = QHBoxLayout() self.filePathName = QLabel("File Path : ") self.filePathName.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.filePathName.setMaximumWidth(self.LABEL_WIDTH) self.filePathName.setMinimumWidth(self.LABEL_WIDTH) self.filePathSelectButton = QPushButton() self.filePathSelectButton.setIcon(QApplication.style().standardIcon(QStyle.SP_DirOpenIcon)) self.filePathEdit = QLineEdit() self.filePathLayout.addWidget(self.filePathName) self.filePathLayout.addWidget(self.filePathEdit) self.filePathLayout.addWidget(self.filePathSelectButton) self.filePathLayout.addSpacerItem(QSpacerItem(self.RIGHT_SPACING_WIDTH,1,QSizePolicy.Fixed,QSizePolicy.Fixed)) self.scaleLayout = QHBoxLayout() self.scaleName = QLabel("Scale : ") self.scaleName.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.scaleName.setMaximumWidth(self.LABEL_WIDTH) self.scaleName.setMinimumWidth(self.LABEL_WIDTH) self.scaleSpinBox = QDoubleSpinBox() self.scaleSpinBox.setMinimum(0.00001) self.scaleSpinBox.setMaximum(10) self.scaleSpinBox.setDecimals(5) self.scaleSpinBox.setSingleStep(0.00001) self.scaleSpinBox.setValue(1) self.scaleLayout.addWidget(self.scaleName) self.scaleLayout.addWidget(self.scaleSpinBox) self.scaleLayout.addSpacerItem(QSpacerItem(self.RIGHT_SPACING_WIDTH,1,QSizePolicy.Fixed,QSizePolicy.Fixed)) self.toleranceLayout = QHBoxLayout() self.toleranceName = QLabel("Tolerance : ") self.toleranceName.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.toleranceName.setMaximumWidth(self.LABEL_WIDTH) self.toleranceName.setMinimumWidth(self.LABEL_WIDTH) self.toleranceSpinBox = QDoubleSpinBox() self.toleranceSpinBox.setMinimum(0.0001) self.toleranceSpinBox.setMaximum(0.1) self.toleranceSpinBox.setSingleStep(0.0001) self.toleranceSpinBox.setDecimals(4) self.toleranceSpinBox.setValue(0.001) self.toleranceLayout.addWidget(self.toleranceName) self.toleranceLayout.addWidget(self.toleranceSpinBox) self.toleranceLayout.addSpacerItem(QSpacerItem(self.RIGHT_SPACING_WIDTH,1,QSizePolicy.Fixed,QSizePolicy.Fixed)) self.wireWidthLayout = QHBoxLayout() self.wireWidthName = QLabel("Wire Width : ") self.wireWidthName.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.wireWidthName.setMaximumWidth(self.LABEL_WIDTH) self.wireWidthName.setMinimumWidth(self.LABEL_WIDTH) self.wireWidthSpinBox = QDoubleSpinBox() self.wireWidthSpinBox.setMinimum(0.0001) self.wireWidthSpinBox.setMaximum(0.1) self.wireWidthSpinBox.setSingleStep(0.0001) self.wireWidthSpinBox.setDecimals(4) self.wireWidthSpinBox.setValue(0.1) self.wireWidthLayout.addWidget(self.wireWidthName) self.wireWidthLayout.addWidget(self.wireWidthSpinBox) self.wireWidthLayout.addSpacerItem(QSpacerItem(self.RIGHT_SPACING_WIDTH,1,QSizePolicy.Fixed,QSizePolicy.Fixed)) self.layerLayout = QHBoxLayout() self.layerName = QLabel("Layer : ") self.layerName.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.layerName.setMaximumWidth(self.LABEL_WIDTH) self.layerName.setMinimumWidth(self.LABEL_WIDTH) self.layerComboBox = QComboBox() self.layerComboBox.addItem("emumerating..") self.layerComboBox.setEnabled(False) self.layerLayout.addWidget(self.layerName) self.layerLayout.addWidget(self.layerComboBox) self.fileInfoName = QLabel("File Name : ") self.fileInfoName.setAlignment(Qt.AlignTop | Qt.AlignRight) self.fileInfoName.setMaximumWidth(self.LABEL_WIDTH) self.fileInfoName.setMinimumWidth(self.LABEL_WIDTH) self.fileInfoTable = QTableWidget() self.fileInfoTable.setColumnCount(3) self.fileInfoTable.verticalHeader().hide() self.fileInfoTable.setHorizontalHeaderLabels(["Layer","Entity Type","Import","Info"]) self.fileInfoTable.horizontalHeader().setClickable(False) self.fileInfoTable.setColumnWidth(0,200) self.fileInfoTable.setColumnWidth(1,200) self.fileInfoTable.setColumnWidth(2,50) self.fileInfoTable.setColumnWidth(3,200) self.fileInfoTable.horizontalHeader().setStretchLastSection(True) self.fileInfoTable.setSelectionBehavior(QAbstractItemView.SelectRows) self.fileInfoLayout =QHBoxLayout() self.fileInfoTableLayout = QVBoxLayout() #self.fileInfoTableLayout.setSpacing(0) self.fileInfoTableButtonLayout = QHBoxLayout() self.fileInfoTableSelectAllButton = QPushButton("Select All") self.fileInfoTableDeselectAllButton = QPushButton("Deselect All") """ buttonWidth = 150 self.fileInfoTableSelectAllButton.setMinimumWidth(buttonWidth) self.fileInfoTableSelectAllButton.setMaximumWidth(buttonWidth) self.fileInfoTableDeselectAllButton.setMaximumWidth(buttonWidth) self.fileInfoTableDeselectAllButton.setMinimumWidth(buttonWidth) """ self.fileInfoTableSelectAllButton.clicked.connect(self.selectAll) self.fileInfoTableDeselectAllButton.clicked.connect(self.deselectAll) self.fileInfoTableButtonLayout.addWidget(self.fileInfoTableSelectAllButton) self.fileInfoTableButtonLayout.addWidget(self.fileInfoTableDeselectAllButton) #self.fileInfoTableButtonLayout.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Fixed)) self.fileInfoLayout.addWidget(self.fileInfoName) self.fileInfoLayout.addLayout(self.fileInfoTableLayout) self.fileInfoLayout.addSpacerItem(QSpacerItem(self.RIGHT_SPACING_WIDTH,1,QSizePolicy.Fixed,QSizePolicy.Fixed)) #self.fileInfoTableLayout.addLayout(self.fileInfoTableButtonLayout) self.fileInfoTableLayout.addWidget(self.fileInfoTable) #self.fileInfoTable.setStyleSheet("QTableWidget::item{border: 0px;padding-left:10px;}") self.filePathSelectButton.clicked.connect(self.fileOpenDialog) self.importButtonLayout = QHBoxLayout() self.cancelButton = QPushButton("Cancel") self.importButton = QPushButton("Import..") self.importCloseButton = QPushButton("Import && Close..") buttonWidth = 150 buttonHeight = 35 self.importButton.setMinimumWidth(buttonWidth) self.importButton.setMaximumWidth(buttonWidth) self.importButton.setMinimumHeight(buttonHeight) self.importButton.setMaximumHeight(buttonHeight) self.importCloseButton.setMinimumWidth(buttonWidth) self.importCloseButton.setMaximumWidth(buttonWidth) self.importCloseButton.setMinimumHeight(buttonHeight) self.importCloseButton.setMaximumHeight(buttonHeight) self.cancelButton.setMinimumWidth(buttonWidth) self.cancelButton.setMaximumWidth(buttonWidth) self.cancelButton.setMinimumHeight(buttonHeight) self.cancelButton.setMaximumHeight(buttonHeight) self.importButtonLayout.addSpacerItem(QSpacerItem(1,1,QSizePolicy.Expanding,QSizePolicy.Fixed)) self.importButtonLayout.addWidget(self.cancelButton) self.importButtonLayout.addWidget(self.importButton) self.importButtonLayout.addWidget(self.importCloseButton) self.cancelButton.clicked.connect(self.close) self.importButton.clicked.connect(self.importFile) self.importCloseButton.clicked.connect(self.importCloseFile) self.layout().addLayout(self.filePathLayout) self.layout().addLayout(self.scaleLayout) self.layout().addLayout(self.toleranceLayout) self.layout().addLayout(self.wireWidthLayout) self.layout().addLayout(self.layerLayout) self.layout().addLayout(self.fileInfoLayout) self.layout().addLayout(self.importButtonLayout) self.enumerateLayers() def event(self,event): if event.type() == self.LAYER_ENUMERATION_COMPLETE_EVENT: self.populateLayerCombo() event.setAccepted(True) return True return QDialog.event(self,event) def populateLayerCombo(self): for layerItem in self.layerData: name,colorIndex,index = layerItem map = QPixmap(12,12) map.fill(QColor(*self.palette[colorIndex])) self.layerComboBox.addItem(QIcon(map),name,QVariant(index)) self.layerComboBox.removeItem(0) self.layerComboBox.setEnabled(True) def enumerateLayers(self): self.layerData = [] def enumerateThread(layerData): self.palette = Eaglepy.paletteall() for index,layer in enumerate(Eaglepy.ULContext().layers(True)): layerData.append((layer.name.get(True),layer.color.get(True),index)) QApplication.postEvent(self,QEvent(self.LAYER_ENUMERATION_COMPLETE_EVENT)) threading.Thread(target=enumerateThread,args=[self.layerData]).start() def importCloseFile(self): if not self.importFile(): self.close() def importFile(self): filePath = str(self.filePathEdit.text()) if not os.path.exists(filePath): messageBox = QMessageBox( QMessageBox.Warning, "File Not Found","The file path '%s' does not exist. Please specify an existing file." % filePath, QMessageBox.Ok,self) return messageBox.exec_() importItems = {} for index in range(self.fileInfoTable.rowCount()): if self.fileInfoTable.item(index,2).checkState() == Qt.Checked: importItems[str(self.fileInfoTable.item(index,0).text())] = str(self.fileInfoTable.item(index,1).text() ) if not len(importItems.keys()): messageBox = QMessageBox( QMessageBox.Warning, "Nothing Selected","You must select at least one entity to import.", QMessageBox.Ok,self) return messageBox.exec_() dxfFile = readfile(filePath) wire_width = self.wireWidthSpinBox.value() scale = self.scaleSpinBox.value() max_deviation = self.toleranceSpinBox.value() layer = str(self.layerComboBox.currentText()) curves = [] lines = [] for entity in dxfFile.entities: if not importItems.has_key(entity.layer): continue if not entity.dxftype in importItems[str(entity.layer)]: continue if isinstance(entity,Polyline): points = [p for p in entity.points()] pointIndex = 0 while(pointIndex < len(points)-1): lines.append(Line(Point(points[pointIndex][0]*scale,points[pointIndex][1]*scale),Point(points[pointIndex+1][0]*scale,points[pointIndex+1][1]*scale))) pointIndex += 1 continue hasControlPoints = True controlPointIndex = 0 while hasControlPoints: points = [] for index in range(controlPointIndex,controlPointIndex+4): points.append(Point(*list(entity.controlpoints[index][:2]))) newCurve = BezierCurve.Construct(*[ p*scale for p in points]) if isinstance(newCurve,Line): lines.append(newCurve) else: curves.append(newCurve) controlPointIndex +=3 if controlPointIndex == len(entity.controlpoints)-1: break del dxfFile splittingCurves = False curveIndex = 0 if len(curves): while not splittingCurves: thisCurve = curves[curveIndex] inflectionPnts = thisCurve.inflection() if len(inflectionPnts): curves.pop(curveIndex) splitCurves = thisCurve.split(inflectionPnts[0]) for curve in splitCurves: if isinstance(curve,Line): lines.append(curve) else: curves.insert(curveIndex,curve) continue biarc = Biarc(thisCurve) if (biarc.angle()) > math.pi/2: intersect = biarc.incenterIntersect() curves.pop(curveIndex) splitCurves = thisCurve.split(intersect[0]) for curve in splitCurves: if isinstance(curve,Line): lines.append(curve) else: curves.insert(curveIndex,curve) continue deviation = biarc.deviation() if deviation[0] > max_deviation: curves.pop(curveIndex) splitCurves = thisCurve.split(deviation[1][2]) for curve in splitCurves: if isinstance(curve,Line): lines.append(curve) else: curves.insert(curveIndex,curve) continue if curveIndex == len(curves)-1: break curveIndex +=1 resultText = "LAYER " + layer + ";SET CONFIRM YES;" for index,curve in enumerate(curves): biarc = Biarc(curve) arcs = biarc.arcs() for arc in arcs: resultText += arc.asScr(wire_width) for index,line in enumerate(lines): resultText += line.asScr(wire_width) resultText += "SET CONFIRM OFF;" Eaglepy.executescr(resultText); def selectAll(self): for index in range(self.fileInfoTable.rowCount()): self.fileInfoTable.item(index,2).setCheckState(Qt.Checked) def deselectAll(self): for index in range(self.fileInfoTable.rowCount()): self.fileInfoTable.item(index,2).setCheckState(Qt.Unchecked) def findEagleWindow(self): pass def fileOpenDialog(self): fileDialog = QFileDialog(filter="*.dxf") fileDialog.setFileMode(QFileDialog.ExistingFile) result = fileDialog.exec_() if result: self.filePathEdit.setText(fileDialog.selectedFiles()[0]) self.readFileInfo() def clearFileInfo(self): for index in range(self.fileInfoTable.rowCount()): self.fileInfoTable.removeRow(0) def readFileInfo(self): self.clearFileInfo() filePath = self.filePathEdit.text() if not os.path.exists(filePath): return dxfFile = readfile(str(filePath)) entityLayers = {} for entity in dxfFile.entities: if not entityLayers.has_key( str(entity.layer) ) == None: entityLayers[str(entity.layer)] = [] entityLayers[str(entity.layer)].append(entity) for layername,entityList in entityLayers.iteritems(): for index,entity in enumerate(entityList): layerItem = QTableWidgetItem(layername) layerItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) entityItem = QTableWidgetItem(str(entity.dxftype)) entityItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) selectItem = QTableWidgetItem() selectItem.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable | Qt.ItemIsUserCheckable) selectItem.setTextAlignment(Qt.AlignRight) selectItem.setCheckState(Qt.Checked) entityInfoText = "" if isinstance(entity,Line): entityInfoText += "Start:" + str(entity.start) + ", Stop:" + str(entity.stop) elif isinstance(entity,Point): entityInfoText += "Position:" + str(entity.point) elif isinstance(entity,Circle): entityInfoText += "Center:" + str(entity.center) + ", Radius:" + str(entity.radius) elif isinstance(entity,Arc): entityInfoText += "Center:" + str(entity.center) + ", Radius:" + str(entity.radius) + ", Start Angle:" + str(entity.startangle) + ", End Angle:" + str(entity.endangle) elif isinstance(entity,Spline): entityInfoText += "Degree:" + str(entity.degree) + ", Start Tangent:" + str(entity.starttangent) + ", End Tangent:" + str(entity.endtangent) + ", Num Ctrl Pnts:" + str(len(entity.controlpoints)) + \ ", Num Fit Pnts:" + str(len(entity.fitpoints)) + ", Num Knots:" + str(len(entity.knots)) + ", Weights:" + str(len(entity.weights)) entityInfoItem = QTableWidgetItem(entityInfoText) self.fileInfoTable.insertRow(index) self.fileInfoTable.setItem(index,0,layerItem) self.fileInfoTable.setItem(index,1,entityItem) self.fileInfoTable.setItem(index,2,selectItem) self.fileInfoTable.setItem(index,3,entityInfoItem) del dxfFile def centerToWidget(self,target=None): if not target: rect = QApplication.desktop().availableGeometry(target if target != None else self) else: rect = target.geometry() center = rect.center() self.move(center.x() - self.width() * 0.5, center.y() - self.height() * 0.5); Eaglepy.initialize() application = QApplication([]) dialog = DXFImportDialog() dialog.show() application.exec_() Eaglepy.shutdown()