From 7604c4e1a999b7e047b6a321a3c8da8fbab2acdd Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Sun, 14 Jun 2015 17:19:24 -0400 Subject: [PATCH 1/2] Continuing the cleanup. First time using Pylint on this codebase. --- .coverage | Bin 4411 -> 7311 bytes cadquery/CQ.py | 545 +++++++++++++++++++++++-------------------------- 2 files changed, 251 insertions(+), 294 deletions(-) diff --git a/.coverage b/.coverage index ef77fe8fd222f59cbd24f29e2ab11e0c23c38406..2714befb645e0f05423af13754ba74ab3cbdfeee 100644 GIT binary patch literal 7311 zcmb`MXP8t~x`k)&eG*ABNR*(^fOdnRpr}Yv;PaK(BrOn{qG?ihbpRPfF^}WbF(OD* zuA>eZP(eg6jsXKk6a>N188haLFREt`w!+14}ZGOsoLMS)_T`oq!(sXPJXR| z;@Pv)1?9!18J%Chpm<)obV^~m-MpT;y>okHOuj$6Xl}YJ<1NhiIr(Si%_yFe&YL;E zw5V`KdETJnf{Homx#eYfBT9>B+7Btq8#ra!$cl97g1mw$(=rGDXY%BtxkcrZC+C(d z$ONfOm?K(@c;ZWKsU!8JfizC%4?Tn@l&8z=lgHS92cKV3Tw0zkEsLKKr82d0>=S&6 zq?Ua~T{%LUNKT~cDR;@;a*wQ)`(?d6C>vy>JR*VuNC%Vv}OCVu#`##dnGy6b&_vHJvqGH2pOFHKR0RGzFTpCZoAP zvrw}{bDL(FX1QjCW|ii4&4ZeUHBW1v)qJe^RP&i;zvfHLSDJ4$z9BS3hB}72hI)nu zhR%jAhHi$_4gC!R3yuP8a5jqGi)(z zH9T&3!tj*g8N=I#cMLlXy9~Px?-_nE{AxI0__zI19<@E{dK}?V-=m>N6OW@j+Ie*F z=;G1MqmRd6kKrDZJSKZg^{DVz>~X8dQjgm_mV2!9SmklM#~P1^JvMl3^w{LF+2b*f ztsYN!yy@|_$4-xTJ$8HS@!03_504K$e(?Cw<0p?_JVKwyrSG! zpX+^Y@VUw77N5mFxB4vgS?06C=K-JfK9Bfp^4aXO#pf-bcYJpG?DBcnXSdILK6`!k z`MmG*fzO9NANhRh;|2HuK|mN#E1*t5^MDosEdyEwv<_$!&^Dl5K!SvFe_kAz}$dLz_kI_2P_U)60j^_dBBQ* zRRL=Po(y;@;F*Bu0=5Ob81PcS%K@(hY!7%XU`N3IfG-2S4)`(P=YU@V4g~xjaBRqc zkbxnCLe2;o5;7@dO31X3bVy;y%#hh3b3%$kG9hIlZkhLM}LhcQDIAlY}#*jxsHic{s*%I<%$jc$yLtYJeE#&QxcS3fC z>l87Y{%OaLXtcX|{u_|J9#O)Dl zBJPa1JK~;*brJVPycY3##G4VjB6dgYiFhw!Z^XWc_ai=t_*cXy5uZkU9;NdK9fFA@ zHdfOm>!vdGazs`7WZ-t{?{6A zY`1^2hPzJgmHVviK3HkDTjg$zmoJ?W ze=Fb1kMff>3TMvC)M3s@LGj%3(xRyc%}AqE{)qqD zcoe3K=fuIPaVpcKI#@NA7L^0vC5~>DbCKspzn7hhGpm9XHd+n06ER*UxQLaKNmU`M z(1om7GRMuud>ghdmPIyh#X;*Dxz+})n`E(_hUIn|R@pg-XJD;8`}Oj$4O^S#G1=-) z{qORuJTKeiMH{nD!=a`d|p|)yrLxj`(~;9A&2ny)wDCPlC|Ow zYo5v+nPXupaHvzqSwtggY%QUSRm<26PL@;UH0dX&Tg@D7cVn2{h!IvV$2*-Yu&_~N zVPlrewwq8Q3*-V>X!jrnj~F|ya{F+D-Gy81HYEDE%x%PKr;UHLTM_Hx{qlg@jA|5# z_aoNECo8uk29tP8Ub0)Vy-Fo_SV-9|@5%e}L8Vsim+!1X{$TYn*`Vs}(M~Os9g1qK zk=4wqO|r6S*QiN$lM?Z?`_wwSPe&=*9W0~?4B4&9Q&cIZmCci!awcGuP|@iQj0QR? zN`!QX!y{3QaLYGFk*^r**l4_BqT(FIxek)fbDU&XvrsW3i%J>cc8RZ2T&?)C;u^)Zit7}AQCzRML2;AfX2mUv#cs=&{y+9w?bz!s z#odZT0PObP>$d*^2V@U94trGbm|~0Kai<4=card&Q-xO?qU}`dQoO5pPw|1`!y1zC znG=JrogRFj)q?|041QPqN1-*ICeVagZ8*Ylo|S}Vn&wUwS~+2;k_4*vNjtHQO|=XkOL4u6aZAre=p`m*!o~Zp|Le zUd=x>A7ugebH}*fX};I|r1@2IK=YgCca1g}gJ%c~wX@V)-%)QvLnA|DLsLUDLvzEC zhL(o5hIWSbh7N|K4V|)l+|_Wb;W$ICp}V1n;dnz&LodULhTeughLa2@8%{BtYBT7;PA17;6}37;l(hm}odBi`|nAQyq3E%w6cP zyT~xhFvl?0P;4kMWDKQ-3d20Zd`ILLWO4i=N92nPml-ZMTw%D%aJAtY!&<{S!@Y+4 z4EH-OUvGHOad}M;f7-G5vxesk&l|QGUNpR9c-ioZVY}gV!&`ehaIkC44>+Xy4W*Xq%tk5 zOSsrM>Quo+>>W*QzKV;o7%q|mE3U7SqAbbac{YW|m=Tv*yBCk~{(>lhWI<~^AXKTWSu3C%JUu*kb9V@xu-~<@IS5mT7zt?Hj`})fOOAUiv zI*g0Aq;`v)FlL8@8zxA~l0#C%#q2O$3Y|B`&RAT@5M?ec&a)h`z>&no)+aBo;gkta z+-UvsX6Ki;$+Ak8h)E)L%ec17GQ>lb)!imn-Nh8~?BQ0SL+8|}LffP=ZIdcAtSm!g zh-~NpGOj_}TfXXCnW1yzUoM?L<`4$03K|b$yPD*9R4UUh4)Bd#3){kuR?@y^(XOit z@p1DR$MUK`lFmQv5MHOWVy>I1c-GseG99W>I-c}ukZzbhq##!HZOdv7YsXaP=xRi1 z;Z~=cZF%FxNCq&DUNMfuBRASYNdhAUuJ26-EryX|8@(>GBXy-k(Rg@PRCc@%IqZnz zQ{3)WL*@>Lm0#P2_gfpBVz7(@Q^FdZ5AJ(y#L3O3oSu#hPf(nw=&k7E;!fWzS@csR zS!Y1D0Y1w$z$0D#Yx9i_HxshqCZ(8EBjQX|6u6W#+v4Tn$=0~y(pe?5r`UUl_$ZG_ zWje*f9FKC+>f18HidA#l>nD;BHzBbs#L+j`hTk4e3VPeVy>Dd*9^i_DxOY#`u(CKv z{}(F_+iO(cMRZPOa*|3TmaUdnxY|@o>d|iEd$_xR${9)Lk2-{DuNudLmalBal#=9U z>ypZJ&8gJuURJH+pCVSnga+cvhpY;YaqY=?N6P2g@r>7Tmdv#ynXx0e!Zjf4T=%g- zHrf8;F?-#x)pj2V0l!d{?c@FGkd+!-nRqE;K)x#pjn5 zHZfa#e$5f|n~tDuXx{5`^FM3k=Fe-;^tXy%9YO!5u%X#Pv~r=@cM#ps;d5&T&Ndk* znb<~Qn}aPE+xUCB#-`qZF7k>?y*BcW(O4Fqq?zdwt&OlreqE%wM01tqY8O5CY2MfT zL-S!aS$^WOqYaLsp_a>z^;~LfWN6|dqs@y+QtV*pXgDUD4)Y9sT?9mNuT0C=FW_m?={_wKW$>BrF^N%bVKVoL4?9}|?qVd^Xe;7NqWO_-(*s+~woSOB8 z**H&)T8-3cEv>Efw7xdX6!hDj-*DuJ+-E0+*_wIUKpSaeZKBO|A8nLq%qUZz*+ZF+~U&^z@my+`lU`}HAxSRc{H^l@FI>vX+t)>rjCeP2J+Z}ofqL4VR6 zh+u`f!ok8J!lA-pLN}qi&{OCo3>1zMjur+9g9R0a2na)kV}((|@xlqhXyIg`P#7Z= z3B^K*Fh$4;<-#1{G-07|fpD>Ki*T#3R9Gh5CfqKp5LOBg3y%tq36BeFgeQfkg=d8i zgpY*Z1u?WVv@&!x9BNPlhB1ay45fywVXk3;VWHt>!>xv;hGmB3hT9D*438O}FswIh zGJJ3N(XidH!|;n?r{Q-)LrY^z6H9YT3(H=XmX^aT-7GyV!!08$BQ3{Tj<=j-8ErY) zQfL`t8EYxBOtVb4%&=rF<(3M|Ov^0G9Ls5z^DPT47g!cq7F#Z~Tx7Y~a*O3w%TmiS z%W}&K%bk|HEUPR}Se~*xZF$D>tmOsETFZ-;b(WVb>n$5Bn=G3xpISb%Y_WW9*=qU9 zQZu4fMD2(=5zQi6MC={0PeiMTeIwdN92C(dqH9FYh~5zzabm>yi1LVvh}jW~BJPM- z5wSAju836;_e9(qaeu^v5o;q}j93@3K4N3UrijfEuSRT**cS0k#J3UOMSLHzJ>sW` z9pRfe>N@H<_H?v%?CWUbXzMu8(aF)-agd{rqrYRQW0+&OW0YgG<77vnV~k^*W2R${ z<5b7#j=7G%InH#P<2cuGo@0Syp<}t@Hpd-~I~^+>cRN-)?s44fxZm-R;}ORO$419% zj@KP;INovm;Mnf?$?>yehvOH=PRDPK-yJnP;<28{6MJfT+IjZ(wD;tBI(RyIx_Nqd zdU|?!dV7xW^!4=jjPxAq8RaSP9Pb(Jne3V3ndX`0neCb5Im2_FXTIkG&mzxa&xM|g zJQsT|^Q`o&@;u;q$n&V@G0)?kHJ)cYA9_CWeB#;S+3NYy^Oa|t=WEY5o^L(dJ@sPh z$25p(7SkeTub7rGtz(A9jE~90Oo*8jGdX5n%sDahV;00Lj9DD>_n3=fE{VA;=JJ>; zVwS{Q6?09@?J=uk?uofK=DwKwV;+ooIA(3k8!>OjycP3K%)2q~#e5p`S;yrg#8oRC*&n`Na&W(J)uWJuY^7c0}=)% z9Gx&Y;g|$X7?ME3u!Io_MG4~)#wScnI4j}ogmV(kO_-l>e!`N3s}rtGxGv%Pgc}lW zO1L>;UBZThjR~6)UQXDY@Jhm~39l!-k??lHI|=V6nSz4dF~fwiOePpaV4SJ*AtheZ`x>T3x^5B-+gI!hzx7@9(bagPyy}>aL>Jz~&PwCV8j6SQ+ z>kGP8U(yY_QD4*7^7reApdO`zba1sQaonHr$W@9{wCK-LkqgLFE!PU2rE~RfX}wqw zip2%GI7A~Cj4S^LMJ^CaLl9Pk5Ufrg|DjMX*65RI04ksV#ZWXhgon>Pd*w6l2oJsU zkEgDYmW?QV@S5qH?;+IL_2nB0jfG~_UcR-kuh6#I%ZF#LEGNB%zC!=BrBrs3;c5LC zFPtuf=RH%HC!8ysCoB-I5S9qnrNv`;weE4JaCf!J@tp9y@Pe>bcu80VNXMYP&o{Z z42=y<3{4Hq4Q&nW4DAgE7&;g_8af-g8M+&K74QFP~I2Ba^D5Nny5WUMh*MK@*3EG0|K5X@4CMh;Vcm6+?q8h6i1Y z47#W!LX|EuK^9YVdJsi6sNvca_&MCK3YdQ&U_Lh_9u2pi1O0OW^lSCS0Q&U-^qa!0 zcqPSs4)$*c*ng;B=vMtIK>r*4cRDqG)a@zqcj(UC;0OQ+xUVf#zy3cFA8tJ$e*cv3 z2mTM@1K$JID}mleI5K7Wz;vSw7LKXnd%!>#B`2o4WP&hJm?TUVW>hVb3SpKoJKZH` zR}o^qaK3P(^Ql@t~43Lgp|R|CdY z;med6+k|gZVEmFY<2Rv($?coS;8IQmJOnW8oi3jJ40$OJIvMh-Fc7Xkoc@u9!RZnj zVi;){Whh7|(PTrpVWwf0VV>cvbQP^Jyk~gd@PXkY!^ehC4OB}MDxj5%KOfJwr1hs|kLXA{sA*t=F z`bhP>=Kr5QQkmUe!VyA9aPU{ZG{=LgWp{*7P_?X2+I3uy6;4SVH% """ -import time,math +import time, math from cadquery import * from cadquery import selectors + class CQContext(object): """ A shared context for modeling. @@ -29,22 +30,25 @@ class CQContext(object): which allows for shared state when needed, """ def __init__(self): - self.pendingWires = [] #a list of wires that have been created and need to be extruded - self.pendingEdges = [] #a list of pending edges that have been created and need to be joined into wires - self.firstPoint = None #a reference to the first point for a set of edges. used to determine how to behave when close() is called - self.tolerance = 0.0001 #user specified tolerance + self.pendingWires = [] # a list of wires that have been created and need to be extruded + self.pendingEdges = [] # a list of created pending edges that need to be joined into wires + # a reference to the first point for a set of edges. + # Used to determine how to behave when close() is called + self.firstPoint = None + self.tolerance = 0.0001 # user specified tolerance + class CQ(object): """ Provides enhanced functionality for a wrapped CAD primitive. Examples include feature selection, feature creation, 2d drawing - using work planes, and 3d opertations like fillets, shells, and splitting + using work planes, and 3d operations like fillets, shells, and splitting """ - def __init__(self,obj): + def __init__(self, obj): """ - Construct a new cadquery (CQ) object that wraps a CAD primitive. + Construct a new CadQuery (CQ) object that wraps a CAD primitive. :param obj: Object to Wrap. :type obj: A CAD Primitive ( wire,vertex,face,solid,edge ) @@ -53,16 +57,14 @@ class CQ(object): self.ctx = CQContext() self.parent = None - if obj: #guarded because sometimes None for internal use + if obj: # guarded because sometimes None for internal use self.objects.append(obj) - def newObject(self,objlist): + def newObject(self, objlist): """ Make a new CQ object. :param objlist: The stack of objects to use - :param newContextSolid: an optional new solid to become the new context solid - :type objlist: a list of CAD primitives ( wire,face,edge,solid,vertex,etc ) The parent of the new object will be set to the current object, @@ -71,23 +73,23 @@ class CQ(object): Custom plugins and subclasses should use this method to create new CQ objects correctly. """ - r = CQ(None) #create a completely blank one + r = CQ(None) # create a completely blank one r.parent = self - r.ctx = self.ctx #context solid remains the same + r.ctx = self.ctx # context solid remains the same r.objects = list(objlist) return r - def _collectProperty(self,propName): + def _collectProperty(self, propName): """ - Collects all of the values for propName, - for all items on the stack. - FreeCAD objects do not implement id correclty, - so hashCode is used to ensure we dont add the same - object multiple times. + Collects all of the values for propName, + for all items on the stack. + FreeCAD objects do not implement id correctly, + so hashCode is used to ensure we don't add the same + object multiple times. - One weird use case is that the stack could have a solid reference object - on it. This is meant to be a reference to the most recently modified version - of the context solid, whatever it is. + One weird use case is that the stack could have a solid reference object + on it. This is meant to be a reference to the most recently modified version + of the context solid, whatever it is. """ all = {} for o in self.objects: @@ -95,17 +97,17 @@ class CQ(object): #tricky-- if an object is a compound of solids, #do not return all of the solids underneath-- typically #then we'll keep joining to ourself - if propName == 'Solids' and isinstance(o, Solid) and o.ShapeType() =='Compound': - for i in getattr(o,'Compounds')(): + if propName == 'Solids' and isinstance(o, Solid) and o.ShapeType() == 'Compound': + for i in getattr(o, 'Compounds')(): all[i.hashCode()] = i else: - if hasattr(o,propName): - for i in getattr(o,propName)(): + if hasattr(o, propName): + for i in getattr(o, propName)(): all[i.hashCode()] = i return list(all.values()) - def split(self,keepTop=False,keepBottom=False): + def split(self, keepTop=False, keepBottom=False): """ Splits a solid on the stack into two parts, optionally keeping the separate parts. @@ -115,13 +117,13 @@ class CQ(object): :raises: ValueError if there is not a solid in the current stack or the parent chain :returns: CQ object with the desired objects on the stack. - The most common operation splits a solid and keeps one half. This sample creates split bushing:: + The most common operation splits a solid and keeps one half. This sample creates + split bushing:: #drill a hole in the side c = Workplane().box(1,1,1).faces(">Z").workplane().circle(0.25).cutThruAll()F #now cut it in half sideways c.faces(">Y").workplane(-0.5).split(keepTop=True) - """ solid = self.findSolid() @@ -130,15 +132,15 @@ class CQ(object): raise ValueError("You have to keep at least one half") maxDim = solid.BoundingBox().DiagonalLength * 10.0 - topCutBox = self.rect(maxDim,maxDim)._extrude(maxDim) - bottomCutBox = self.rect(maxDim,maxDim)._extrude(-maxDim) + topCutBox = self.rect(maxDim, maxDim)._extrude(maxDim) + bottomCutBox = self.rect(maxDim, maxDim)._extrude(-maxDim) top = solid.cut(bottomCutBox) bottom = solid.cut(topCutBox) if keepTop and keepBottom: #put both on the stack, leave original unchanged - return self.newObject([top,bottom]) + return self.newObject([top, bottom]) else: # put the one we are keeping on the stack, and also update the context solid #to the one we kept @@ -149,8 +151,7 @@ class CQ(object): solid.wrapped = bottom.wrapped return self.newObject([bottom]) - - def combineSolids(self,otherCQToCombine=None): + def combineSolids(self, otherCQToCombine=None): """ !!!DEPRECATED!!! use union() Combines all solids on the current stack, and any context object, together @@ -158,12 +159,11 @@ class CQ(object): After the operation, the returned solid is also the context solid. - :param otherCQToCombine: another cadquery to combine. + :param otherCQToCombine: another CadQuery to combine. :return: a cQ object with the resulting combined solid on the stack. Most of the time, both objects will contain a single solid, which is combined and returned on the stack of the new object. - """ #loop through current stack objects, and combine them #TODO: combine other types of objects as well, like edges and wires @@ -176,8 +176,8 @@ class CQ(object): if len(toCombine) < 1: raise ValueError("Cannot Combine: at least one solid required!") - #get context solid - ctxSolid = self.findSolid(searchStack=False,searchParents=True) #we dont want to find our own objects + #get context solid and we don't want to find our own objects + ctxSolid = self.findSolid(searchStack=False, searchParents=True) if ctxSolid is None: ctxSolid = toCombine.pop(0) @@ -199,14 +199,12 @@ class CQ(object): Contrast with vals, which returns the underlying objects for all of the items on the stack - """ return [self.newObject([o]) for o in self.objects] def size(self): """ Return the number of objects currently on the stack - """ return len(self.objects) @@ -218,26 +216,24 @@ class CQ(object): :returns: the values of the objects on the stack. Contrast with :py:meth:`all`, which returns CQ objects for all of the items on the stack - """ res = [] return self.objects - def add(self,obj): + def add(self, obj): """ - adds an object or a list of objects to the stack + adds an object or a list of objects to the stack - :param obj: an object to add - :type obj: a CQ object, CAD primitive, or list of CAD primitives - :return: a CQ object with the requested operation performed + :param obj: an object to add + :type obj: a CQ object, CAD primitive, or list of CAD primitives + :return: a CQ object with the requested operation performed - If an CQ object, the values of that object's stack are added. If a list of cad primitives, - they are all added. If a single CAD primitive it is added - - Used in rare cases when you need to combine the results of several CQ results - into a single CQ object. Shelling is one common example + If an CQ object, the values of that object's stack are added. If a list of cad primitives, + they are all added. If a single CAD primitive it is added + Used in rare cases when you need to combine the results of several CQ results + into a single CQ object. Shelling is one common example """ if type(obj) == list: self.objects.extend(obj) @@ -258,16 +254,15 @@ class CQ(object): def toFreecad(self): """ - Directly returns the wrapped FreeCAD object to cut down on the amount of boiler plate code needed when - rendering a model in FreeCAD's 3D view. + Directly returns the wrapped FreeCAD object to cut down on the amount of boiler plate code + needed when rendering a model in FreeCAD's 3D view. :return: The wrapped FreeCAD object :rtype A FreeCAD object or a SolidReference """ return self.objects[0].wrapped - - def workplane(self,offset=0.0,invert=False): + def workplane(self, offset=0.0, invert=False): """ Creates a new 2-D workplane, located relative to the first face on the stack. @@ -278,38 +273,40 @@ class CQ(object): :type invert: boolean or None=False :rtype: Workplane object ( which is a subclass of CQ ) - The first element on the stack must be a face, or a vertex. If a vertex, then the parent item on the - chain immediately before the vertex must be a face. + The first element on the stack must be a face, or a vertex. If a vertex, then the parent + item on the chain immediately before the vertex must be a face. The result will be a 2-d working plane with a new coordinate system set up as follows: - * The origin will be located in the *center* of the face, if a face was selected. If a vertex was - selected, the origin will be at the vertex, and located on the face. + * The origin will be located in the *center* of the face, if a face was selected. If a + vertex was selected, the origin will be at the vertex, and located on the face. * The Z direction will be normal to the plane of the face,computed at the center point. - * The X direction will be parallel to the x-y plane. If the workplane is parallel to the global - x-y plane, the x direction of the workplane will co-incide with the global x direction. + * The X direction will be parallel to the x-y plane. If the workplane is parallel to + the global x-y plane, the x direction of the workplane will co-incide with the + global x direction. Most commonly, the selected face will be planar, and the workplane lies in the same plane of the face ( IE, offset=0). Occasionally, it is useful to define a face offset from - an existing surface, and even more rarely to define a workplane based on a face that is not planar. + an existing surface, and even more rarely to define a workplane based on a face that is + not planar. To create a workplane without first having a face, use the Workplane() method. Future Enhancements: * Allow creating workplane from planar wires * Allow creating workplane based on an arbitrary point on a face, not just the center. - For now you can work around by creating a workplane and then offsetting the center afterwards. - + For now you can work around by creating a workplane and then offsetting the center + afterwards. """ obj = self.objects[0] def _computeXdir(normal): - xd = Vector(0,0,1).cross(normal) + xd = Vector(0, 0, 1).cross(normal) if xd.Length < self.ctx.tolerance: #this face is parallel with the x-y plane, so choose x to be in global coordinates - xd = Vector(1,0,0) + xd = Vector(1, 0, 0) return xd faceToBuildOn = None @@ -322,18 +319,18 @@ class CQ(object): # xDir = _computeXdir(normal) # else: # raise ValueError("If a vertex is selected, a face must be the immediate parent") - if isinstance(obj,Face): + if isinstance(obj, Face): faceToBuildOn = obj center = obj.Center() normal = obj.normalAt(center) xDir = _computeXdir(normal) else: - if hasattr(obj,'Center'): + if hasattr(obj, 'Center'): center = obj.Center() normal = self.plane.zDir xDir = self.plane.xDir else: - raise ValueError ("Needs a face or a vertex or point on a work plane") + raise ValueError("Needs a face or a vertex or point on a work plane") #invert if requested if invert: @@ -360,7 +357,7 @@ class CQ(object): """ return self.newObject(self.objects[0:1]) - def item(self,i): + def item(self, i): """ Return the ith item on the stack. @@ -395,9 +392,7 @@ class CQ(object): else: raise ValueError("Cannot End the chain-- no parents!") - - - def findSolid(self,searchStack=True,searchParents=True): + def findSolid(self, searchStack=True, searchParents=True): """ Finds the first solid object in the chain, searching from the current node backwards through parents until one is found. @@ -424,11 +419,11 @@ class CQ(object): return s if searchParents and self.parent is not None: - return self.parent.findSolid(searchStack=True,searchParents=searchParents) + return self.parent.findSolid(searchStack=True, searchParents=searchParents) return None - def _selectObjects(self,objType,selector=None): + def _selectObjects(self, objType, selector=None): """ Filters objects of the selected type with the specified selector,and returns results @@ -436,11 +431,11 @@ class CQ(object): :type objType: string: (Vertex|Edge|Wire|Solid|Shell|Compound|CompSolid) :return: a CQ object with the selected objects on the stack. - **Implementation Note**: This is the base implmentation of the vertices,edges,faces,solids,shells, + **Implementation Note**: This is the base implementation of the vertices,edges,faces,solids,shells, and other similar selector methods. It is a useful extension point for plugin developers to make other selector methods. """ - toReturn = self._collectProperty(objType) #all of the faces from all objects on the stack, in a single list + toReturn = self._collectProperty(objType) # all of the faces from all objects on the stack, in a single list if selector is not None: if type(selector) == str: @@ -451,14 +446,14 @@ class CQ(object): return self.newObject(toReturn) - def vertices(self,selector=None): + def vertices(self, selector=None): """ Select the vertices of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the vertices of all objects are collected and a list of all the distinct vertices is returned. :param selector: :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains the *distinct* vertices of *all* objects on the current stack, + :return: a CQ object who's stack contains the *distinct* vertices of *all* objects on the current stack, after being filtered by the selector, if provided If there are no vertices for any objects on the current stack, an empty CQ object is returned @@ -478,16 +473,16 @@ class CQ(object): :py:class:`StringSyntaxSelector` """ - return self._selectObjects('Vertices',selector) + return self._selectObjects('Vertices', selector) - def faces(self,selector=None): + def faces(self, selector=None): """ Select the faces of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the faces of all objects are collected and a list of all the distinct faces is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* faces of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* faces of *all* objects on the current stack, filtered by the provided selector. If there are no vertices for any objects on the current stack, an empty CQ object is returned @@ -508,16 +503,16 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Faces',selector) + return self._selectObjects('Faces', selector) - def edges(self,selector=None): + def edges(self, selector=None): """ Select the edges of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the edges of all objects are collected and a list of all the distinct edges is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* edges of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* edges of *all* objects on the current stack, filtered by the provided selector. If there are no edges for any objects on the current stack, an empty CQ object is returned @@ -538,16 +533,16 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Edges',selector) + return self._selectObjects('Edges', selector) - def wires(self,selector=None): + def wires(self, selector=None): """ Select the wires of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the wires of all objects are collected and a list of all the distinct wires is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* wires of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* wires of *all* objects on the current stack, filtered by the provided selector. If there are no wires for any objects on the current stack, an empty CQ object is returned @@ -560,16 +555,16 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Wires',selector) + return self._selectObjects('Wires', selector) - def solids(self,selector=None): + def solids(self, selector=None): """ Select the solids of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the solids of all objects are collected and a list of all the distinct solids is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* solids of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. If there are no solids for any objects on the current stack, an empty CQ object is returned @@ -584,16 +579,16 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Solids',selector) + return self._selectObjects('Solids', selector) - def shells(self,selector=None): + def shells(self, selector=None): """ Select the shells of objects on the stack, optionally filtering the selection. If there are multiple objects on the stack, the shells of all objects are collected and a list of all the distinct shells is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* solids of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. If there are no shells for any objects on the current stack, an empty CQ object is returned @@ -603,16 +598,16 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Shells',selector) + return self._selectObjects('Shells', selector) - def compounds(self,selector=None): + def compounds(self, selector=None): """ Select compounds on the stack, optionally filtering the selection. If there are multiple objects on the stack, they are collected and a list of all the distinct compounds is returned. :param selector: A selector :type selector: None, a Selector object, or a string selector expression. - :return: a CQ object whos stack contains all of the *distinct* solids of *all* objects on the current stack, + :return: a CQ object who's stack contains all of the *distinct* solids of *all* objects on the current stack, filtered by the provided selector. A compound contains multiple CAD primitives that resulted from a single operation, such as a union, cut, @@ -620,21 +615,21 @@ class CQ(object): See more about selectors HERE """ - return self._selectObjects('Compounds',selector) + return self._selectObjects('Compounds', selector) - def toSvg(self,opts=None): + def toSvg(self, opts=None): """ Returns svg text that represents the first item on the stack. for testing purposes. - :param options: svg formatting options - :type options: dictionary, width and height + :param opts: svg formatting options + :type opts: dictionary, width and height :return: a string that contains SVG that represents this item. """ - return SVGexporter.getSVG(self.val().wrapped,opts) + return SVGexporter.getSVG(self.val().wrapped, opts) - def exportSvg(self,fileName): + def exportSvg(self, fileName): """ Exports the first item on the stack as an SVG file @@ -644,9 +639,9 @@ class CQ(object): :type fileName: String, absolute path to the file """ - exporters.exportSVG(self,fileName) + exporters.exportSVG(self, fileName) - def rotateAboutCenter(self,axisEndPoint,angleDegrees): + def rotateAboutCenter(self, axisEndPoint, angleDegrees): """ Rotates all items on the stack by the specified angle, about the specified axis @@ -665,7 +660,7 @@ class CQ(object): Future Enhancements: * A version of this method that returns a transformed copy, rather than modifying the originals - * This method doesnt expose a very good interface, becaues the axis of rotation + * This method doesnt expose a very good interface, because the axis of rotation could be inconsistent between multiple objects. This is because the beginning of the axis is variable, while the end is fixed. This is fine when operating on one object, but is not cool for multiple. @@ -678,7 +673,7 @@ class CQ(object): def _rot(obj): startPt = obj.Center() endPt = startPt + endVec - return obj.rotate(startPt,endPt,angleDegrees) + return obj.rotate(startPt, endPt, angleDegrees) return self.each(_rot, False) @@ -701,7 +696,7 @@ class CQ(object): """ return self.newObject([o.rotate(axisStartPoint, axisEndPoint, angleDegrees) for o in self.objects]) - def translate(self,vec): + def translate(self, vec): """ Returns a copy of all of the items on the stack moved by the specified translation vector. @@ -718,7 +713,7 @@ class CQ(object): return self.newObject([o.translate(vec) for o in self.objects]) - def shell(self,thickness): + def shell(self, thickness): """ Remove the selected faces to create a shell of the specified thickness. @@ -751,20 +746,18 @@ class CQ(object): Future Enhancements: Better selectors to make it easier to select multiple faces - """ solidRef = self.findSolid() for f in self.objects: if type(f) != Face: - raise ValueError ("Shelling requires that faces be selected") + raise ValueError("Shelling requires that faces be selected") - s = solidRef.shell(self.objects,thickness) + s = solidRef.shell(self.objects, thickness) solidRef.wrapped = s.wrapped return self.newObject([s]) - - def fillet(self,radius): + def fillet(self, radius): """ Fillets a solid on the selected edges. @@ -788,12 +781,13 @@ class CQ(object): edgeList = self.edges().vals() if len(edgeList) < 1: - raise ValueError ("Fillets requires that edges be selected") + raise ValueError("Fillets requires that edges be selected") - s = solid.fillet(radius,edgeList) + s = solid.fillet(radius, edgeList) solid.wrapped = s.wrapped return self.newObject([s]) + class Workplane(CQ): """ Defines a coordinate system in space, in which 2-d coordinates can be used. @@ -817,19 +811,16 @@ class Workplane(CQ): .. note:: You can also create workplanes on the surface of existing faces using :py:meth:`CQ.workplane` - - """ FOR_CONSTRUCTION = 'ForConstruction' - - def __init__(self, inPlane ,origin=(0,0,0), obj=None): + def __init__(self, inPlane, origin=(0, 0, 0), obj=None): """ make a workplane from a particular plane - :param plane: the plane in which the workplane will be done - :type plane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) + :param inPlane: the plane in which the workplane will be done + :type inPlane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) :param origin: the desired origin of the new workplane :type origin: a 3-tuple in global coordinates, or None to default to the origin :param obj: an object to use initially for the stack @@ -848,21 +839,21 @@ class Workplane(CQ): if inPlane.__class__.__name__ == 'Plane': tmpPlane = inPlane elif type(inPlane) == str: - tmpPlane = Plane.named(inPlane,origin) + tmpPlane = Plane.named(inPlane, origin) else: tmpPlane = None - if tmpPlane == None: + if tmpPlane is None: raise ValueError(" Provided value %s is not a valid work plane." % str(inPlane)) self.obj = obj self.plane = tmpPlane self.firstPoint = None - self.objects = [self.plane.origin] #changed so that workplane has the center as the first item on the stack + self.objects = [self.plane.origin] # changed so that workplane has the center as the first item on the stack self.parent = None self.ctx = CQContext() - def transformed(self,rotate=(0,0,0),offset=(0,0,0)): + def transformed(self, rotate=(0, 0, 0), offset=(0, 0, 0)): """ Create a new workplane based on the current one. The origin of the new plane is located at the existing origin+offset vector, where offset is given in @@ -881,13 +872,13 @@ class Workplane(CQ): offset = offset.toTuple() p = self.plane.rotated(rotate) - p.setOrigin3d(self.plane.toWorldCoords(offset )) + p.setOrigin3d(self.plane.toWorldCoords(offset)) ns = self.newObject([p.origin]) ns.plane = p return ns - def newObject(self,objlist): + def newObject(self, objlist): """ Create a new workplane object from this one. @@ -908,7 +899,7 @@ class Workplane(CQ): ns.ctx = self.ctx return ns - def _findFromPoint(self,useLocalCoords=False): + def _findFromPoint(self, useLocalCoords=False): """ finds the start point for an operation when an existing point is implied. Examples include 2d operations such as lineTo, @@ -931,10 +922,10 @@ class Workplane(CQ): """ obj = self.objects[0] p = None - if isinstance(obj,Edge): + if isinstance(obj, Edge): p = obj.endPoint() - elif isinstance(obj,Vector): - p = obj + elif isinstance(obj, Vector): + p = obj else: raise RuntimeError("Cannot convert object type '%s' to vector " % type(obj)) @@ -943,7 +934,7 @@ class Workplane(CQ): else: return p - def rarray(self,xSpacing,ySpacing,xCount,yCount,center=True): + def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True): """ Creates an array of points and pushes them onto the stack. If you want to position the array at another point, create another workplane @@ -952,7 +943,7 @@ class Workplane(CQ): :param xSpacing: spacing between points in the x direction ( must be > 0) :param ySpacing: spacing between points in the y direction ( must be > 0) :param xCount: number of points ( > 0 ) - :param yCount: number of poitns ( > 0 ) + :param yCount: number of points ( > 0 ) :param center: if true, the array will be centered at the center of the workplane. if false, the lower left corner will be at the center of the work plane """ @@ -960,10 +951,10 @@ class Workplane(CQ): if xSpacing < 1 or ySpacing < 1 or xCount < 1 or yCount < 1: raise ValueError("Spacing and count must be > 0 ") - lpoints = [] #coordinates relative to bottom left point + lpoints = [] # coordinates relative to bottom left point for x in range(xCount): for y in range(yCount): - lpoints.append( (xSpacing*(x), ySpacing*(y)) ) + lpoints.append((xSpacing * x, ySpacing * y)) #shift points down and left relative to origin if requested if center: @@ -971,12 +962,12 @@ class Workplane(CQ): yc = ySpacing*(yCount-1) * 0.5 cpoints = [] for p in lpoints: - cpoints.append( ( p[0] - xc, p[1] - yc )) + cpoints.append((p[0] - xc, p[1] - yc)) lpoints = list(cpoints) return self.pushPoints(lpoints) - def pushPoints(self,pntList): + def pushPoints(self, pntList): """ Pushes a list of points onto the stack as vertices. The points are in the 2-d coordinate space of the workplane face @@ -1002,7 +993,7 @@ class Workplane(CQ): return self.newObject(vecs) - def center(self,x,y): + def center(self, x, y): """ Shift local coordinates to the specified location. @@ -1030,11 +1021,11 @@ class Workplane(CQ): """ "Shift local coordinates to the specified location, according to current coordinates" - self.plane.setOrigin2d(x,y) + self.plane.setOrigin2d(x, y) n = self.newObject([self.plane.origin]) return n - def lineTo(self, x, y,forConstruction=False): + def lineTo(self, x, y, forConstruction=False): """ Make a line from the current point to the provided point @@ -1049,7 +1040,7 @@ class Workplane(CQ): endPoint = self.plane.toWorldCoords((x, y)) - p = Edge.makeLine(startPoint,endPoint) + p = Edge.makeLine(startPoint, endPoint) if not forConstruction: self._addPendingEdge(p) @@ -1057,7 +1048,7 @@ class Workplane(CQ): return self.newObject([p]) #line a specified incremental amount from current point - def line(self, xDist, yDist ,forConstruction=False): + def line(self, xDist, yDist, forConstruction=False): """ Make a line from the current point to the provided point, using dimensions relative to the current point @@ -1067,58 +1058,53 @@ class Workplane(CQ): :return: the workplane object with the current point at the end of the new line see :py:meth:`lineTo` if you want to use absolute coordinates to make a line instead. - """ - p = self._findFromPoint(True) #return local coordinates - return self.lineTo(p.x + xDist, yDist + p.y,forConstruction) + p = self._findFromPoint(True) # return local coordinates + return self.lineTo(p.x + xDist, yDist + p.y, forConstruction) - def vLine(self, distance,forConstruction=False): + def vLine(self, distance, forConstruction=False): """ Make a vertical line from the current point the provided distance :param float distance: (y) distance from current point :return: the workplane object with the current point at the end of the new line - """ - return self.line(0, distance,forConstruction) + return self.line(0, distance, forConstruction) - def vLineTo(self,yCoord,forConstruction=False): + def vLineTo(self, yCoord, forConstruction=False): """ - Make a vertcial line from the current point to the provided y coordinate. + Make a veritcal line from the current point to the provided y coordinate. - Useful if it is more convienient to specify the end location rather than distance, + Useful if it is more convenient to specify the end location rather than distance, as in :py:meth:`vLine` :param float yCoord: y coordinate for the end of the line :return: the Workplane object with the current point at the end of the new line - """ p = self._findFromPoint(True) - return self.lineTo(p.x,yCoord,forConstruction) + return self.lineTo(p.x, yCoord, forConstruction) - def hLineTo(self,xCoord,forConstruction=False): + def hLineTo(self, xCoord, forConstruction=False): """ - Make a horizontal line from the curren tpoint to the provided x coordinate. + Make a horizontal line from the current point to the provided x coordinate. - Useful if it is more convienient to specify the end location rather than distance, + Useful if it is more convenient to specify the end location rather than distance, as in :py:meth:`hLine` :param float xCoord: x coordinate for the end of the line :return: the Workplane object with the current point at the end of the new line - """ p = self._findFromPoint(True) - return self.lineTo(xCoord,p.y,forConstruction) + return self.lineTo(xCoord, p.y, forConstruction) - def hLine(self, distance,forConstruction=False): + def hLine(self, distance, forConstruction=False): """ Make a horizontal line from the current point the provided distance :param float distance: (x) distance from current point :return: the Workplane object with the current point at the end of the new line - """ - return self.line(distance, 0,forConstruction) + return self.line(distance, 0, forConstruction) #absolute move in current plane, not drawing def moveTo(self, x=0, y=0): @@ -1127,7 +1113,7 @@ class Workplane(CQ): :param x: desired x location, in local coordinates :type x: float, or none for zero - :param y: desired y location, in local coorindates + :param y: desired y location, in local coordinates :type y: float, or none for zero. Not to be confused with :py:meth:`center`, which moves the center of the entire @@ -1136,7 +1122,7 @@ class Workplane(CQ): See :py:meth:`move` to do the same thing but using relative dimensions """ - newCenter = Vector(x,y,0) + newCenter = Vector(x, y, 0) return self.newObject([self.plane.toWorldCoords(newCenter)]) #relative move in current plane, not drawing @@ -1146,7 +1132,7 @@ class Workplane(CQ): :param xDist: desired x distance, in local coordinates :type xDist: float, or none for zero - :param yDist: desired y distance, in local coorindates + :param yDist: desired y distance, in local coordinates :type yDist: float, or none for zero. Not to be confused with :py:meth:`center`, which moves the center of the entire @@ -1156,10 +1142,9 @@ class Workplane(CQ): See :py:meth:`moveTo` to do the same thing but using absolute coordinates """ p = self._findFromPoint(True) - newCenter = p + Vector(xDist,yDist,0) + newCenter = p + Vector(xDist, yDist, 0) return self.newObject([self.plane.toWorldCoords(newCenter)]) - def spline(self, listOfXYTuple, forConstruction=False): """ Create a spline interpolated through the provided points. @@ -1169,7 +1154,7 @@ class Workplane(CQ): :return: a Workplane object with the current point at the end of the spline The spline will begin at the current point, and - end with the last point in the XY typle list + end with the last point in the XY tuple list This example creates a block with a spline for one side:: @@ -1206,7 +1191,7 @@ class Workplane(CQ): return self.newObject([e]) - def threePointArc(self,point1, point2,forConstruction=False): + def threePointArc(self, point1, point2, forConstruction=False): """ Draw an arc from the current point, through point1, and ending at point2 @@ -1216,7 +1201,7 @@ class Workplane(CQ): :type point2: 2-tuple, in workplane coordinates :return: a workplane with the current point at the end of the arc - Future Enhancments: + Future Enhancements: provide a version that allows an arc using relative measures provide a centerpoint arc provide tangent arcs @@ -1227,21 +1212,21 @@ class Workplane(CQ): gpoint1 = self.plane.toWorldCoords(point1) gpoint2 = self.plane.toWorldCoords(point2) - arc = Edge.makeThreePointArc(gstartPoint,gpoint1,gpoint2) + arc = Edge.makeThreePointArc(gstartPoint, gpoint1, gpoint2) if not forConstruction: self._addPendingEdge(arc) return self.newObject([arc]) - def rotateAndCopy(self,matrix): + def rotateAndCopy(self, matrix): """ Makes a copy of all edges on the stack, rotates them according to the provided matrix, and then attempts to consolidate them into a single wire. :param matrix: a 4xr transformation matrix, in global coordinates :type matrix: a FreeCAD Base.Matrix object - :return: a cadquery object with consolidated wires, and any originals on the stack. + :return: a CadQuery object with consolidated wires, and any originals on the stack. The most common use case is to create a set of open edges, and then mirror them around either the X or Y axis to complete a closed shape. @@ -1251,8 +1236,6 @@ class Workplane(CQ): Future Enhancements: faster implementation: this one transforms 3 times to accomplish the result - - """ #convert edges to a wire, if there are pending edges @@ -1312,7 +1295,7 @@ class Workplane(CQ): tm.rotateX(math.pi) return self.rotateAndCopy(tm) - def _addPendingEdge(self,edge): + def _addPendingEdge(self, edge): """ Queues an edge for later combination into a wire. @@ -1324,7 +1307,7 @@ class Workplane(CQ): if self.ctx.firstPoint is None: self.ctx.firstPoint = self.plane.toLocalCoords(edge.startPoint()) - def _addPendingWire(self,wire): + def _addPendingWire(self, wire): """ Queue a Wire for later extrusion @@ -1341,7 +1324,6 @@ class Workplane(CQ): """ self.ctx.pendingWires.append(wire) - def consolidateWires(self): """ Attempt to consolidate wires on the stack into a single. @@ -1370,9 +1352,7 @@ class Workplane(CQ): r._addPendingWire(w) return r - - - def wire(self,forConstruction=False): + def wire(self, forConstruction=False): """ Returns a CQ object with all pending edges connected into a wire. @@ -1382,11 +1362,11 @@ class Workplane(CQ): :param forConstruction: whether the wire should be used to make a solid, or if it is just for reference :type forConstruction: boolean. true if the object is only for reference - This method is primarily of use to plugin developers making utilites for 2-d construction. This method - shoudl be called when a user operation implies that 2-d construction is finished, and we are ready to + This method is primarily of use to plugin developers making utilities for 2-d construction. This method + should be called when a user operation implies that 2-d construction is finished, and we are ready to begin working in 3d - SEE '2-d construction concepts' for a more detailed explanation of how cadquery handles edges, wires, etc + SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles edges, wires, etc Any non edges will still remain. """ @@ -1411,7 +1391,7 @@ class Workplane(CQ): return self.newObject(others + [w]) - def each(self,callBackFunction,useLocalCoordinates=False): + def each(self, callBackFunction, useLocalCoordinates=False): """ runs the provided function on each value in the stack, and collects the return values into a new CQ object. @@ -1434,7 +1414,7 @@ class Workplane(CQ): If true, the calling function is assumed to be working in local coordinates. Objects are transformed to local coordinates before they are passed into the callback method, and result objects are transformed - to global coorindates after they are returned. + to global coordinates after they are returned. This allows plugin developers to create objects in local coordinates, without worrying about the fact that the working plane is different than the global coordinate system. @@ -1457,12 +1437,11 @@ class Workplane(CQ): if not r.forConstruction: self._addPendingWire(r) - results.append ( r ) - + results.append(r) return self.newObject(results) - def eachpoint(self,callbackFunction, useLocalCoordinates=False): + def eachpoint(self, callbackFunction, useLocalCoordinates=False): """ Same as each(), except each item on the stack is converted into a point before it is passed into the callback function. @@ -1491,11 +1470,9 @@ class Workplane(CQ): for v in self.objects: pnts.append(v.Center()) - return self.newObject(pnts).each(callbackFunction,useLocalCoordinates ) + return self.newObject(pnts).each(callbackFunction, useLocalCoordinates) - - #make a rectangle - def rect(self,xLen,yLen,centered=True,forConstruction=False): + def rect(self, xLen, yLen, centered=True, forConstruction=False): """ Make a rectangle for each item on the stack. @@ -1520,27 +1497,27 @@ class Workplane(CQ): """ def makeRectangleWire(pnt): - #here pnt is in local coordinates due to useLocalCoords=True - (xc,yc,zc) = pnt.toTuple() + # Here pnt is in local coordinates due to useLocalCoords=True + # (xc,yc,zc) = pnt.toTuple() if centered: - p1 = pnt.add(Vector(xLen/-2.0, yLen/-2.0,0) ) - p2 = pnt.add(Vector(xLen/2.0, yLen/-2.0,0) ) - p3 = pnt.add(Vector(xLen/2.0, yLen/2.0,0) ) - p4 = pnt.add(Vector(xLen/-2.0, yLen/2.0,0) ) + p1 = pnt.add(Vector(xLen/-2.0, yLen/-2.0, 0)) + p2 = pnt.add(Vector(xLen/2.0, yLen/-2.0, 0)) + p3 = pnt.add(Vector(xLen/2.0, yLen/2.0, 0)) + p4 = pnt.add(Vector(xLen/-2.0, yLen/2.0, 0)) else: p1 = pnt - p2 = pnt.add(Vector(xLen,0,0)) - p3 = pnt.add(Vector( xLen,yLen,0 )) - p4 = pnt.add(Vector(0,yLen,0)) + p2 = pnt.add(Vector(xLen, 0, 0)) + p3 = pnt.add(Vector(xLen, yLen, 0)) + p4 = pnt.add(Vector(0, yLen, 0)) - w = Wire.makePolygon([p1,p2,p3,p4,p1],forConstruction) + w = Wire.makePolygon([p1, p2, p3, p4, p1], forConstruction) return w #return Part.makePolygon([p1,p2,p3,p4,p1]) - return self.eachpoint(makeRectangleWire,True) + return self.eachpoint(makeRectangleWire, True) #circle from current point - def circle(self,radius,forConstruction=False): + def circle(self, radius, forConstruction=False): """ Make a circle for each item on the stack. @@ -1568,35 +1545,36 @@ class Workplane(CQ): """ def makeCircleWire(obj): - cir = Wire.makeCircle(radius,obj,Vector(0,0,1)) + cir = Wire.makeCircle(radius, obj, Vector(0, 0, 1)) cir.forConstruction = forConstruction return cir - return self.eachpoint(makeCircleWire,useLocalCoordinates=True) + return self.eachpoint(makeCircleWire, useLocalCoordinates=True) - def polygon(self,nSides,diameter): + def polygon(self, nSides, diameter): """ - Creates a polygon incribed in a circle of the specified diamter for each point on the stack + Creates a polygon inscribed in a circle of the specified diameter for each point on the stack The first vertex is always oriented in the x direction. :param nSides: number of sides, must be > 3 - :param diameter: the size of the circle the polygon is incribed into + :param diameter: the size of the circle the polygon is inscribed into :return: a polygon wire """ def _makePolygon(center): #pnt is a vector in local coordinates - angle = 2.0 *math.pi / nSides + angle = 2.0 * math.pi / nSides pnts = [] for i in range(nSides+1): - pnts.append( center + Vector((diameter / 2.0 * math.cos(angle*i)),(diameter / 2.0 * math.sin(angle*i)),0)) + pnts.append(center + Vector((diameter / 2.0 * math.cos(angle*i)), + (diameter / 2.0 * math.sin(angle*i)), 0)) return Wire.makePolygon(pnts) - return self.eachpoint(_makePolygon,True) + return self.eachpoint(_makePolygon, True) - def polyline(self,listOfXYTuple,forConstruction=False): + def polyline(self, listOfXYTuple, forConstruction=False): """ Create a polyline from a list of points @@ -1608,19 +1586,17 @@ class Workplane(CQ): *NOTE* most commonly, the resulting wire should be closed. - Future Enhacement: + Future Enhancement: This should probably yield a list of edges, not a wire, so that it is possible to combine a polyline with other edges and arcs """ - vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple ] + vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple] w = Wire.makePolygon(vecs) if not forConstruction: self._addPendingWire(w) return self.newObject([w]) - #finish a set of lines. - # def close(self): """ End 2-d construction, and attempt to build a closed wire. @@ -1654,7 +1630,7 @@ class Workplane(CQ): else: return 1000000 - def cutEach(self,fcn,useLocalCoords=False): + def cutEach(self, fcn, useLocalCoords=False): """ Evaluates the provided function at each point on the stack ( ie, eachpoint ) and then cuts the result from the context solid. @@ -1665,10 +1641,10 @@ class Workplane(CQ): """ ctxSolid = self.findSolid() if ctxSolid is None: - raise ValueError ("Must have a solid in the chain to cut from!") + raise ValueError("Must have a solid in the chain to cut from!") #will contain all of the counterbores as a single compound - results = self.eachpoint(fcn,useLocalCoords).vals() + results = self.eachpoint(fcn, useLocalCoords).vals() s = ctxSolid for cb in results: s = s.cut(cb) @@ -1677,12 +1653,12 @@ class Workplane(CQ): return self.newObject([s]) #but parameter list is different so a simple function pointer wont work - def cboreHole(self,diameter,cboreDiameter,cboreDepth,depth=None): + def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None): """ Makes a counterbored hole for each item on the stack. :param diameter: the diameter of the hole - :type diamter: float > 0 + :type diameter: float > 0 :param cboreDiameter: the diameter of the cbore :type cboreDiameter: float > 0 and > diameter :param cboreDepth: depth of the counterbore @@ -1714,25 +1690,25 @@ class Workplane(CQ): returns a solid suitable for subtraction pnt is in local coordinates """ - boreDir = Vector(0,0,-1) + boreDir = Vector(0, 0, -1) #first make the hole - hole = Solid.makeCylinder(diameter/2.0,depth,center,boreDir) # local coordianates! + hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coordianates! #add the counter bore - cbore = Solid.makeCylinder(cboreDiameter/2.0,cboreDepth,center,boreDir) + cbore = Solid.makeCylinder(cboreDiameter / 2.0, cboreDepth, center, boreDir) r = hole.fuse(cbore) return r - return self.cutEach(_makeCbore,True) + return self.cutEach(_makeCbore, True) #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work - def cskHole(self,diameter, cskDiameter,cskAngle,depth=None): + def cskHole(self, diameter, cskDiameter, cskAngle, depth=None): """ Makes a countersunk hole for each item on the stack. :param diameter: the diameter of the hole - :type diamter: float > 0 + :type diameter: float > 0 :param cskDiameter: the diameter of the countersink :type cskDiameter: float > 0 and > diameter :param cskAngle: angle of the countersink, in degrees ( 82 is common ) @@ -1762,27 +1738,26 @@ class Workplane(CQ): def _makeCsk(center): #center is in local coordinates - boreDir = Vector(0,0,-1) + boreDir = Vector(0, 0, -1) #first make the hole - hole = Solid.makeCylinder(diameter/2.0,depth,center,boreDir) # local coords! + hole = Solid.makeCylinder(diameter/2.0, depth, center, boreDir) # local coords! r = cskDiameter / 2.0 h = r / math.tan(math.radians(cskAngle / 2.0)) - csk = Solid.makeCone(r,0.0,h,center,boreDir) + csk = Solid.makeCone(r, 0.0, h, center, boreDir) r = hole.fuse(csk) return r - return self.cutEach(_makeCsk,True) - + return self.cutEach(_makeCsk, True) #TODO: almost all code duplicated! #but parameter list is different so a simple function pointer wont work - def hole(self,diameter,depth=None): + def hole(self, diameter, depth=None): """ Makes a hole for each item on the stack. :param diameter: the diameter of the hole - :type diamter: float > 0 + :type diameter: float > 0 :param depth: the depth of the hole :type depth: float > 0 or None to drill thru the entire part. @@ -1810,15 +1785,15 @@ class Workplane(CQ): returns a solid suitable for subtraction pnt is in local coordinates """ - boreDir = Vector(0,0,-1) + boreDir = Vector(0, 0, -1) #first make the hole - hole = Solid.makeCylinder(diameter/2.0,depth,center,boreDir) # local coordianates! + hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordianates! return hole - return self.cutEach(_makeHole,True) + return self.cutEach(_makeHole, True) #TODO: duplicated code with _extrude and extrude - def twistExtrude(self,distance,angleDegrees,combine=True): + def twistExtrude(self, distance, angleDegrees, combine=True): """ Extrudes a wire in the direction normal to the plane, but also twists by the specified angle over the length of the extrusion @@ -1838,7 +1813,7 @@ class Workplane(CQ): """ #group wires together into faces based on which ones are inside the others #result is a list of lists - wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires),self.plane,[]) + wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion @@ -1853,7 +1828,7 @@ class Workplane(CQ): #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are multiple sets r = None for ws in wireSets: - thisObj = Solid.extrudeLinearWithRotation(ws[0],ws[1:],self.plane.origin, eDir,angleDegrees) + thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin, eDir, angleDegrees) if r is None: r = thisObj else: @@ -1864,7 +1839,7 @@ class Workplane(CQ): else: return self.newObject([r]) - def extrude(self,distance,combine=True): + def extrude(self, distance, combine=True): """ Use all un-extruded wires in the parent chain to create a prismatic solid. @@ -1886,7 +1861,7 @@ class Workplane(CQ): Support for non-prismatic extrusion ( IE, sweeping along a profile, not just perpendicular to the plane extrude to surface. this is quite tricky since the surface selected may not be planar """ - r = self._extrude(distance) #returns a Solid ( or a compound if there were multiple ) + r = self._extrude(distance) # returns a Solid (or a compound if there were multiple) if combine: return self._combineWithBase(r) else: @@ -1914,14 +1889,14 @@ class Workplane(CQ): and the resulting solid becomes the new context solid. """ #Make sure we account for users specifying angles larger than 360 degrees - angleDegrees = angleDegrees % 360.0 + angleDegrees %= 360.0 #Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees #The default start point of the vector defining the axis of rotation will be the origin of the workplane if axisStart is None: - axisStart = self.plane.toWorldCoords((0,0)).toTuple() + axisStart = self.plane.toWorldCoords((0, 0)).toTuple() else: axisStart = self.plane.toWorldCoords(axisStart).toTuple() @@ -1929,35 +1904,19 @@ class Workplane(CQ): if axisEnd is None: #Make sure we match the user's assumed axis of rotation if they specified an start but not an end if axisStart[1] != 0: - axisEnd = self.plane.toWorldCoords((0,axisStart[1])).toTuple() + axisEnd = self.plane.toWorldCoords((0, axisStart[1])).toTuple() else: - axisEnd = self.plane.toWorldCoords((0,1)).toTuple() + axisEnd = self.plane.toWorldCoords((0, 1)).toTuple() else: axisEnd = self.plane.toWorldCoords(axisEnd).toTuple() - r = self._revolve(angleDegrees, axisStart, axisEnd) # returns a Solid ( or a compound if there were multiple ) + r = self._revolve(angleDegrees, axisStart, axisEnd) # returns a Solid ( or a compound if there were multiple ) if combine: return self._combineWithBase(r) else: return self.newObject([r]) - def _combineWithBase2(self,obj): - """ - Combines the provided object with the base solid, if one can be found. - :param obj: - :return: a new object that represents the result of combining the base object with obj, - or obj if one could not be found - - """ - baseSolid = self.findSolid(searchParents=True) - r = obj - if baseSolid is not None: - r = baseSolid.fuse(obj) - baseSolid.wrapped = r.wrapped - - return self.newObject([r]) - - def _combineWithBase(self,obj): + def _combineWithBase(self, obj): """ Combines the provided object with the base solid, if one can be found. :param obj: @@ -1988,7 +1947,7 @@ class Workplane(CQ): return self.newObject([s]) - def union(self,toUnion=None,combine=True): + def union(self, toUnion=None, combine=True): """ Unions all of the items on the stack of toUnion with the current solid. If there is no current solid, the items in toUnion are unioned together. @@ -2005,7 +1964,7 @@ class Workplane(CQ): #first collect all of the items together if type(toUnion) == CQ or type(toUnion) == Workplane: solids = toUnion.solids().vals() - if len(solids) < 1 : + if len(solids) < 1: raise ValueError("CQ object must have at least one solid on the stack to union!") newS = solids.pop(0) for s in solids: @@ -2016,7 +1975,7 @@ class Workplane(CQ): raise ValueError("Cannot union Type '%s' " % str(type(toUnion))) #now combine with existing solid, if there is one - solidRef = self.findSolid(searchStack=True,searchParents=True) #look for parents to cut from + solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from if combine and solidRef is not None: t = solidRef.fuse(newS) solidRef.wrapped = newS.wrapped @@ -2024,7 +1983,7 @@ class Workplane(CQ): else: return self.newObject([newS]) - def cut(self,toCut,combine=True): + def cut(self, toCut, combine=True): """ Cuts the provided solid from the current solid, IE, perform a solid subtraction @@ -2038,10 +1997,10 @@ class Workplane(CQ): """ - solidRef = self.findSolid(searchStack=True,searchParents=True) #look for parents to cut from + solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from if solidRef is None: - raise ValueError("Cannot find solid to cut from!!!") + raise ValueError("Cannot find solid to cut from!!!") solidToCut = None if type(toCut) == CQ or type(toCut) == Workplane: solidToCut = toCut.val() @@ -2055,8 +2014,7 @@ class Workplane(CQ): solidRef.wrapped = newS.wrapped return self.newObject([newS]) - - def cutBlind(self,distanceToCut): + def cutBlind(self, distanceToCut): """ Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. @@ -2080,11 +2038,11 @@ class Workplane(CQ): solidRef = self.findSolid() - s= solidRef.cut(toCut) + s = solidRef.cut(toCut) solidRef.wrapped = s.wrapped return self.newObject([s]) - def cutThruAll(self,positive=False): + def cutThruAll(self, positive=False): """ Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. @@ -2104,8 +2062,7 @@ class Workplane(CQ): return self.cutBlind(maxDim) - - def loft(self,filled=True,ruled=False,combine=True): + def loft(self, filled=True, ruled=False, combine=True): """ Make a lofted solid, through the set of wires. :return: @@ -2116,14 +2073,14 @@ class Workplane(CQ): r = Solid.makeLoft(wiresToLoft, ruled) if combine: - parentSolid = self.findSolid(searchStack=False,searchParents=True) + parentSolid = self.findSolid(searchStack=False, searchParents=True) if parentSolid is not None: r = parentSolid.fuse(r) parentSolid.wrapped = r.wrapped return self.newObject([r]) - def _extrude(self,distance): + def _extrude(self, distance): """ Make a prismatic solid from the existing set of pending wires. @@ -2140,9 +2097,9 @@ class Workplane(CQ): #group wires together into faces based on which ones are inside the others #result is a list of lists s = time.time() - wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires),self.plane,[]) + wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) #print "sorted wires in %d sec" % ( time.time() - s ) - self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion + self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion #compute extrusion vector and extrude eDir = self.plane.zDir.multiply(distance) @@ -2193,7 +2150,7 @@ class Workplane(CQ): This method is a utility method, primarily for plugin and internal use. """ #We have to gather the wires to be revolved - wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires),self.plane,[]) + wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) #Mark that all of the wires have been used to create a revolution self.ctx.pendingWires = [] @@ -2206,7 +2163,7 @@ class Workplane(CQ): return Compound.makeCompound(toFuse) - def box(self,length,width,height,centered=(True,True,True),combine=True): + def box(self, length, width, height, centered=(True, True, True), combine=True): """ Return a 3d box with specified dimensions for each object on the stack. @@ -2249,17 +2206,17 @@ class Workplane(CQ): def _makebox(pnt): #(xp,yp,zp) = self.plane.toLocalCoords(pnt) - (xp,yp,zp) = pnt.toTuple() + (xp, yp, zp) = pnt.toTuple() if centered[0]: - xp = xp-(length/2.0) + xp -= (length / 2.0) if centered[1]: - yp = yp-(width/2.0) + yp -= (width / 2.0) if centered[2]: - zp = zp-(height/2.0) + zp -= (height / 2.0) - return Solid.makeBox(length,width,height,Vector(xp,yp,zp)) + return Solid.makeBox(length, width, height, Vector(xp, yp, zp)) - boxes = self.eachpoint(_makebox,True) + boxes = self.eachpoint(_makebox, True) #if combination is not desired, just return the created boxes if not combine: @@ -2314,11 +2271,11 @@ class Workplane(CQ): (xp, yp, zp) = pnt.toTuple() if centered[0]: - xp = xp - radius + xp -= radius if centered[1]: - yp = yp - radius + yp -= radius if centered[2]: - zp = zp - radius + zp -= radius return Solid.makeSphere(radius, Vector(xp, yp, zp), direct, angle1, angle2, angle3) From 5531d695fa234b5c025737602ef2d9d065058c0d Mon Sep 17 00:00:00 2001 From: Jeremy Wright Date: Mon, 15 Jun 2015 12:24:38 -0400 Subject: [PATCH 2/2] First round of clean up on CQ.py. Coverage still needs to be increased. --- .coverage | Bin 7311 -> 7370 bytes cadquery/CQ.py | 1318 +++++++++++++++++++++-------------------- tests/TestCadQuery.py | 36 +- 3 files changed, 713 insertions(+), 641 deletions(-) diff --git a/.coverage b/.coverage index 2714befb645e0f05423af13754ba74ab3cbdfeee..612f7c075cb8c48b43e9ff8d2bf1c82431b18957 100644 GIT binary patch delta 3032 zcmZwJ`PY`y9>?*%`+MzuUAKxRg-VY`i6W#@Cy~(CJBm1IGDRhGMUxw)aaEq4XpoGd zd7hKZQ*%j@92E{tN-9*&eJ-B0jgm(S}xs=+4>W_OkB=~l_|>@2Hn)vUVJ zv^sWy)wc%L&>C4|Yhu?L*3z!C>#enQv`%)Xb+)^#i*>bb*4^&2Cv1QXvcWdQhS_i% zVWX_T#@I79*2dWcn`+Z-hRw9uHs2Q7B3o+9Y`Lwlx9n~E)K=U2tZlSyw%zvFUfXB; z?SLJ$L-xBJ#|yD=o=``qCtM;l5E=@NgvLU1;VR*3;ToZZaIJvQQn*gIQD`IlQ)nx+ z7di+Xg-*il!kt1lp}X*q&_n1gWcv!kgyF&{VY2X=@VfAZFjbf)%n)V?%Y_xfTf$1= z9pPQ!ec?l4m++ksIfA2#qne|kqmkoA$4!ndj&6=Vj((0Ij(kUfV}@gvW42?CW3FR< z*0Ip>vEviRD#upGR}N8BiIiALl#EhT$x|vTRg~&V4dra5mU4x1rE-MP+6obQsVXdZEOM4O1)Bf3ZQjp&y<88JFy zZp6HZ1rZA)7Dp_NSQhbS#9I+-Bi2Q%kJuRTS;XduEfHHI_C*|s_&MU2h{L&Cjzs(x zaV+9QLs4tjp^9QGXX9QFL_`Q3BObKG;nQzRgPC=ds#2C4vVuz_7sZ zz=*)eKz?9mU`}9BU`b$k;LX5_z{3G5B*3+xa4lnopV91ffk zQzE8R%xN)a#FUSz7?T%seazi4U1GY%+!J$e%z&6dF~eep$Bc-{j~Ns5Ow8Dr@i7x) zo{yOn^J2`)F;ilu#!QQu9y23mR?M83l`(5#*2b)hvuudj7_%v6Tg+E6+he|t`B%)2 zm|ZakV-Cd}jyV!@H0DIiA2CG|Bta9(CY+H_A)#WznF)CbwG-+joS#rP;i7~F2@Mk( zCHy0yNy23bO%pCpXqM1C;iiP_%?WK2+9&i(=#}tD!lMa&68a?!PZ*hypD;S1AYn|x zvkBu8-br{r;e&(^6Fy4#IN_6oPZL%rtWDUEurXnC!j^6!ik$Y~2I724GNg0Y} zD3+mkhLbatD3PIbhO!yTWjH<4xlOZzys{V0%a895*=y3>Z)I)lW@~Hh?6%zUcgb!3 z{q~SOY(1=}^|Idfqz%mN|Il>(N9NZ5Y0J+EFu^9;BzwVLw3qBXrXH(QreytMhgYP7~xrAtT0X(FH8_73eO9ZgcpRDgqMX^ zgjXf|Uk?AYaEdTpm@Ui^<_hzK1;RpMv9Lr~ngaQDO5=NlG_Dd>3u}b6!g^t|uwD2@ z*dgpp)!Z%Y5%vo|2?vCqg`>i+!ZG2vP{iea<^G~Ib|j9Xj^d6}93>nj9c3Kl92FcD z9hDq;slZiJgwJ-=be!Wj*HPPXzT*N%UB`uviyRj_>NzfP)OR#U#cu3q;%Mf$($U;; zwWEc@a(A_Kv~sj|v~jd`v~#p~bZ~TZbaLG4xUHa4$#U)Pb3EYa;ppk;<>;NF-#2x? zzvHpoR~(N!o^T9w3~~&1Om<9hOm$3iOn1!8m4IVG+5(FliyccGOC8G!@=KP>ew5b1 z8pm44I>!dbX2+H^7QS?Bb8L6)aO`yKa(wIf-m%xQ-*M3Kv*VECu;ZBHxT8p6S@<+A ziYdjF5=v>Mj8axPLn*IRP|hsOjjAmRd!wduPFfwcl{!jYB-4|0E#MR!~g&Q delta 2993 zcmZwJdGJ^B9>?+f{=Cm;sccD$tbOwl}aJ)<>rb|m`ccUzfSX;acAy7=btlkWUS6d6~U>)ry>tbE4yY;Z1*4z5p-8Rhbv-|A< zd&nNPzuTksggt2^Y?M7`&)amHVK3Tjn``rIp)InP?G;;M%j{iy&sNzQ+fcBLw%K;u z*Y=HlYv0?C_A{AqhH#cpL#Qd#7S0yV5$XyT2^R~O2$u>Cg+@YS;c}sg&`fA9AhZy! z6~;;Ax9XB3XeI4a}ljItTWWmL(ino&LD%#0ct7i3(KaZN_2jLsQdGX`gj z&KQ$1F5{Vu2^o_zCTC2|n3l01V`0XkjKvvCGL~k%n(=zZM;V`FY|Pl4u_a?`#*U1C zXM9%3IFRu}#=(rAGNLDWN_on7j`bYpDetN1In8s1r>>`-r@rR`PeadTo<^R=o+h4l zo(`Ulo*O-#JvVu}czSqxdU|>Kc=~xB^Zdi}glB~3DbGmHC|_W-XN+f@XS`>kXO3sC z=Vi}g&l1lv&&Qq(o{gSOp3R;uo^77(o*kY~Jv%+0d3Jerd$NEJgg^|G3X};{3>+Ug zA#h^gq`=96%7LnZ>Vdj}dVxayz`23*0}TRA1I+>!XdP%1XdCDd=oaW6=pE=2=o{!4 z=pPsm7!-IsFd{HIFg7qgFd;B8FgY+S@J8Ux!1BPmffa$3f%gNe0;>aS0&4^71A78{ z3xV$fKLmaZ{1iA8_$^Q~(l~N?q)Ft;NYhBCNS8?WNRLRbNZ-iKk^YfeB7-8gMQ)D_ zjtq(16}c~Rf8>G4gOP_K!y}JG#zvlxOpnZnycn4onH5XvI!H7?UL`;-Qlt~pe?63->3CuSsOCSFRcO{`0Nl-QKmlGvKqme`)yk@z&RGx2%i%f#-)*QrMv zX)>~M>3Z|0#9Kn4xm{x|t(CR68?3W+D-L|0;=uQ_o2|bMw7YDm-DCIWWB@8bvZ|4}S zu$2XS-&Pk*_`ufMI{VPp7h%|9+w4=@XszX`t=|G#r& zj^K!n zwqnPDIqkPPZgbr380@&iF~o6aiTA^D>8Iq(KkJz0c+T;nW2R%4<0Z#z#~jDp!x7+E z=vd@f>{vE(a@mT76^_-84;vtgfOzvH0e zXU8GOFOJ_FPEjROf>OF9B#zEQqO5YPQcfwaR8T4^m6Q{d%Ebn#s#H^|D}Pl^D`}3i zl$uH{rM6N>IY+6h)Kls!=PBna7bp#s3zdtNOO#8MhIytOZj>g<70Q)LQ>EGe_DV5U zTIRjdO1Z8WC9RdVN;{>!a=p?)=~&V-ot3V6%N!1wUU|#(QTi$Ul>y2v%0Ok1a;q{} zxkDMEe^k!UlEfL7SI)i41ImNSL&|XF5#=%EMP+93?JVUbWp*ArbCr2{?EJZI-pT{# z9pzo+U&;z)rSiVAN?EO}QPwFRE9>(J+M2h|c4ddMQ~6xkrF^OER=!d8D0`LfmHo;A a<;P+l6|d)); zero - :type radius: positive float - :raises: ValueError if at least one edge is not selected - :raises: ValueError if the solid containing the edge is not in the chain - :returns: cq object with the resulting solid selected. + :param radius: the radius of the fillet, must be > zero + :type radius: positive float + :raises: ValueError if at least one edge is not selected + :raises: ValueError if the solid containing the edge is not in the chain + :returns: cq object with the resulting solid selected. - This example will create a unit cube, with the top edges filleted:: + This example will create a unit cube, with the top edges filleted:: - s = Workplane().box(1,1,1).faces("+Z").edges().fillet(0.1) + s = Workplane().box(1,1,1).faces("+Z").edges().fillet(0.1) """ - #TODO: we will need much better edge selectors for this to work - #TODO: ensure that edges selected actually belong to the solid in the chain, otherwise, fe segfault + # TODO: we will need much better edge selectors for this to work + # TODO: ensure that edges selected actually belong to the solid in the chain, otherwise, + # TODO: we segfault solid = self.findSolid() @@ -790,50 +810,52 @@ class CQ(object): class Workplane(CQ): """ - Defines a coordinate system in space, in which 2-d coordinates can be used. + Defines a coordinate system in space, in which 2-d coordinates can be used. - :param plane: the plane in which the workplane will be done - :type plane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) - :param origin: the desired origin of the new workplane - :type origin: a 3-tuple in global coordinates, or None to default to the origin - :param obj: an object to use initially for the stack - :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial stack value. - :raises: ValueError if the provided plane is not a plane, a valid named workplane - :return: A Workplane object, with coordinate system matching the supplied plane. + :param plane: the plane in which the workplane will be done + :type plane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) + :param origin: the desired origin of the new workplane + :type origin: a 3-tuple in global coordinates, or None to default to the origin + :param obj: an object to use initially for the stack + :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial + stack value. + :raises: ValueError if the provided plane is not a plane, a valid named workplane + :return: A Workplane object, with coordinate system matching the supplied plane. - The most common use is:: + The most common use is:: - s = Workplane("XY") + s = Workplane("XY") - After creation, the stack contains a single point, the origin of the underlying plane, and the - *current point* is on the origin. + After creation, the stack contains a single point, the origin of the underlying plane, + and the *current point* is on the origin. - .. note:: - You can also create workplanes on the surface of existing faces using - :py:meth:`CQ.workplane` + .. note:: + You can also create workplanes on the surface of existing faces using + :py:meth:`CQ.workplane` """ FOR_CONSTRUCTION = 'ForConstruction' def __init__(self, inPlane, origin=(0, 0, 0), obj=None): """ - make a workplane from a particular plane + make a workplane from a particular plane - :param inPlane: the plane in which the workplane will be done - :type inPlane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) - :param origin: the desired origin of the new workplane - :type origin: a 3-tuple in global coordinates, or None to default to the origin - :param obj: an object to use initially for the stack - :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial stack value. - :raises: ValueError if the provided plane is not a plane, or one of XY|YZ|XZ - :return: A Workplane object, with coordinate system matching the supplied plane. + :param inPlane: the plane in which the workplane will be done + :type inPlane: a Plane object, or a string in (XY|YZ|XZ|front|back|top|bottom|left|right) + :param origin: the desired origin of the new workplane + :type origin: a 3-tuple in global coordinates, or None to default to the origin + :param obj: an object to use initially for the stack + :type obj: a CAD primitive, or None to use the centerpoint of the plane as the initial + stack value. + :raises: ValueError if the provided plane is not a plane, or one of XY|YZ|XZ + :return: A Workplane object, with coordinate system matching the supplied plane. - The most common use is:: + The most common use is:: - s = Workplane("XY") + s = Workplane("XY") - After creation, the stack contains a single point, the origin of the underlying plane, and the - *current point* is on the origin. + After creation, the stack contains a single point, the origin of the underlying plane, and + the *current point* is on the origin. """ if inPlane.__class__.__name__ == 'Plane': @@ -849,16 +871,18 @@ class Workplane(CQ): self.obj = obj self.plane = tmpPlane self.firstPoint = None - self.objects = [self.plane.origin] # changed so that workplane has the center as the first item on the stack + # Changed so that workplane has the center as the first item on the stack + self.objects = [self.plane.origin] self.parent = None self.ctx = CQContext() def transformed(self, rotate=(0, 0, 0), offset=(0, 0, 0)): """ Create a new workplane based on the current one. - The origin of the new plane is located at the existing origin+offset vector, where offset is given in - coordinates local to the current plane - The new plane is rotated through the angles specified by the components of the rotation vector + The origin of the new plane is located at the existing origin+offset vector, where offset is + given in coordinates local to the current plane + The new plane is rotated through the angles specified by the components of the rotation + vector. :param rotate: 3-tuple of angles to rotate, in degrees relative to work plane coordinates :param offset: 3-tuple to offset the new plane, in local work plane coordinates :return: a new work plane, transformed as requested @@ -880,15 +904,14 @@ class Workplane(CQ): def newObject(self, objlist): """ - Create a new workplane object from this one. + Create a new workplane object from this one. - Overrides CQ.newObject, and should be used by extensions, plugins, and - subclasses to create new objects. - - :param objlist: new objects to put on the stack - :type objlist: a list of CAD primitives - :return: a new Workplane object with the current workplane as a parent. + Overrides CQ.newObject, and should be used by extensions, plugins, and + subclasses to create new objects. + :param objlist: new objects to put on the stack + :type objlist: a list of CAD primitives + :return: a new Workplane object with the current workplane as a parent. """ #copy the current state to the new object @@ -901,24 +924,24 @@ class Workplane(CQ): def _findFromPoint(self, useLocalCoords=False): """ - finds the start point for an operation when an existing point - is implied. Examples include 2d operations such as lineTo, - which allows specifying the end point, and implicitly use the - end of the previous line as the starting point + Finds the start point for an operation when an existing point + is implied. Examples include 2d operations such as lineTo, + which allows specifying the end point, and implicitly use the + end of the previous line as the starting point - :return: a Vector representing the point to use, or none if - such a point is not available. + :return: a Vector representing the point to use, or none if + such a point is not available. - :param useLocalCoords: selects whether the point is returned - in local coordinates or global coordinates. + :param useLocalCoords: selects whether the point is returned + in local coordinates or global coordinates. - The algorithm is this: - * If an Edge is on the stack, its end point is used.yp - * if a vector is on the stack, it is used + The algorithm is this: + * If an Edge is on the stack, its end point is used.yp + * if a vector is on the stack, it is used - WARNING: only the first object on the stack is used. + WARNING: only the first object on the stack is used. - NOTE: + NOTE: """ obj = self.objects[0] p = None @@ -936,16 +959,16 @@ class Workplane(CQ): def rarray(self, xSpacing, ySpacing, xCount, yCount, center=True): """ - Creates an array of points and pushes them onto the stack. - If you want to position the array at another point, create another workplane - that is shifted to the position you would like to use as a reference + Creates an array of points and pushes them onto the stack. + If you want to position the array at another point, create another workplane + that is shifted to the position you would like to use as a reference - :param xSpacing: spacing between points in the x direction ( must be > 0) - :param ySpacing: spacing between points in the y direction ( must be > 0) - :param xCount: number of points ( > 0 ) - :param yCount: number of points ( > 0 ) - :param center: if true, the array will be centered at the center of the workplane. if false, the lower - left corner will be at the center of the work plane + :param xSpacing: spacing between points in the x direction ( must be > 0) + :param ySpacing: spacing between points in the y direction ( must be > 0) + :param xCount: number of points ( > 0 ) + :param yCount: number of points ( > 0 ) + :param center: if true, the array will be centered at the center of the workplane. if + false, the lower left corner will be at the center of the work plane """ if xSpacing < 1 or ySpacing < 1 or xCount < 1 or yCount < 1: @@ -969,22 +992,23 @@ class Workplane(CQ): def pushPoints(self, pntList): """ - Pushes a list of points onto the stack as vertices. - The points are in the 2-d coordinate space of the workplane face + Pushes a list of points onto the stack as vertices. + The points are in the 2-d coordinate space of the workplane face - :param pntList: a list of points to push onto the stack - :type pntList: list of 2-tuples, in *local* coordinates - :return: a new workplane with the desired points on the stack. + :param pntList: a list of points to push onto the stack + :type pntList: list of 2-tuples, in *local* coordinates + :return: a new workplane with the desired points on the stack. - A common use is to provide a list of points for a subsequent operation, such as creating circles or holes. - This example creates a cube, and then drills three holes through it, based on three points:: + A common use is to provide a list of points for a subsequent operation, such as creating + circles or holes. This example creates a cube, and then drills three holes through it, + based on three points:: - s = Workplane().box(1,1,1).faces(">Z").workplane().pushPoints([(-0.3,0.3),(0.3,0.3),(0,0)]) - body = s.circle(0.05).cutThruAll() - - Here the circle function operates on all three points, and is then extruded to create three holes. - See :py:meth:`circle` for how it works. + s = Workplane().box(1,1,1).faces(">Z").workplane().\ + pushPoints([(-0.3,0.3),(0.3,0.3),(0,0)]) + body = s.circle(0.05).cutThruAll() + Here the circle function operates on all three points, and is then extruded to create three + holes. See :py:meth:`circle` for how it works. """ vecs = [] for pnt in pntList: @@ -995,30 +1019,29 @@ class Workplane(CQ): def center(self, x, y): """ - Shift local coordinates to the specified location. + Shift local coordinates to the specified location. - The location is specified in terms of local coordinates. + The location is specified in terms of local coordinates. - :param float x: the new x location - :param float y: the new y location - :returns: the workplane object, with the center adjusted. + :param float x: the new x location + :param float y: the new y location + :returns: the workplane object, with the center adjusted. - The current point is set to the new center. - This method is useful to adjust the center point after it has been created automatically on a face, - but not where you'd like it to be. + The current point is set to the new center. + This method is useful to adjust the center point after it has been created automatically on + a face, but not where you'd like it to be. - In this example, we adjust the workplane center to be at the corner of a cube, instead of - the center of a face, which is the default:: + In this example, we adjust the workplane center to be at the corner of a cube, instead of + the center of a face, which is the default:: - #this workplane is centered at x=0.5,y=0.5, the center of the upper face - s = Workplane().box(1,1,1).faces(">Z").workplane() + #this workplane is centered at x=0.5,y=0.5, the center of the upper face + s = Workplane().box(1,1,1).faces(">Z").workplane() - s.center(-0.5,-0.5) # move the center to the corner - t = s.circle(0.25).extrude(0.2) - assert ( t.faces().size() == 9 ) # a cube with a cylindrical nub at the top right corner - - The result is a cube with a round boss on the corner + s.center(-0.5,-0.5) # move the center to the corner + t = s.circle(0.25).extrude(0.2) + assert ( t.faces().size() == 9 ) # a cube with a cylindrical nub at the top right corner + The result is a cube with a round boss on the corner """ "Shift local coordinates to the specified location, according to current coordinates" self.plane.setOrigin2d(x, y) @@ -1027,14 +1050,13 @@ class Workplane(CQ): def lineTo(self, x, y, forConstruction=False): """ - Make a line from the current point to the provided point + Make a line from the current point to the provided point - :param float x: the x point, in workplane plane coordinates - :param float y: the y point, in workplane plane coordinates - :return: the Workplane object with the current point at the end of the new line - - see :py:meth:`line` if you want to use relative dimensions to make a line instead. + :param float x: the x point, in workplane plane coordinates + :param float y: the y point, in workplane plane coordinates + :return: the Workplane object with the current point at the end of the new line + see :py:meth:`line` if you want to use relative dimensions to make a line instead. """ startPoint = self._findFromPoint(False) @@ -1050,77 +1072,77 @@ class Workplane(CQ): #line a specified incremental amount from current point def line(self, xDist, yDist, forConstruction=False): """ - Make a line from the current point to the provided point, using - dimensions relative to the current point + Make a line from the current point to the provided point, using + dimensions relative to the current point - :param float xDist: x distance from current point - :param float yDist: y distance from current point - :return: the workplane object with the current point at the end of the new line + :param float xDist: x distance from current point + :param float yDist: y distance from current point + :return: the workplane object with the current point at the end of the new line - see :py:meth:`lineTo` if you want to use absolute coordinates to make a line instead. + see :py:meth:`lineTo` if you want to use absolute coordinates to make a line instead. """ p = self._findFromPoint(True) # return local coordinates return self.lineTo(p.x + xDist, yDist + p.y, forConstruction) def vLine(self, distance, forConstruction=False): """ - Make a vertical line from the current point the provided distance + Make a vertical line from the current point the provided distance - :param float distance: (y) distance from current point - :return: the workplane object with the current point at the end of the new line + :param float distance: (y) distance from current point + :return: the workplane object with the current point at the end of the new line """ return self.line(0, distance, forConstruction) def vLineTo(self, yCoord, forConstruction=False): """ - Make a veritcal line from the current point to the provided y coordinate. + Make a vertical line from the current point to the provided y coordinate. - Useful if it is more convenient to specify the end location rather than distance, - as in :py:meth:`vLine` + Useful if it is more convenient to specify the end location rather than distance, + as in :py:meth:`vLine` - :param float yCoord: y coordinate for the end of the line - :return: the Workplane object with the current point at the end of the new line + :param float yCoord: y coordinate for the end of the line + :return: the Workplane object with the current point at the end of the new line """ p = self._findFromPoint(True) return self.lineTo(p.x, yCoord, forConstruction) def hLineTo(self, xCoord, forConstruction=False): """ - Make a horizontal line from the current point to the provided x coordinate. + Make a horizontal line from the current point to the provided x coordinate. - Useful if it is more convenient to specify the end location rather than distance, - as in :py:meth:`hLine` + Useful if it is more convenient to specify the end location rather than distance, + as in :py:meth:`hLine` - :param float xCoord: x coordinate for the end of the line - :return: the Workplane object with the current point at the end of the new line + :param float xCoord: x coordinate for the end of the line + :return: the Workplane object with the current point at the end of the new line """ p = self._findFromPoint(True) return self.lineTo(xCoord, p.y, forConstruction) def hLine(self, distance, forConstruction=False): """ - Make a horizontal line from the current point the provided distance + Make a horizontal line from the current point the provided distance - :param float distance: (x) distance from current point - :return: the Workplane object with the current point at the end of the new line + :param float distance: (x) distance from current point + :return: the Workplane object with the current point at the end of the new line """ return self.line(distance, 0, forConstruction) #absolute move in current plane, not drawing def moveTo(self, x=0, y=0): """ - Move to the specified point, without drawing. + Move to the specified point, without drawing. - :param x: desired x location, in local coordinates - :type x: float, or none for zero - :param y: desired y location, in local coordinates - :type y: float, or none for zero. + :param x: desired x location, in local coordinates + :type x: float, or none for zero + :param y: desired y location, in local coordinates + :type y: float, or none for zero. - Not to be confused with :py:meth:`center`, which moves the center of the entire - workplane, this method only moves the current point ( and therefore does not affect objects - already drawn ). + Not to be confused with :py:meth:`center`, which moves the center of the entire + workplane, this method only moves the current point ( and therefore does not affect objects + already drawn ). - See :py:meth:`move` to do the same thing but using relative dimensions + See :py:meth:`move` to do the same thing but using relative dimensions """ newCenter = Vector(x, y, 0) return self.newObject([self.plane.toWorldCoords(newCenter)]) @@ -1128,18 +1150,18 @@ class Workplane(CQ): #relative move in current plane, not drawing def move(self, xDist=0, yDist=0): """ - Move the specified distance from the current point, without drawing. + Move the specified distance from the current point, without drawing. - :param xDist: desired x distance, in local coordinates - :type xDist: float, or none for zero - :param yDist: desired y distance, in local coordinates - :type yDist: float, or none for zero. + :param xDist: desired x distance, in local coordinates + :type xDist: float, or none for zero + :param yDist: desired y distance, in local coordinates + :type yDist: float, or none for zero. - Not to be confused with :py:meth:`center`, which moves the center of the entire - workplane, this method only moves the current point ( and therefore does not affect objects - already drawn ). + Not to be confused with :py:meth:`center`, which moves the center of the entire + workplane, this method only moves the current point ( and therefore does not affect objects + already drawn ). - See :py:meth:`moveTo` to do the same thing but using absolute coordinates + See :py:meth:`moveTo` to do the same thing but using absolute coordinates """ p = self._findFromPoint(True) newCenter = p + Vector(xDist, yDist, 0) @@ -1147,36 +1169,35 @@ class Workplane(CQ): def spline(self, listOfXYTuple, forConstruction=False): """ - Create a spline interpolated through the provided points. + Create a spline interpolated through the provided points. - :param listOfXYTuple: points to interpolate through - :type listOfXYTuple: list of 2-tuple - :return: a Workplane object with the current point at the end of the spline + :param listOfXYTuple: points to interpolate through + :type listOfXYTuple: list of 2-tuple + :return: a Workplane object with the current point at the end of the spline - The spline will begin at the current point, and - end with the last point in the XY tuple list + The spline will begin at the current point, and + end with the last point in the XY tuple list - This example creates a block with a spline for one side:: + This example creates a block with a spline for one side:: - s = Workplane(Plane.XY()) - sPnts = [ - (2.75,1.5), - (2.5,1.75), - (2.0,1.5), - (1.5,1.0), - (1.0,1.25), - (0.5,1.0), - (0,1.0) - ] - r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close() - r = r.extrude(0.5) + s = Workplane(Plane.XY()) + sPnts = [ + (2.75,1.5), + (2.5,1.75), + (2.0,1.5), + (1.5,1.0), + (1.0,1.25), + (0.5,1.0), + (0,1.0) + ] + r = s.lineTo(3.0,0).lineTo(3.0,1.0).spline(sPnts).close() + r = r.extrude(0.5) - *WARNING* It is fairly easy to create a list of points - that cannot be correctly interpreted as a spline. - - Future Enhancements: - * provide access to control points + *WARNING* It is fairly easy to create a list of points + that cannot be correctly interpreted as a spline. + Future Enhancements: + * provide access to control points """ gstartPoint = self._findFromPoint(False) gEndPoint = self.plane.toWorldCoords(listOfXYTuple[-1]) @@ -1193,19 +1214,18 @@ class Workplane(CQ): def threePointArc(self, point1, point2, forConstruction=False): """ - Draw an arc from the current point, through point1, and ending at point2 + Draw an arc from the current point, through point1, and ending at point2 - :param point1: point to draw through - :type point1: 2-tuple, in workplane coordinates - :param point2: end point for the arc - :type point2: 2-tuple, in workplane coordinates - :return: a workplane with the current point at the end of the arc - - Future Enhancements: - provide a version that allows an arc using relative measures - provide a centerpoint arc - provide tangent arcs + :param point1: point to draw through + :type point1: 2-tuple, in workplane coordinates + :param point2: end point for the arc + :type point2: 2-tuple, in workplane coordinates + :return: a workplane with the current point at the end of the arc + Future Enhancements: + provide a version that allows an arc using relative measures + provide a centerpoint arc + provide tangent arcs """ gstartPoint = self._findFromPoint(False) @@ -1221,21 +1241,21 @@ class Workplane(CQ): def rotateAndCopy(self, matrix): """ - Makes a copy of all edges on the stack, rotates them according to the - provided matrix, and then attempts to consolidate them into a single wire. + Makes a copy of all edges on the stack, rotates them according to the + provided matrix, and then attempts to consolidate them into a single wire. - :param matrix: a 4xr transformation matrix, in global coordinates - :type matrix: a FreeCAD Base.Matrix object - :return: a CadQuery object with consolidated wires, and any originals on the stack. + :param matrix: a 4xr transformation matrix, in global coordinates + :type matrix: a FreeCAD Base.Matrix object + :return: a CadQuery object with consolidated wires, and any originals on the stack. - The most common use case is to create a set of open edges, and then mirror them - around either the X or Y axis to complete a closed shape. + The most common use case is to create a set of open edges, and then mirror them + around either the X or Y axis to complete a closed shape. - see :py:meth:`mirrorX` and :py:meth:`mirrorY` to mirror about the global X and Y axes - see :py:meth:`mirrorX` and for an example + see :py:meth:`mirrorX` and :py:meth:`mirrorY` to mirror about the global X and Y axes + see :py:meth:`mirrorX` and for an example - Future Enhancements: - faster implementation: this one transforms 3 times to accomplish the result + Future Enhancements: + faster implementation: this one transforms 3 times to accomplish the result """ #convert edges to a wire, if there are pending edges @@ -1257,21 +1277,21 @@ class Workplane(CQ): def mirrorY(self): """ - Mirror entities around the y axis of the workplane plane. + Mirror entities around the y axis of the workplane plane. - :return: a new object with any free edges consolidated into as few wires as possible. + :return: a new object with any free edges consolidated into as few wires as possible. - All free edges are collected into a wire, and then the wire is mirrored, - and finally joined into a new wire + All free edges are collected into a wire, and then the wire is mirrored, + and finally joined into a new wire - Typically used to make creating wires with symmetry easier. This line of code:: + Typically used to make creating wires with symmetry easier. This line of code:: - s = Workplane().lineTo(2,2).threePointArc((3,1),(2,0)).mirrorX().extrude(0.25) + s = Workplane().lineTo(2,2).threePointArc((3,1),(2,0)).mirrorX().extrude(0.25) - Produces a flat, heart shaped object + Produces a flat, heart shaped object - Future Enhancements: - mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness + Future Enhancements: + mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness """ tm = Matrix() tm.rotateY(math.pi) @@ -1279,17 +1299,17 @@ class Workplane(CQ): def mirrorX(self): """ - Mirror entities around the x axis of the workplane plane. + Mirror entities around the x axis of the workplane plane. - :return: a new object with any free edges consolidated into as few wires as possible. + :return: a new object with any free edges consolidated into as few wires as possible. - All free edges are collected into a wire, and then the wire is mirrored, - and finally joined into a new wire + All free edges are collected into a wire, and then the wire is mirrored, + and finally joined into a new wire - Typically used to make creating wires with symmetry easier. + Typically used to make creating wires with symmetry easier. - Future Enhancements: - mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness + Future Enhancements: + mirrorX().mirrorY() should work but doesnt, due to some FreeCAD weirdness """ tm = Matrix() tm.rotateX(math.pi) @@ -1297,7 +1317,7 @@ class Workplane(CQ): def _addPendingEdge(self, edge): """ - Queues an edge for later combination into a wire. + Queues an edge for later combination into a wire. :param edge: :return: @@ -1309,30 +1329,30 @@ class Workplane(CQ): def _addPendingWire(self, wire): """ - Queue a Wire for later extrusion + Queue a Wire for later extrusion - Internal Processing Note. In FreeCAD, edges-->wires-->faces-->solids. + Internal Processing Note. In FreeCAD, edges-->wires-->faces-->solids. - but users do not normally care about these distinctions. Users 'think' in terms - of edges, and solids. + but users do not normally care about these distinctions. Users 'think' in terms + of edges, and solids. - CadQuery tracks edges as they are drawn, and automatically combines them into wires - when the user does an operation that needs it. + CadQuery tracks edges as they are drawn, and automatically combines them into wires + when the user does an operation that needs it. - Similarly, cadQuery tracks pending wires, and automatically combines them into faces - when necessary to make a solid. + Similarly, cadQuery tracks pending wires, and automatically combines them into faces + when necessary to make a solid. """ self.ctx.pendingWires.append(wire) def consolidateWires(self): """ - Attempt to consolidate wires on the stack into a single. - If possible, a new object with the results are returned. - if not possible, the wires remain separated + Attempt to consolidate wires on the stack into a single. + If possible, a new object with the results are returned. + if not possible, the wires remain separated - FreeCAD has a bug in Part.Wire([]) which does not create wires/edges properly sometimes - Additionally, it has a bug where a profile composed of two wires ( rather than one ) - also does not work properly. Together these are a real problem. + FreeCAD has a bug in Part.Wire([]) which does not create wires/edges properly sometimes + Additionally, it has a bug where a profile composed of two wires ( rather than one ) + also does not work properly. Together these are a real problem. """ wires = self.wires().vals() if len(wires) < 2: @@ -1354,21 +1374,23 @@ class Workplane(CQ): def wire(self, forConstruction=False): """ - Returns a CQ object with all pending edges connected into a wire. + Returns a CQ object with all pending edges connected into a wire. - All edges on the stack that can be combined will be combined into a single wire object, - and other objects will remain on the stack unmodified + All edges on the stack that can be combined will be combined into a single wire object, + and other objects will remain on the stack unmodified - :param forConstruction: whether the wire should be used to make a solid, or if it is just for reference - :type forConstruction: boolean. true if the object is only for reference + :param forConstruction: whether the wire should be used to make a solid, or if it is just + for reference + :type forConstruction: boolean. true if the object is only for reference - This method is primarily of use to plugin developers making utilities for 2-d construction. This method - should be called when a user operation implies that 2-d construction is finished, and we are ready to - begin working in 3d + This method is primarily of use to plugin developers making utilities for 2-d construction. + This method should be called when a user operation implies that 2-d construction is + finished, and we are ready to begin working in 3d - SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles edges, wires, etc + SEE '2-d construction concepts' for a more detailed explanation of how CadQuery handles + edges, wires, etc - Any non edges will still remain. + Any non edges will still remain. """ edges = self.ctx.pendingEdges @@ -1393,35 +1415,35 @@ class Workplane(CQ): def each(self, callBackFunction, useLocalCoordinates=False): """ - runs the provided function on each value in the stack, and collects the return values into a new CQ - object. + Runs the provided function on each value in the stack, and collects the return values into + a new CQ object. - Special note: a newly created workplane always has its center point as its only stack item + Special note: a newly created workplane always has its center point as its only stack item - :param callBackFunction: the function to call for each item on the current stack. - :param useLocalCoordinates: should values be converted from local coordinates first? - :type useLocalCoordinates: boolean + :param callBackFunction: the function to call for each item on the current stack. + :param useLocalCoordinates: should values be converted from local coordinates first? + :type useLocalCoordinates: boolean - The callback function must accept one argument, which is the item on the stack, and return - one object, which is collected. If the function returns None, nothing is added to the stack. - The object passed into the callBackFunction is potentially transformed to local coordinates, if - useLocalCoordinates is true + The callback function must accept one argument, which is the item on the stack, and return + one object, which is collected. If the function returns None, nothing is added to the stack. + The object passed into the callBackFunction is potentially transformed to local coordinates, + if useLocalCoordinates is true - useLocalCoordinates is very useful for plugin developers. + useLocalCoordinates is very useful for plugin developers. - If false, the callback function is assumed to be working in global coordinates. Objects created are added - as-is, and objects passed into the function are sent in using global coordinates + If false, the callback function is assumed to be working in global coordinates. Objects + created are added as-is, and objects passed into the function are sent in using global + coordinates - If true, the calling function is assumed to be working in local coordinates. Objects are transformed - to local coordinates before they are passed into the callback method, and result objects are transformed - to global coordinates after they are returned. + If true, the calling function is assumed to be working in local coordinates. Objects are + transformed to local coordinates before they are passed into the callback method, and result + objects are transformed to global coordinates after they are returned. - This allows plugin developers to create objects in local coordinates, without worrying - about the fact that the working plane is different than the global coordinate system. + This allows plugin developers to create objects in local coordinates, without worrying + about the fact that the working plane is different than the global coordinate system. - TODO: wrapper object for Wire will clean up forConstruction flag everywhere - + TODO: wrapper object for Wire will clean up forConstruction flag everywhere """ results = [] for obj in self.objects: @@ -1443,21 +1465,20 @@ class Workplane(CQ): def eachpoint(self, callbackFunction, useLocalCoordinates=False): """ - Same as each(), except each item on the stack is converted into a point before it - is passed into the callback function. + Same as each(), except each item on the stack is converted into a point before it + is passed into the callback function. - :return: CadQuery object which contains a list of vectors (points ) on its stack. + :return: CadQuery object which contains a list of vectors (points ) on its stack. - :param useLocalCoordinates: should points be in local or global coordinates - :type useLocalCoordinates: boolean + :param useLocalCoordinates: should points be in local or global coordinates + :type useLocalCoordinates: boolean - The resulting object has a point on the stack for each object on the original stack. - Vertices and points remain a point. Faces, Wires, Solids, Edges, and Shells are converted - to a point by using their center of mass. - - If the stack has zero length, a single point is returned, which is the center of the current - workplane/coordinate system + The resulting object has a point on the stack for each object on the original stack. + Vertices and points remain a point. Faces, Wires, Solids, Edges, and Shells are converted + to a point by using their center of mass. + If the stack has zero length, a single point is returned, which is the center of the current + workplane/coordinate system """ #convert stack to a list of points pnts = [] @@ -1474,27 +1495,29 @@ class Workplane(CQ): def rect(self, xLen, yLen, centered=True, forConstruction=False): """ - Make a rectangle for each item on the stack. + Make a rectangle for each item on the stack. - :param xLen: length in xDirection ( in workplane coordinates ) - :type xLen: float > 0 - :param yLen: length in yDirection ( in workplane coordinates ) - :type yLen: float > 0 - :param boolean centered: true if the rect is centered on the reference point, false if the lower-left is on the reference point - :param forConstruction: should the new wires be reference geometry only? - :type forConstruction: true if the wires are for reference, false if they are creating part geometry - :return: a new CQ object with the created wires on the stack + :param xLen: length in xDirection ( in workplane coordinates ) + :type xLen: float > 0 + :param yLen: length in yDirection ( in workplane coordinates ) + :type yLen: float > 0 + :param boolean centered: true if the rect is centered on the reference point, false if the + lower-left is on the reference point + :param forConstruction: should the new wires be reference geometry only? + :type forConstruction: true if the wires are for reference, false if they are creating part + geometry + :return: a new CQ object with the created wires on the stack - A common use case is to use a for-construction rectangle to define the centers of a hole pattern:: + A common use case is to use a for-construction rectangle to define the centers of a hole + pattern:: - s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25) + s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25) - Creates 4 circles at the corners of a square centered on the origin. - - Future Enhancements: - better way to handle forConstruction - project points not in the workplane plane onto the workplane plane + Creates 4 circles at the corners of a square centered on the origin. + Future Enhancements: + better way to handle forConstruction + project points not in the workplane plane onto the workplane plane """ def makeRectangleWire(pnt): # Here pnt is in local coordinates due to useLocalCoords=True @@ -1519,29 +1542,31 @@ class Workplane(CQ): #circle from current point def circle(self, radius, forConstruction=False): """ - Make a circle for each item on the stack. + Make a circle for each item on the stack. - :param radius: radius of the circle - :type radius: float > 0 - :param forConstruction: should the new wires be reference geometry only? - :type forConstruction: true if the wires are for reference, false if they are creating part geometry - :return: a new CQ object with the created wires on the stack + :param radius: radius of the circle + :type radius: float > 0 + :param forConstruction: should the new wires be reference geometry only? + :type forConstruction: true if the wires are for reference, false if they are creating + part geometry + :return: a new CQ object with the created wires on the stack - A common use case is to use a for-construction rectangle to define the centers of a hole pattern:: + A common use case is to use a for-construction rectangle to define the centers of a + hole pattern:: - s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25) + s = Workplane().rect(4.0,4.0,forConstruction=True).vertices().circle(0.25) - Creates 4 circles at the corners of a square centered on the origin. Another common case is to use - successive circle() calls to create concentric circles. This works because the center of a circle - is its reference point:: + Creates 4 circles at the corners of a square centered on the origin. Another common case is + to use successive circle() calls to create concentric circles. This works because the + center of a circle is its reference point:: - s = Workplane().circle(2.0).circle(1.0) + s = Workplane().circle(2.0).circle(1.0) - Creates two concentric circles, which when extruded will form a ring. + Creates two concentric circles, which when extruded will form a ring. - Future Enhancements: - better way to handle forConstruction - project points not in the workplane plane onto the workplane plane + Future Enhancements: + better way to handle forConstruction + project points not in the workplane plane onto the workplane plane """ def makeCircleWire(obj): @@ -1553,15 +1578,14 @@ class Workplane(CQ): def polygon(self, nSides, diameter): """ - Creates a polygon inscribed in a circle of the specified diameter for each point on the stack + Creates a polygon inscribed in a circle of the specified diameter for each point on + the stack The first vertex is always oriented in the x direction. :param nSides: number of sides, must be > 3 :param diameter: the size of the circle the polygon is inscribed into :return: a polygon wire - - """ def _makePolygon(center): #pnt is a vector in local coordinates @@ -1576,19 +1600,20 @@ class Workplane(CQ): def polyline(self, listOfXYTuple, forConstruction=False): """ - Create a polyline from a list of points + Create a polyline from a list of points - :param listOfXYTuple: a list of points in Workplane coordinates - :type listOfXYTuple: list of 2-tuples - :param forConstruction: should the new wire be reference geometry only? - :type forConstruction: true if the wire is for reference, false if they are creating part geometry - :return: a new CQ object with the new wire on the stack + :param listOfXYTuple: a list of points in Workplane coordinates + :type listOfXYTuple: list of 2-tuples + :param forConstruction: should the new wire be reference geometry only? + :type forConstruction: true if the wire is for reference, false if they are creating + part geometry + :return: a new CQ object with the new wire on the stack - *NOTE* most commonly, the resulting wire should be closed. + *NOTE* most commonly, the resulting wire should be closed. - Future Enhancement: - This should probably yield a list of edges, not a wire, so that - it is possible to combine a polyline with other edges and arcs + Future Enhancement: + This should probably yield a list of edges, not a wire, so that + it is possible to combine a polyline with other edges and arcs """ vecs = [self.plane.toWorldCoords(p) for p in listOfXYTuple] w = Wire.makePolygon(vecs) @@ -1599,18 +1624,17 @@ class Workplane(CQ): def close(self): """ - End 2-d construction, and attempt to build a closed wire. + End 2-d construction, and attempt to build a closed wire. - :return: a CQ object with a completed wire on the stack, if possible. + :return: a CQ object with a completed wire on the stack, if possible. - After 2-d drafting with lineTo,threePointArc, and polyline, it is necessary - to convert the edges produced by these into one or more wires. + After 2-d drafting with lineTo,threePointArc, and polyline, it is necessary + to convert the edges produced by these into one or more wires. - When a set of edges is closed, cadQuery assumes it is safe to build the group of edges - into a wire. This example builds a simple triangular prism:: - - s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2) + When a set of edges is closed, cadQuery assumes it is safe to build the group of edges + into a wire. This example builds a simple triangular prism:: + s = Workplane().lineTo(1,0).lineTo(1,1).close().extrude(0.2) """ self.lineTo(self.ctx.firstPoint.x, self.ctx.firstPoint.y) return self.wire() @@ -1620,10 +1644,10 @@ class Workplane(CQ): Finds the largest dimension in the stack. Used internally to create thru features, this is how you can compute how long or wide a feature must be to make sure to cut through all of the material - :return: + :return: A value representing the largest dimension of the first solid on the stack """ #TODO: this implementation is naive and returns the dims of the first solid... most of - #the time this works. but a stronger implementation would be to search all solids. + #TODO: the time this works. but a stronger implementation would be to search all solids. s = self.findSolid() if s: return s.BoundingBox().DiagonalLength * 5.0 @@ -1634,7 +1658,8 @@ class Workplane(CQ): """ Evaluates the provided function at each point on the stack ( ie, eachpoint ) and then cuts the result from the context solid. - :param function: a function suitable for use in the eachpoint method: ie, that accepts a vector + :param fcn: a function suitable for use in the eachpoint method: ie, that accepts + a vector :param useLocalCoords: same as for :py:meth:`eachpoint` :return: a CQ object that contains the resulting solid :raises: an error if there is not a context solid to cut from @@ -1655,40 +1680,41 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def cboreHole(self, diameter, cboreDiameter, cboreDepth, depth=None): """ - Makes a counterbored hole for each item on the stack. + Makes a counterbored hole for each item on the stack. - :param diameter: the diameter of the hole - :type diameter: float > 0 - :param cboreDiameter: the diameter of the cbore - :type cboreDiameter: float > 0 and > diameter - :param cboreDepth: depth of the counterbore - :type cboreDepth: float > 0 - :param depth: the depth of the hole - :type depth: float > 0 or None to drill thru the entire part. + :param diameter: the diameter of the hole + :type diameter: float > 0 + :param cboreDiameter: the diameter of the cbore + :type cboreDiameter: float > 0 and > diameter + :param cboreDepth: depth of the counterbore + :type cboreDepth: float > 0 + :param depth: the depth of the hole + :type depth: float > 0 or None to drill thru the entire part. - The surface of the hole is at the current workplane plane. + The surface of the hole is at the current workplane plane. - One hole is created for each item on the stack. A very common use case is to use a - construction rectangle to define the centers of a set of holes, like so:: + One hole is created for each item on the stack. A very common use case is to use a + construction rectangle to define the centers of a set of holes, like so:: - s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane().rect(1.5,3.5,forConstruction=True)\ - .vertices().cboreHole(0.125, 0.25,0.125,depth=None) + s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\ + .rect(1.5,3.5,forConstruction=True)\ + .vertices().cboreHole(0.125, 0.25,0.125,depth=None) - This sample creates a plate with a set of holes at the corners. + This sample creates a plate with a set of holes at the corners. - **Plugin Note**: this is one example of the power of plugins. Counterbored holes are quite time consuming - to create, but are quite easily defined by users. + **Plugin Note**: this is one example of the power of plugins. Counterbored holes are quite + time consuming to create, but are quite easily defined by users. - see :py:meth:`cskHole` to make countersinks instead of counterbores + see :py:meth:`cskHole` to make countersinks instead of counterbores """ if depth is None: depth = self.largestDimension() def _makeCbore(center): """ - Makes a single hole with counterbore at the supplied point - returns a solid suitable for subtraction - pnt is in local coordinates + Makes a single hole with counterbore at the supplied point + returns a solid suitable for subtraction + pnt is in local coordinates """ boreDir = Vector(0, 0, -1) #first make the hole @@ -1705,31 +1731,32 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def cskHole(self, diameter, cskDiameter, cskAngle, depth=None): """ - Makes a countersunk hole for each item on the stack. + Makes a countersunk hole for each item on the stack. - :param diameter: the diameter of the hole - :type diameter: float > 0 - :param cskDiameter: the diameter of the countersink - :type cskDiameter: float > 0 and > diameter - :param cskAngle: angle of the countersink, in degrees ( 82 is common ) - :type cskAngle: float > 0 - :param depth: the depth of the hole - :type depth: float > 0 or None to drill thru the entire part. + :param diameter: the diameter of the hole + :type diameter: float > 0 + :param cskDiameter: the diameter of the countersink + :type cskDiameter: float > 0 and > diameter + :param cskAngle: angle of the countersink, in degrees ( 82 is common ) + :type cskAngle: float > 0 + :param depth: the depth of the hole + :type depth: float > 0 or None to drill thru the entire part. - The surface of the hole is at the current workplane. + The surface of the hole is at the current workplane. - One hole is created for each item on the stack. A very common use case is to use a - construction rectangle to define the centers of a set of holes, like so:: + One hole is created for each item on the stack. A very common use case is to use a + construction rectangle to define the centers of a set of holes, like so:: - s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane().rect(1.5,3.5,forConstruction=True)\ - .vertices().cskHole(0.125, 0.25,82,depth=None) + s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\ + .rect(1.5,3.5,forConstruction=True)\ + .vertices().cskHole(0.125, 0.25,82,depth=None) - This sample creates a plate with a set of holes at the corners. + This sample creates a plate with a set of holes at the corners. - **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite time consuming - to create, but are quite easily defined by users. + **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite + time consuming to create, but are quite easily defined by users. - see :py:meth:`cboreHole` to make counterbores instead of countersinks + see :py:meth:`cboreHole` to make counterbores instead of countersinks """ if depth is None: @@ -1754,40 +1781,41 @@ class Workplane(CQ): #but parameter list is different so a simple function pointer wont work def hole(self, diameter, depth=None): """ - Makes a hole for each item on the stack. + Makes a hole for each item on the stack. - :param diameter: the diameter of the hole - :type diameter: float > 0 - :param depth: the depth of the hole - :type depth: float > 0 or None to drill thru the entire part. + :param diameter: the diameter of the hole + :type diameter: float > 0 + :param depth: the depth of the hole + :type depth: float > 0 or None to drill thru the entire part. - The surface of the hole is at the current workplane. + The surface of the hole is at the current workplane. - One hole is created for each item on the stack. A very common use case is to use a - construction rectangle to define the centers of a set of holes, like so:: + One hole is created for each item on the stack. A very common use case is to use a + construction rectangle to define the centers of a set of holes, like so:: - s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane().rect(1.5,3.5,forConstruction=True)\ - .vertices().hole(0.125, 0.25,82,depth=None) + s = Workplane(Plane.XY()).box(2,4,0.5).faces(">Z").workplane()\ + .rect(1.5,3.5,forConstruction=True)\ + .vertices().hole(0.125, 0.25,82,depth=None) - This sample creates a plate with a set of holes at the corners. + This sample creates a plate with a set of holes at the corners. - **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite time consuming - to create, but are quite easily defined by users. + **Plugin Note**: this is one example of the power of plugins. CounterSunk holes are quite + time consuming to create, but are quite easily defined by users. - see :py:meth:`cboreHole` and :py:meth:`cskHole` to make counterbores or countersinks + see :py:meth:`cboreHole` and :py:meth:`cskHole` to make counterbores or countersinks """ if depth is None: depth = self.largestDimension() def _makeHole(center): """ - Makes a single hole with counterbore at the supplied point - returns a solid suitable for subtraction - pnt is in local coordinates + Makes a single hole with counterbore at the supplied point + returns a solid suitable for subtraction + pnt is in local coordinates """ boreDir = Vector(0, 0, -1) #first make the hole - hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordianates! + hole = Solid.makeCylinder(diameter / 2.0, depth, center, boreDir) # local coordinates! return hole return self.cutEach(_makeHole, True) @@ -1795,40 +1823,42 @@ class Workplane(CQ): #TODO: duplicated code with _extrude and extrude def twistExtrude(self, distance, angleDegrees, combine=True): """ - Extrudes a wire in the direction normal to the plane, but also twists by the specified angle over the - length of the extrusion + Extrudes a wire in the direction normal to the plane, but also twists by the specified + angle over the length of the extrusion - The center point of the rotation will be the center of the workplane + The center point of the rotation will be the center of the workplane - See extrude for more details, since this method is the same except for the the addition of the angle. - in fact, if angle=0, the result is the same as a linear extrude. + See extrude for more details, since this method is the same except for the the addition + of the angle. In fact, if angle=0, the result is the same as a linear extrude. - **NOTE** This method can create complex calculations, so be careful using it with complex geometries - - :param distance: the distance to extrude normal to the workplane - :param angle: angline ( in degrees) to rotate through the extrusion - :param boolean combine: True to combine the resulting solid with parent solids if found. - :return: a CQ object with the resulting solid selected. + **NOTE** This method can create complex calculations, so be careful using it with + complex geometries + :param distance: the distance to extrude normal to the workplane + :param angle: angline ( in degrees) to rotate through the extrusion + :param boolean combine: True to combine the resulting solid with parent solids if found. + :return: a CQ object with the resulting solid selected. """ #group wires together into faces based on which ones are inside the others #result is a list of lists wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) - self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion + self.ctx.pendingWires = [] # now all of the wires have been used to create an extrusion #compute extrusion vector and extrude eDir = self.plane.zDir.multiply(distance) #one would think that fusing faces into a compound and then extruding would work, - #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc), + #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc) #but then cutting it from the main solid fails with BRep_NotDone. #the work around is to extrude each and then join the resulting solids, which seems to work - #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are multiple sets + #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there + # are multiple sets r = None for ws in wireSets: - thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin, eDir, angleDegrees) + thisObj = Solid.extrudeLinearWithRotation(ws[0], ws[1:], self.plane.origin, + eDir, angleDegrees) if r is None: r = thisObj else: @@ -1841,25 +1871,26 @@ class Workplane(CQ): def extrude(self, distance, combine=True): """ - Use all un-extruded wires in the parent chain to create a prismatic solid. + Use all un-extruded wires in the parent chain to create a prismatic solid. - :param distance: the distance to extrude, normal to the workplane plane - :type distance: float, negative means opposite the normal direction - :param boolean combine: True to combine the resulting solid with parent solids if found. - :return: a CQ object with the resulting solid selected. + :param distance: the distance to extrude, normal to the workplane plane + :type distance: float, negative means opposite the normal direction + :param boolean combine: True to combine the resulting solid with parent solids if found. + :return: a CQ object with the resulting solid selected. - extrude always *adds* material to a part. + extrude always *adds* material to a part. - The returned object is always a CQ object, and depends on wither combine is True, and - whether a context solid is already defined: + The returned object is always a CQ object, and depends on wither combine is True, and + whether a context solid is already defined: - * if combine is False, the new value is pushed onto the stack. - * if combine is true, the value is combined with the context solid if it exists, - and the resulting solid becomes the new context solid. + * if combine is False, the new value is pushed onto the stack. + * if combine is true, the value is combined with the context solid if it exists, + and the resulting solid becomes the new context solid. - FutureEnhancement: - Support for non-prismatic extrusion ( IE, sweeping along a profile, not just perpendicular to the plane - extrude to surface. this is quite tricky since the surface selected may not be planar + FutureEnhancement: + Support for non-prismatic extrusion ( IE, sweeping along a profile, not just + perpendicular to the plane extrude to surface. this is quite tricky since the surface + selected may not be planar """ r = self._extrude(distance) # returns a Solid (or a compound if there were multiple) if combine: @@ -1869,24 +1900,24 @@ class Workplane(CQ): def revolve(self, angleDegrees=360.0, axisStart=None, axisEnd=None, combine=True): """ - Use all un-revolved wires in the parent chain to create a solid. + Use all un-revolved wires in the parent chain to create a solid. - :param angleDegrees: the angle to revolve through. - :type angleDegrees: float, anything less than 360 degrees will leave the shape open - :param axisStart: the start point of the axis of rotation - :type axisStart: tuple, a two tuple - :param axisEnd: the end point of the axis of rotation - :type axisEnd: tuple, a two tuple - :param combine: True to combine the resulting solid with parent solids if found. - :type combine: boolean, combine with parent solid - :return: a CQ object with the resulting solid selected. + :param angleDegrees: the angle to revolve through. + :type angleDegrees: float, anything less than 360 degrees will leave the shape open + :param axisStart: the start point of the axis of rotation + :type axisStart: tuple, a two tuple + :param axisEnd: the end point of the axis of rotation + :type axisEnd: tuple, a two tuple + :param combine: True to combine the resulting solid with parent solids if found. + :type combine: boolean, combine with parent solid + :return: a CQ object with the resulting solid selected. - The returned object is always a CQ object, and depends on wither combine is True, and - whether a context solid is already defined: + The returned object is always a CQ object, and depends on wither combine is True, and + whether a context solid is already defined: - * if combine is False, the new value is pushed onto the stack. - * if combine is true, the value is combined with the context solid if it exists, - and the resulting solid becomes the new context solid. + * if combine is False, the new value is pushed onto the stack. + * if combine is true, the value is combined with the context solid if it exists, + and the resulting solid becomes the new context solid. """ #Make sure we account for users specifying angles larger than 360 degrees angleDegrees %= 360.0 @@ -1894,15 +1925,18 @@ class Workplane(CQ): #Compensate for FreeCAD not assuming that a 0 degree revolve means a 360 degree revolve angleDegrees = 360.0 if angleDegrees == 0 else angleDegrees - #The default start point of the vector defining the axis of rotation will be the origin of the workplane + # The default start point of the vector defining the axis of rotation will be the origin + # of the workplane if axisStart is None: axisStart = self.plane.toWorldCoords((0, 0)).toTuple() else: axisStart = self.plane.toWorldCoords(axisStart).toTuple() - #The default end point of the vector defining the axis of rotation should be along the normal from the plane + # The default end point of the vector defining the axis of rotation should be along the + # normal from the plane if axisEnd is None: - #Make sure we match the user's assumed axis of rotation if they specified an start but not an end + # Make sure we match the user's assumed axis of rotation if they specified an start + # but not an end if axisStart[1] != 0: axisEnd = self.plane.toWorldCoords((0, axisStart[1])).toTuple() else: @@ -1910,7 +1944,8 @@ class Workplane(CQ): else: axisEnd = self.plane.toWorldCoords(axisEnd).toTuple() - r = self._revolve(angleDegrees, axisStart, axisEnd) # returns a Solid ( or a compound if there were multiple ) + # returns a Solid (or a compound if there were multiple) + r = self._revolve(angleDegrees, axisStart, axisEnd) if combine: return self._combineWithBase(r) else: @@ -1918,11 +1953,10 @@ class Workplane(CQ): def _combineWithBase(self, obj): """ - Combines the provided object with the base solid, if one can be found. - :param obj: - :return: a new object that represents the result of combining the base object with obj, - or obj if one could not be found - + Combines the provided object with the base solid, if one can be found. + :param obj: + :return: a new object that represents the result of combining the base object with obj, + or obj if one could not be found """ baseSolid = self.findSolid(searchParents=True) r = obj @@ -1934,11 +1968,11 @@ class Workplane(CQ): def combine(self): """ - Attempts to combine all of the items on the items on the stack into a single item. - WARNING: all of the items must be of the same type! + Attempts to combine all of the items on the items on the stack into a single item. + WARNING: all of the items must be of the same type! - :raises: ValueError if there are no items on the stack, or if they cannot be combined - :return: a CQ object with the resulting object selected + :raises: ValueError if there are no items on the stack, or if they cannot be combined + :return: a CQ object with the resulting object selected """ items = list(self.objects) s = items.pop(0) @@ -1949,11 +1983,10 @@ class Workplane(CQ): def union(self, toUnion=None, combine=True): """ - Unions all of the items on the stack of toUnion with the current solid. - If there is no current solid, the items in toUnion are unioned together. - if combine=True, the result and the original are updated to point to the new object - if combine=False, the result will be on the stack, but the original is unmodified - + Unions all of the items on the stack of toUnion with the current solid. + If there is no current solid, the items in toUnion are unioned together. + if combine=True, the result and the original are updated to point to the new object + if combine=False, the result will be on the stack, but the original is unmodified :param toUnion: :type toUnion: a solid object, or a CQ object having a solid, @@ -1975,7 +2008,8 @@ class Workplane(CQ): raise ValueError("Cannot union Type '%s' " % str(type(toUnion))) #now combine with existing solid, if there is one - solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from + # look for parents to cut from + solidRef = self.findSolid(searchStack=True, searchParents=True) if combine and solidRef is not None: t = solidRef.fuse(newS) solidRef.wrapped = newS.wrapped @@ -1985,19 +2019,19 @@ class Workplane(CQ): def cut(self, toCut, combine=True): """ - Cuts the provided solid from the current solid, IE, perform a solid subtraction + Cuts the provided solid from the current solid, IE, perform a solid subtraction - if combine=True, the result and the original are updated to point to the new object - if combine=False, the result will be on the stack, but the original is unmodified - - :param toCut: object to cut - :type toCut: a solid object, or a CQ object having a solid, - :raises: ValueError if there is no solid to subtract from in the chain - :return: a CQ object with the resulting object selected + if combine=True, the result and the original are updated to point to the new object + if combine=False, the result will be on the stack, but the original is unmodified + :param toCut: object to cut + :type toCut: a solid object, or a CQ object having a solid, + :raises: ValueError if there is no solid to subtract from in the chain + :return: a CQ object with the resulting object selected """ - solidRef = self.findSolid(searchStack=True, searchParents=True) # look for parents to cut from + # look for parents to cut from + solidRef = self.findSolid(searchStack=True, searchParents=True) if solidRef is None: raise ValueError("Cannot find solid to cut from!!!") @@ -2016,20 +2050,21 @@ class Workplane(CQ): def cutBlind(self, distanceToCut): """ - Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. + Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. - Similar to extrude, except that a solid in the parent chain is required to remove material from. - cutBlind always removes material from a part. + Similar to extrude, except that a solid in the parent chain is required to remove material + from. cutBlind always removes material from a part. - :param distanceToCut: distance to extrude before cutting - :type distanceToCut: float, >0 means in the positive direction of the workplane normal, <0 means in the negative direction - :raises: ValueError if there is no solid to subtract from in the chain - :return: a CQ object with the resulting object selected + :param distanceToCut: distance to extrude before cutting + :type distanceToCut: float, >0 means in the positive direction of the workplane normal, + <0 means in the negative direction + :raises: ValueError if there is no solid to subtract from in the chain + :return: a CQ object with the resulting object selected - see :py:meth:`cutThruAll` to cut material from the entire part + see :py:meth:`cutThruAll` to cut material from the entire part - Future Enhancements: - Cut Up to Surface + Future Enhancements: + Cut Up to Surface """ #first, make the object toCut = self._extrude(distanceToCut) @@ -2044,17 +2079,17 @@ class Workplane(CQ): def cutThruAll(self, positive=False): """ - Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. + Use all un-extruded wires in the parent chain to create a prismatic cut from existing solid. - Similar to extrude, except that a solid in the parent chain is required to remove material from. - cutThruAll always removes material from a part. + Similar to extrude, except that a solid in the parent chain is required to remove material + from. cutThruAll always removes material from a part. - :param boolean positive: True to cut in the positive direction, false to cut in the negative direction - :raises: ValueError if there is no solid to subtract from in the chain - :return: a CQ object with the resulting object selected - - see :py:meth:`cutBlind` to cut material to a limited depth + :param boolean positive: True to cut in the positive direction, false to cut in the + negative direction + :raises: ValueError if there is no solid to subtract from in the chain + :return: a CQ object with the resulting object selected + see :py:meth:`cutBlind` to cut material to a limited depth """ maxDim = self.largestDimension() if not positive: @@ -2064,8 +2099,8 @@ class Workplane(CQ): def loft(self, filled=True, ruled=False, combine=True): """ - Make a lofted solid, through the set of wires. - :return: + Make a lofted solid, through the set of wires. + :return: a CQ object containing the created loft """ wiresToLoft = self.ctx.pendingWires self.ctx.pendingWires = [] @@ -2082,16 +2117,16 @@ class Workplane(CQ): def _extrude(self, distance): """ - Make a prismatic solid from the existing set of pending wires. + Make a prismatic solid from the existing set of pending wires. - :param distance: distance to extrude - :return: a FreeCAD solid, suitable for boolean operations. + :param distance: distance to extrude + :return: a FreeCAD solid, suitable for boolean operations. - This method is a utility method, primarily for plugin and internal use. - It is the basis for cutBlind,extrude,cutThruAll, and all similar methods. + This method is a utility method, primarily for plugin and internal use. + It is the basis for cutBlind,extrude,cutThruAll, and all similar methods. - Future Enhancements: - extrude along a profile ( sweep ) + Future Enhancements: + extrude along a profile (sweep) """ #group wires together into faces based on which ones are inside the others @@ -2106,27 +2141,26 @@ class Workplane(CQ): #one would think that fusing faces into a compound and then extruding would work, - #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc), + #but it doesnt-- the resulting compound appears to look right, ( right number of faces, etc) #but then cutting it from the main solid fails with BRep_NotDone. #the work around is to extrude each and then join the resulting solids, which seems to work - #underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are multiple sets + # underlying cad kernel can only handle simple bosses-- we'll aggregate them if there are + # multiple sets - # IMPORTANT NOTE: OCC is slow slow slow in boolean operations. So you do NOT want to fuse each item to - # another and save the result-- instead, you want to combine all of the new items into a compound, and fuse - # them together!!! - """ - r = None - for ws in wireSets: - thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir) - if r is None: - r = thisObj - else: - s = time.time() - r = r.fuse(thisObj) - print "Fused in %0.3f sec" % ( time.time() - s ) - return r - """ + # IMPORTANT NOTE: OCC is slow slow slow in boolean operations. So you do NOT want to fuse + # each item to another and save the result-- instead, you want to combine all of the new + # items into a compound, and fuse them together!!! + # r = None + # for ws in wireSets: + # thisObj = Solid.extrudeLinear(ws[0], ws[1:], eDir) + # if r is None: + # r = thisObj + # else: + # s = time.time() + # r = r.fuse(thisObj) + # print "Fused in %0.3f sec" % ( time.time() - s ) + # return r toFuse = [] for ws in wireSets: @@ -2137,17 +2171,17 @@ class Workplane(CQ): def _revolve(self, angleDegrees, axisStart, axisEnd): """ - Make a solid from the existing set of pending wires. + Make a solid from the existing set of pending wires. - :param angleDegrees: the angle to revolve through. - :type angleDegrees: float, anything less than 360 degrees will leave the shape open - :param axisStart: the start point of the axis of rotation - :type axisStart: tuple, a two tuple - :param axisEnd: the end point of the axis of rotation - :type axisEnd: tuple, a two tuple - :return: a FreeCAD solid, suitable for boolean operations. + :param angleDegrees: the angle to revolve through. + :type angleDegrees: float, anything less than 360 degrees will leave the shape open + :param axisStart: the start point of the axis of rotation + :type axisStart: tuple, a two tuple + :param axisEnd: the end point of the axis of rotation + :type axisEnd: tuple, a two tuple + :return: a FreeCAD solid, suitable for boolean operations. - This method is a utility method, primarily for plugin and internal use. + This method is a utility method, primarily for plugin and internal use. """ #We have to gather the wires to be revolved wireSets = sortWiresByBuildOrder(list(self.ctx.pendingWires), self.plane, []) @@ -2173,20 +2207,22 @@ class Workplane(CQ): :type width: float > 0 :param height: box size in Z direction :type height: float > 0 - :param centered: should the box be centered, or should reference point be at the lower bound of the range? - :param combine: should the results be combined with other solids on the stack ( and each other)? + :param centered: should the box be centered, or should reference point be at the lower + bound of the range? + :param combine: should the results be combined with other solids on the stack + (and each other)? :type combine: true to combine shapes, false otherwise. - Centered is a tuple that describes whether the box should be centered on the x,y, and z axes. If true, - the box is centered on the respective axis relative to the workplane origin, if false, the workplane center - will represent the lower bound of the resulting box + Centered is a tuple that describes whether the box should be centered on the x,y, and + z axes. If true, the box is centered on the respective axis relative to the workplane + origin, if false, the workplane center will represent the lower bound of the resulting box - one box is created for each item on the current stack. If no items are on the stack, one box using - the current workplane center is created. + one box is created for each item on the current stack. If no items are on the stack, one box + using the current workplane center is created. If combine is true, the result will be a single object on the stack: - if a solid was found in the chain, the result is that solid with all boxes produced fused onto it - otherwise, the result is the combination of all the produced boxes + if a solid was found in the chain, the result is that solid with all boxes produced + fused onto it otherwise, the result is the combination of all the produced boxes if combine is false, the result will be a list of the boxes produced @@ -2225,7 +2261,8 @@ class Workplane(CQ): #combine everything return self.union(boxes) - def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, centered=(True, True, True), combine=True): + def sphere(self, radius, direct=(0, 0, 1), angle1=-90, angle2=90, angle3=360, + centered=(True, True, True), combine=True): """ Returns a 3D sphere with the specified radius for each point on the stack @@ -2239,21 +2276,24 @@ class Workplane(CQ): :type angle2: float > 0 :param angle3: The third angle to sweep the sphere arc through :type angle3: float > 0 - :param centered: A three-tuple of booleans that determines whether the sphere is centered on each axis origin - :param combine: Whether the results should be combined with other solids on the stack (and each other) + :param centered: A three-tuple of booleans that determines whether the sphere is centered + on each axis origin + :param combine: Whether the results should be combined with other solids on the stack + (and each other) :type combine: true to combine shapes, false otherwise :return: A sphere object for each point on the stack - Centered is a tuple that describes whether the sphere should be centered on the x,y, and z axes. If true, - the sphere is centered on the respective axis relative to the workplane origin, if false, the workplane center - will represent the lower bound of the resulting sphere + Centered is a tuple that describes whether the sphere should be centered on the x,y, and + z axes. If true, the sphere is centered on the respective axis relative to the workplane + origin, if false, the workplane center will represent the lower bound of the resulting + sphere. - One sphere is created for each item on the current stack. If no items are on the stack, one box using - the current workplane center is created. + One sphere is created for each item on the current stack. If no items are on the stack, one + box using the current workplane center is created. If combine is true, the result will be a single object on the stack: - If a solid was found in the chain, the result is that solid with all spheres produced fused onto it - otherwise, the result is the combination of all the produced boxes + If a solid was found in the chain, the result is that solid with all spheres produced + fused onto it otherwise, the result is the combination of all the produced boxes If combine is false, the result will be a list of the spheres produced """ diff --git a/tests/TestCadQuery.py b/tests/TestCadQuery.py index fb2645d..65c2195 100644 --- a/tests/TestCadQuery.py +++ b/tests/TestCadQuery.py @@ -70,8 +70,7 @@ class TestCadQuery(BaseTest): writeStringToFile(existingSummary,SUMMARY_FILE) - - def saveModel(self,shape): + def saveModel(self, shape): """ shape must be a CQ object Save models in SVG and STEP format @@ -79,6 +78,16 @@ class TestCadQuery(BaseTest): shape.exportSvg(os.path.join(OUTDIR,self._testMethodName + ".svg")) shape.val().exportStep(os.path.join(OUTDIR,self._testMethodName + ".step")) + def testToFreeCAD(self): + """ + Tests to make sure that a CadQuery object is converted correctly to a FreeCAD object. + """ + r = Workplane('XY').rect(5, 5).extrude(5) + + r = r.toFreecad() + + self.assertEqual(12, len(r.Edges)) + def testCubePlugin(self): """ Tests a plugin that combines cubes together with a base @@ -763,6 +772,21 @@ class TestCadQuery(BaseTest): self.assertEqual(8, result.solids().item(0).faces().size()) self.assertEqual(8, result.solids().item(1).faces().size()) + def testSplitKeepingBottom(self): + """ + Tests splitting a solid improperly + """ + # Drill a hole in the side + c = CQ(makeUnitCube()).faces(">Z").workplane().circle(0.25).cutThruAll() + self.assertEqual(7, c.faces().size()) + + # Now cut it in half sideways + result = c.faces(">Y").workplane(-0.5).split(keepTop=False, keepBottom=True) + + #stack will have both halves, original will be unchanged + self.assertEqual(1, result.solids().size()) # one solid is on the stack + self.assertEqual(8, result.solids().item(0).faces().size()) + def testBoxDefaults(self): """ Tests creating a single box @@ -786,6 +810,14 @@ class TestCadQuery(BaseTest): s1.add(s.faces("+Y")).add(s.faces("+X")) self.saveModel(s1.shell(0.2)) + # Tests the list option variation of add + s1 = s.faces("+Z") + s1.add(s.faces("+Y")).add([s.faces("+X")]) + + # Tests the raw object option variation of add + s1 = s.faces("+Z") + s1.add(s.faces("+Y")).add(s.faces("+X").val().wrapped) + def testTopFaceFillet(self): s = Workplane("XY").box(1, 1, 1).faces("+Z").edges().fillet(0.1) self.assertEquals(s.faces().size(), 10)