diff --git a/resources/generation/freecad/Pyscad/Init.py b/resources/generation/freecad/Pyscad/Init.py deleted file mode 100644 index 82c7349eb..000000000 --- a/resources/generation/freecad/Pyscad/Init.py +++ /dev/null @@ -1,3 +0,0 @@ -# FreeCAD init script of the pyscad module - -# print("Init from ", __name__) diff --git a/resources/generation/freecad/Pyscad/README.md b/resources/generation/freecad/Pyscad/README.md deleted file mode 100644 index a997e94f3..000000000 --- a/resources/generation/freecad/Pyscad/README.md +++ /dev/null @@ -1,136 +0,0 @@ - -# FreePySCAD -You like OpenSCAD but you hate it at the same time? -You can't work in FreeCAD because don't like wasting your time moving the mouse and clicking? -FreePySCAD is for you! - -note: it's in an alpha stage right now. You can use it but some things may not work as advertised. Tested against 0.17. -## How it work -FreePySCAD is a python library for FreeCAD to let user write their code in a text editor and see the result after a "compilation" process, like OpenSCAD but in FreeCAD. -To install the library, clone the github repository into the "FreeCAD X.xx/mod" directory -To write your code, you can open the FreeCAD macro editor and beginning your macro with "from FreePySCAD.FreePySCAD import *" -You can also type in the python console "execfile('path_to/my_pycad.py')", this has the advantage to show the errors. -The geometry passed inside the scene().redraw(...) function will be added inside the current document, replacing everything. -## what's different -The braces are replaced with parenthesis -The ';' are replaced with ',' and you also have to place it after ')' if no other ')' are directly after that to respect the python syntax. -You can't let the modifiers like translate, rotate... be unattached: use the parenthesis or a dot (see below) - - OpenSCAD: difference(){ translate([1,1,0]) cube(2); rotate([0,0,45]) cube(2); } - FreePySCAD: difference()( translate([1,1,0]).cube(2), rotate([0,0,45])(cube(2)),) -resize, minkowski and hull aren't implemented. - -You can also wrote a more concise code with FreePySCAD if you want (i was tired of writing "translate([ ])" over an over) - - cut()( move(1,1).cube(2), cube(2).rotate(0,0,60) , rotate(z=30)(cube(2)) ) - -You can now use functions with real variables that can be changed! -Here is a working ugly example: - - from Pyscad.pyscad import * - def make_T(l,h): - big = cube(l,w,h) - l = l/3.0 - h = 2.0*h/3.0 - return cut("T")( - big, - cube(l,w,h), - cube(l,w,h).move(l*2), - ) - w=10 - T_10cube = make_T(10,10) - w=3 - T_3cube = make_T(10,10) - scene().redraw( - T_10cube, - T_3cube.move(12), - ) -You also have to pass your objects inside the scene.redraw() function to put it into the FreeCAD environment. -You can see and execute some complex exemples in the exemple directory -## FreePySCAD cheatsheet: - -#### 1D: -* line([x1,y1,z1],[x2,y2,z2]) -* arc([x1,y1,z1],[x2,y2,z2],[x3,y3,z3]) -* helix(r,p,h) # p = pitch = height between the begin and the end of a single loop - -#### 1D | 2D: -* circle(r) -* ellipse(r,l) -* polygon([points],closed) -* bspline([points],closed) -* bezier([points],closed) - -#### 2D: -* square(size) -* square([width,height]) | square(width,height) | rectangle([width,height]) -* poly_reg(r|d,nb,inscr) -* text(text,size) -* gear(nb, mod, angle, external, high_precision) - - -#### transformation 1D to 2D to 3D: -* create_wire(closed)(...1D) #create a new wire from many edges, can be extruded if they are connected, you can check that by putting closed to True -* offset2D(length,fillet,fusion)(...2D) -* linear_extrude(height,twist,taper)(obj_2D) -* extrude(x,y,z,taper)(obj_2D) -* rotate_extrude(angle)(obj_2D) #rotate over the Z axis -* path_extrude(frenet,transition)(path_1D, patron_2D) - -note: most of these transformations can only work on a single object, as these can't be unionized before. - -#### 3D: -* sphere(r|d,fn) -* cube(size) -* cube(x,y,z) | cube([width,depth,height]) | box(x,y,z) -* triangle(x,y,z) | triangle([width,depth,height]) -* cylinder(r|d,h,fn,angle) #will call poly_ext if fn >0 -* cone(r1|d1,r2|d2,h,fn) | cylinder(r1|d1,r2|d2,h,fn) -* torus(r1,r2,h) -* poly_ext(r,nb,h) # r = radius, nb = nb vertex (min 3) -* poly_int(a,nb,h) # a = apothem, nb = nb vertex (min 3) -* polyhedron(points, faces) # for debugging use polyhedron_wip : it creates a group of points & faces instead of a 3D solid mesh -* solid_slices(points, centers) #new way to create not-so complicated shells, see below. centers are optional. Much simpler than polyhedron. May not work with not-convex shapes. -* thread(r,p,nb,r2, pattern,fn,center) # implementation of a way to create threads, with pattern (2D array of points). It creates a new 3D object from triangles (vertexes & faces). -* iso_thread(d,p,h,internal,offset,fn) # usage of thread method with an iso pattern. - -#### 3D Boolean operations: -* union()(...3D) | union().add(...3D) # can work with 2D -* intersection()(...3D) -* difference()(...3D) | cut()(...3D) - -#### Transformations: -* mirror(x,y,z)(...) | mirror([x,y,z])(...) -* offset(length,fillet,fusion)(...3D) - -wip, don't work, use the gui for now: -* chamfer().setEdges(radius,edge_id...)(...3D) -* fillet().setEdges(radius,edge_id...)(...3D) - -#### Modifiers: -* .x/y/z() | .center() -* translate/move(x,y,z)(...) | move([x,y,z])(...) | .move(x,y,z) | .move([x,y,z]) | move(x,y,z).stdfuncXXX( -* rotate(x,y,z)(...) | rotate([x,y,z])(...) | .rotate(x,y,z) | .rotate([x,y,z]) | rotate(x,y,z).stdfuncXXX( -* scale(x,y,z)(...) | scale([x,y,z])(...) | .scale(x,y,z) | .scale([x,y,z]) | scale(x,y,z).stdfuncXXX( -* .color("colorname") | .color(r,g,b,a) | .color([r,g,b,a]) | color(something)(...) | color(something).stdfuncXXX( -* .multmatrix(m) - -#### Other: -* scene().draw(...3D) | scene().redraw(...3D) #redraw() erase everything in the document before rebuilding the object tree. Draw() try to update when possible and don't erase everything, but sometimes it fail to detect a change. -* importSvg(filepath,ids) #ids is an optional array of index to say which one have to be imported -* importStl(filepath,ids) -* group()(...) # a group of nodes (1D, 2D & 3D can be mixed), for viewing purpose only as it can't be used by anything, although you can use the modifiers. - -All python syntax and standard library can be used - -### notes: -* ...3D represent a list (possibly empty) of 3D node -* You can replace )(...) by ).add(...) for union, difference and -* Center: on almost every object, you can set as parameter, center=True or center=center_x, center=center_yz, ... - you can also use the transformation .center() or .x(), .yz(), .xyz() .... -* Label: on almost everything, you can set the "name" parameter to whatever you want, it will be shown in the FreeCAD object hierarchy. -* The notation move(2).box(1) should be used only when it's very convenient, it's here mainly to make conversion from OpenSCAD to FreePySCAD more easy, but it can led to strange behaviors, see the two points below. -* Order of execution: move(6)(move(3).move(2).cube(1).move(4).move(5)) => it begin at the object then move away from it. -* The move(2).box(1) work but you cannot do move(1).myfunc() because myfunc isn't in the list of functions that is available to the "move object". In this case, you have to use move(1)(myfunc()) or myfunc().move(1) -* When a part fail to compile, it creates a sphere of size _default_size. you can change the variable _default_size, it's used as a default value when 0.0 will create an impossible object. Example: circle() == circle(_default_size). -* solid_slices : need a double-array of points. Each array of points is a slice. It creates triangles to join one slice to the next. The last point of each slice have to be approximately aligned ( = don't put them 180° apart), because it's used as the first edge. The middle point (mean of all points if not given via the centers argument) is used to choose the next point to draw triangle and for closing the shell at the bottom layer and top layer. The line from the center of a slice to the center of the next one must be inside the slice and the next slice. diff --git a/resources/generation/freecad/Pyscad/__init__.py b/resources/generation/freecad/Pyscad/__init__.py deleted file mode 100644 index be506e0ce..000000000 --- a/resources/generation/freecad/Pyscad/__init__.py +++ /dev/null @@ -1,33 +0,0 @@ -# *************************************************************************** -# * * -# * Copyright (c) 2016 - Victor Titov (DeepSOIC) * -# * * -# * This program is free software; you can redistribute it and/or modify * -# * it under the terms of the GNU Lesser General Public License (LGPL) * -# * as published by the Free Software Foundation; either version 2 of * -# * the License, or (at your option) any later version. * -# * for detail see the LICENCE text file. * -# * * -# * This program is distributed in the hope that it will be useful, * -# * but WITHOUT ANY WARRANTY; without even the implied warranty of * -# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * -# * GNU Library General Public License for more details. * -# * * -# * You should have received a copy of the GNU Library General Public * -# * License along with this program; if not, write to the Free Software * -# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * -# * USA * -# * * -# *************************************************************************** - -__title__ = "Pyscad" -__author__ = "supermerill" -__url__ = "http://www.freecadweb.org" -__doc__ = "Dafuk" - -## @package CompoundTools -# \ingroup PART -# \brief CompoundTools Package for Part workbench - -# import pyscad - diff --git a/resources/generation/freecad/Pyscad/pyscad.py b/resources/generation/freecad/Pyscad/pyscad.py deleted file mode 100644 index 0c7ecdb99..000000000 --- a/resources/generation/freecad/Pyscad/pyscad.py +++ /dev/null @@ -1,2240 +0,0 @@ -######################################################### -# This file is distributed against the LGPL licence # -# This file is made by supermerill (Remi Durand) # -######################################################### - -###################################### -# print("You can use something like 'from ", __name__," import *' for a more convenient experience") -# -# usage: scene().show( many statement ) -# if you want to make a function, don't forget to return the root node! -# Each function like cube() or union() return an object (EasyNode) -# which store the know-how of how to build the object tree if needed. -# The scene will only redraw a node and these children if one of the -# child is changed. -# -# -# move() rotate() scale() color() return a different kind of object -# because they are not a mod but a modifier onto a node. They can -# be use almost like the other one, no ned to worry. -# -# Color, multmatrix and scale can't be applied on a node so they are -# passed to the leafs. -# -# more info on the github wiki -###################################### - -import Part, FreeCAD, math, Draft, InvoluteGearFeature, importSVG, Mesh -from FreeCAD import Base - -######### basic functions ######### -def _getColorCode(str): - if(str == "red"): return (1.0,0.0,0.0,0.0) - if(str == "lime"): return (0.0,1.0,0.0,0.0) - if(str == "blue"): return (0.0,0.0,1.0,0.0) - if(str == "cyan"): return (0.0,1.0,1.0,0.0) - if(str == "aqua"): return (0.0,1.0,1.0,0.0) - if(str == "fuchsia"): return (1.0,0.0,1.0,0.0) - if(str == "yellow"): return (1.0,1.0,0.0,0.0) - if(str == "black"): return (0.0,0.0,0.0,0.0) - if(str == "black"): return (0.0,0.0,0.0,0.0) - if(str == "gray"): return (0.5,0.5,0.5,0.0) - if(str == "silver"): return (.75,.75,.75,0.0) - if(str == "white"): return ( 1, 1, 1,0.0) - if(str == "maroon"): return (0.5,0.0,0.0,0.0) - if(str == "green"): return (0.0,0.5,0.0,0.0) - if(str == "navy"): return (0.0,0.0,0.5,0.0) - if(str == "teal"): return (0.0,0.5,0.5,0.0) - if(str == "purple"): return (0.5,0.0,0.5,0.0) - if(str == "olive"): return (0.5,0.5,0.0,0.0) - return (0.5,0.5,0.5,0.5) - -def _getTuple2(a,b,default_first_item): - if(isinstance(a, list)): - if(len(a)==0): - a=default_first_item - elif(len(a)==1): - a=a[0] - elif(len(a)==2): - b=a[1] - a=a[0] - return (a,b) -def _getTuple2Abs(a,b,default_first_item): - (a,b) = _getTuple2(a,b,default_first_item) - return (abs(a), abs(b)) -def _getTuple3(a,b,c,default_first_item): - if(isinstance(a, list)): - if(len(a)==0): - a=default_first_item - elif(len(a)==1): - a=a[0] - elif(len(a)==2): - b=a[1] - a=a[0] - elif(len(a)==3): - c=a[2] - b=a[1] - a=a[0] - return (a,b,c) -def _getTuple3Abs(a,b,c,default_first_item): - (a,b,c) = _getTuple3(a,b,c,default_first_item) - return (abs(a),abs(b),abs(c)) -def _getTuple4(a,b,c,d,default_first_item): - if(isinstance(a, list)): - if(len(a)==0): - a=default_first_item - elif(len(a)==1): - a=a[0] - elif(len(a)==2): - b=a[1] - a=a[0] - elif(len(a)==3): - c=a[2] - b=a[1] - a=a[0] - elif(len(a)==4): - d=a[3] - c=a[2] - b=a[1] - a=a[0] - return (a,b,c,d) -def _getTuple4Abs(a,b,c,d,default_first_item): - (a,b,c,d) = _getTuple4(a,b,c,d,default_first_item) - return (abs(a),abs(b),abs(c),abs(d)) - -def _moy_vect(points=[]): - moy = [0.0,0.0,0.0] - nb=0 - for p in points: - moy[0]+=p[0] - moy[1]+=p[1] - moy[2]+=p[2] - nb+=1 - moy[0]/=nb - moy[1]/=nb - moy[2]/=nb - return moy - -######### scene class ######### - -if(not 'tree_storage' in globals()): - global tree_storage - tree_storage = {} - -class Scene: - def __init__(self): - doc = FreeCAD.ActiveDocument - #when draw() don't refresh correctly - def redraw(self, *tupleOfNodes): - for obj in FreeCAD.ActiveDocument.Objects : - FreeCAD.ActiveDocument.removeObject(obj.Name) - - for node in tupleOfNodes: - node.create() - global tree_storage - tree_storage[FreeCAD.ActiveDocument] = tupleOfNodes - FreeCAD.ActiveDocument.recompute() - def draw(self, *tupleOfNodes): - global tree_storage - try: - if(not FreeCAD.ActiveDocument in tree_storage): - tree_storage[FreeCAD.ActiveDocument] = [] - keep_nodes = [] - i=0 - doc = FreeCAD.ActiveDocument - newArrayOfNodes = [] - # This algo can't support a shuffle (or insert) of new object in the root. - # But it works - for node_idx in range(len(tupleOfNodes)): - if(len(tree_storage[FreeCAD.ActiveDocument])<=node_idx): - newArrayOfNodes.append(tupleOfNodes[node_idx].create()) - else: - newArrayOfNodes.append(tupleOfNodes[node_idx].check(doc, tree_storage[FreeCAD.ActiveDocument][node_idx])) - node_idx+=1 - if(len(tree_storage[FreeCAD.ActiveDocument])>len(tupleOfNodes)): - for node_idx in range(len(tupleOfNodes), len(tree_storage[FreeCAD.ActiveDocument])): - tree_storage[FreeCAD.ActiveDocument][node_idx].destroyObj(doc) - except: - print("Unexpected error:") - # if True: - tree_storage[FreeCAD.ActiveDocument] = [] - for obj in doc.Objects : - doc.removeObject(obj.Name) - FreeCAD.ActiveDocument.recompute() - raise - tree_storage[FreeCAD.ActiveDocument] = tuple(newArrayOfNodes) - FreeCAD.ActiveDocument.recompute() - return self; - def __call__(self, *tupleOfNodes): - return self.draw(*tupleOfNodes) - -def scene(): - return Scene() - -######### Basic class a utilities ######### - -_idx_EasyNode = 0 -_default_size = 1 - -class EasyNode: - def __init__(self): - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def addAction(self, method, args): - self.actions.append((method,args)) - def __hash__(self): - if(hasattr(self, '_hash')): - if(self._hash): - return 0 - else: - self._hash = True - else: - self._hash = True - hashval = 0 - i=0 - for action in self.actions: - # print("action="+str(action)) - hashval = hashval + (hash(action[0].__name__)) - i+=517 - for arg in action[1]: - if(isinstance(arg, EasyNode) or isinstance(arg, tuple) or isinstance(arg, list)): - temp = 0 - else: - temp = (hash(arg)*i) - hashval = (hashval+temp) if temp<10000 else hashval ^ temp - i+=317 - return hashval - def printhash(self): - hashval = 0 - i=0 - for action in self.actions: - hashval = hashval + (hash(action[0].__name__)) - print("hash of act "+action[0].__name__+" = ",str(hash(action[0].__name__))+" => "+str(hashval)) - i+=517 - - for arg in action[1]: - print(isinstance(arg,EasyNode)) - for arg in action[1]: - # print("arg : "+str(arg)+" "+str( isinstance(arg, EasyNode))) - # try: - # print(dir(arg)) - # except: - # print("not a class") - if(isinstance(arg, EasyNode) or isinstance(arg, tuple)): - temp = 0 - print("arg passed") - else: - temp = (hash(arg)*i) - hashval = (hashval+temp) if temp<10000 else hashval ^ temp - print("hash of arg "+str(arg)+" = "+str(temp*i)+" => "+str(hashval)) - i+=317 - print("final => "+str(hashval)) - return hashval - def hashWhithChilds(self): - hashval = hash(self) - i=0; - for child in self.childs: - hashval = hashval ^ (child.hashWhithChilds()*i) - i+=317 - return hashval - def create(self): - for action in self.actions : - action[0](*action[1]) - for action in self.actions_after : - action[0](*action[1]) - return self - def destroyObj(self, doc): - if(hasattr(self,'obj')): - print("destroy "+self.obj.Name) - doc.removeObject(self.obj.Name) - for child in self.childs: - child.destroyObj(doc) - # return the node to use - def check(self, doc, clone): - print("check "+self.name) - ok = True - if(self.simple): - #must be done before, to update things? - hash(self) - hash(clone) - if(hash(self) == hash(clone) and len(self.childs) == len(clone.childs)): - i=0 - for item in self.childs: - print(hash(self.childs[i])," == ",hash(clone.childs[i])) - ok = ok and hash(self.childs[i]) == hash(clone.childs[i]) - # if(not hash(self.childs[i]) == hash(clone.childs[i])): - # self.childs[i].printhash() - # clone.childs[i].printhash(); - - i+=1 - print("simple, same hash and childs are ",ok) - # print(hash(self)," == ",hash(clone)) - # self.printhash() - # clone.printhash(); - else: - ok = False - print("simple but hash: ",hash(self)," != ",hash(clone),hash(self)==hash(clone)," or ",len(self.childs)," != ",len(clone.childs),len(self.childs)==len(clone.childs)) - else: - ok = (self.hashWhithChilds() == clone.hashWhithChilds()) - print("it's complicated ",ok) - if(ok): - print("keep "+clone.name+" and ditch "+self.name) - #ok, do not modify it but check the childs - self.obj = clone.obj - newNodes = [] - i=0 - ok = True - for child in self.childs: - good = self.childs[i].check(doc, clone.childs[i]) - print("use the new one in the old coll? "+str(good == self.childs[i])+" ( "+str(good)+" =?= "+str(self.childs[i])) - if(not good == clone.childs[i]): - clone.childs[i] = good - ok = False - i+=1 - if(not ok): - print("reflow "+clone.name) - clone.clear_childs().layout_childs(*tuple(clone.childs)) - return clone - else: - print("recreate "+self.name) - # redo everything - clone.destroyObj(doc) - self.create() - return self - - def add(self, *tupleOfNodes): - if(len(tupleOfNodes)==1 and (isinstance(tupleOfNodes[0], list) or isinstance(tupleOfNodes[0], tuple))): - tupleOfNodes = tuple(tupleOfNodes[0]) - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - self.childs.append(enode) - else: - print("error, trying to add '"+str(enode)+"' into a union of EsayNode") - self.actions.append((self.create_childs,tupleOfNodes)) - self.actions.append((self.layout_childs,tupleOfNodes)) - return self - def __call__(self, *tupleOfNodes): - return self.add(*tupleOfNodes) - def create_childs(self, *tupleOfNodes): - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - enode.create() - def layout_childs(self, *tupleOfNodes): - arrayShape = self.obj.Shapes - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - arrayShape.append(enode.obj) - if enode.obj.ViewObject is not None: - enode.obj.ViewObject.Visibility = False - else: - print("error, trying to layout '"+str(enode)+"' into a union of EsayNode") - self.obj.Shapes = arrayShape - return self - def clear_childs(self, *tupleOfNodes): - self.obj.Shapes = [] - return self - # def printmyargs(self, *args): - # print(args) - def translate(self, x=0,y=0,z=0): - return move(x,y,z) - def move(self, x=0,y=0,z=0): - self.actions.append((self.move_action,(x,y,z))) - return self - def move_action(self, x=0,y=0,z=0): - (x,y,z) = _getTuple3(x,y,z,0) - self.obj.Placement.move(Base.Vector(x, y, z)) - return self - def rotate(self, x=0,y=0,z=0): - self.actions.append((self.rotate_action,(x,y,z))) - return self - def rotate_action(self, x=0,y=0,z=0): - (x,y,z) = _getTuple3(x,y,z,0) - myMat = Base.Matrix() - myMat.rotateX(x*math.pi/180) - myMat.rotateY(y*math.pi/180) - myMat.rotateZ(z*math.pi/180) - self.obj.Placement=FreeCAD.Placement(myMat).multiply(self.obj.Placement) - return self - def scale(self, x=0,y=0,z=0,_instantly=False): - self.actions_after.append((self.scale_action,(x,y,z))) - self.simple = False - return self - def scale_action(self, x=0,y=0,z=0): - for node in self.childs: - node.scale(x,y,z,_instantly=True) - return self - def multmatrix(self, mat): - self.actions_after.append((self.multmatrix_action,(mat,))) - self.simple = False - return self - def multmatrix_action(self, mat): - for node in self.childs: - node.multmatrix(mat) - return self - def color(self, r=0.0,v=0.0,b=0,a=0.0,_instantly=False): - self.actions_after.append((self.color_action,(r,v,b,a))) - self.simple= False - return self - def color_action(self, r=0.0,v=0.0,b=0,a=0.0): - for node in self.childs: - node.color(r,v,b,a,_instantly=True) - self.simple = False - return self - def x(self): - self.actions.append((self.xyz_action,(1,0,0))) - return self - def y(self): - self.actions.append((self.xyz_action,(0,1,0))) - return self - def z(self): - self.actions.append((self.xyz_action,(0,0,1))) - return self - def xy(self): - self.actions.append((self.xyz_action,(1,1,0))) - return self - def xz(self): - self.actions.append((self.xyz_action,(1,0,1))) - return self - def yz(self): - self.actions.append((self.xyz_action,(0,1,1))) - return self - def xyz(self): - self.actions.append((self.xyz_action,(1,1,1))) - return self - def xyz_action(self,x,y,z): - self.move_action(-self.centerx*x,-self.centery*y,-self.centerz*z) - return self - def center(self): - self.actions.append((self.center_action,(1,1,1))) - return self - def center_action(self,x,y,z): - self.move_action(-self.centerx if self.centerx>0 else 0.0,-self.centery if self.centery>0 else 0.0,-self.centerz if self.centerz>0 else 0.0) - return self - - -def _easyNodeStub(obj, name): - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - node.name = name+"_"+str(_idx_EasyNode) - node.obj = obj - return node - -def useCenter(node, center): - if(center != None and callable(center)): - center(node) - elif(center != None and isinstance(center, bool) and center): - node.center() - -class EasyNodeColored(EasyNode): - def color(self, r=0.0,v=0.0,b=0.0,a=0.0,_instantly=False): - if isinstance(r, str): - if(a!=0.0): - colorcode = _getColorCode(r) - colorcode[3] = a - if(_instantly): - self.color_action(colorcode) - else: - self.actions.append((self.color_action,(colorcode,))) - else: - if(_instantly): - self.color_action(_getColorCode(r)) - else: - self.actions.append((self.color_action,(_getColorCode(r),))) - else: - #fixme: try to see in an array is not hidden inside the r - (r,v,b,a) = _getTuple4Abs(r,v,b,a,0.0) - if(_instantly): - self.color_action((max(0.0,min(r,1.0)),max(0.0,min(v,1.0)),max(0.0,min(b,1.0)),max(0.0,min(a,1.0)))) - else: - self.actions.append((self.color_action,((max(0.0,min(r,1.0)),max(0.0,min(v,1.0)),max(0.0,min(b,1.0)),max(0.0,min(a,1.0))),))) - return self - def color_action(self, colorcode): - if self.obj.ViewObject is not None: - self.obj.ViewObject.DiffuseColor = [colorcode] - return self -def magic_color(node, r=0.0,v=0.0,b=0.0,a=0.0): - node.color(r,v,b,a) - return node - -class EasyNodeLeaf(EasyNodeColored): - # def __init__(self): - # self.childs = [] - # self.actions = [] - # self.actions_after = [] - # self.simple = True - # self.centerx=0.0 - # self.centery=0.0 - # self.centerz=0.0 - def scale(self, x=0.0,y=0.0,z=0,_instantly=False): - if(_instantly): - self.scale_action(x,y,z) - else: - self.actions.append((self.scale_action,(x,y,z))) - return self - def scale_action(self, x,y,z): - (x,y,z) = _getTuple3(x,y,z,0.0) - myMat = Base.Matrix() - myMat.scale(x,y,z) - self.obj.Shape = self.obj.Shape.transformGeometry(myMat) - return self - def multmatrix_action(self, mat): - flatarray = [] - for array in mat: - for num in array: - flatarray.append(num) - if matsize != len(flatarray): - print("error, wrong matrix") - else: - myMat = Base.Matrix() - myMat.A = flatarray - self.obj.Shape = self.obj.Shape.transformGeometry(myMat) - return self - -# def import_) - -######### transformations ######### - -class EasyNodeUnion(EasyNode): - is2D = False - def layout_childs(self, *tupleOfNodes): - if(self.is2D): - arrayobj= [] - for node in self.childs: - arrayobj.append(node.obj) - newObj = Draft.upgrade(arrayobj,delete=True)[0][0] - # for() - newObj.Placement = Base.Placement(self.obj.Placement) - FreeCAD.ActiveDocument.removeObject(self.obj.Name) - self.obj = newObj - else: - arrayShape = self.obj.Shapes - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - arrayShape.append(enode.obj) - if enode.obj.ViewObject is not None: - enode.obj.ViewObject.Visibility = False - else: - print("error, trying to layout '"+str(enode)+"' into a union of EsayNode") - self.obj.Shapes = arrayShape - return self - -def union(name=None): - global _idx_EasyNode - node = EasyNodeUnion() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "union_"+str(_idx_EasyNode) - else: - node.name = name - def createUnion(node,name): - if(len(node.childs)>0 and isinstance(node.childs[0], EasyNode2D)): - node.is2D = True - #Draft.upgrade(*tuple(node.childs)) - node.obj = FreeCAD.ActiveDocument.addObject("Part::MultiFuse", node.name) - node.addAction(createUnion, (node,name)) - return node - -def inter(name=None): - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "inter_"+str(_idx_EasyNode) - else: - node.name = name - def createInter(node,name): - node.obj = FreeCAD.ActiveDocument.addObject("Part::MultiCommon", node.name) - node.addAction(createInter, (node,name)) - return node -def intersection(name=None): - return inter(name) - -class EasyNodeDiff(EasyNode): - def __init__(self): - self.my_union = None - self.unionChilds = [] - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def add(self, *tupleOfNodes): - if(len(tupleOfNodes)==1 and (isinstance(tupleOfNodes[0], list) or isinstance(tupleOfNodes[0], tuple))): - tupleOfNodes = tuple(tupleOfNodes[0]) - good_array = [] - minidx = 0 - newunion = False - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - good_array.append(enode) - if(len(good_array)==0): - return self - if(len(self.childs)==2 and len(self.childs[1].childs)>1): - # simple case: give them to the union - for enode in good_array : - self.childs.append(enode) - self.unionChilds.append(enode) - elif( (len(self.childs)<2 or len(self.childs[1].childs)==0) and len(self.childs)+len(good_array)>2): - # create the union - self.my_union = union() - newunion = True - self.unionChilds = [] - temp = None - if(len(self.childs)==0): - self.childs.append(good_array[minidx]) - minidx+=1 - if(len(self.childs)==1): - self.childs.append(self.my_union) - else: - self.unionChilds.append(self.childs[1]) - self.childs[1] = self.my_union - self.childs.append(self.unionChilds[0]) - for enode in good_array[minidx:] : - self.childs.append(enode) - self.unionChilds.append(enode) - else: - # just use them - if(len(self.childs)==0 and len(good_array)>0): - self.childs.append(good_array[minidx]) - minidx+=1 - if(len(self.childs)==1 and len(good_array)>0): - self.childs.append(good_array[minidx]) - minidx+=1 - - if(newunion): - self.actions.append((self.create_childs,(self.my_union,)+tuple(good_array))) - else: - self.actions.append((self.create_childs,tuple(good_array))) - self.actions.append((self.layout_childs,tuple(good_array))) - return self - def layout_childs(self, *tupleOfNodes): - if(self.obj.Base == None and len(tupleOfNodes)>0): - self.obj.Base = tupleOfNodes[0].obj - if tupleOfNodes[0].obj.ViewObject is not None: - tupleOfNodes[0].obj.ViewObject.Visibility = False - tupleOfNodes = tupleOfNodes[1:] - if(self.my_union == None and self.obj.Tool == None and len(tupleOfNodes)>0): - self.obj.Tool = tupleOfNodes[0].obj - if tupleOfNodes[0].obj.ViewObject is not None: - tupleOfNodes[0].obj.ViewObject.Visibility = False - tupleOfNodes = tupleOfNodes[1:] - if(self.my_union != None and len(tupleOfNodes)>0): - self.obj.Tool = self.my_union.obj - self.my_union.layout_childs(*tupleOfNodes) - return self - -def cut(name=None): - global _idx_EasyNode - node = EasyNodeDiff() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "cut_"+str(_idx_EasyNode) - else: - node.name = name - def createCut(node,name): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Cut", node.name) - node.obj.Base = None - node.obj.Tool = None - node.addAction(createCut, (node,name)) - return node -def difference(name=None): - return cut(name) - -# FIXME: The color doesn't apply to the chamfered/fillet section -class EasyNodeChamfer(EasyNode): - def __init__(self): - self.edges = []; - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def layout_childs(self, *tupleOfNodes): - if(len(tupleOfNodes)>0): - self.obj.Base = tupleOfNodes[0].obj - if self.obj.Base.ViewObject is not None: - self.obj.Base.ViewObject.Visibility = False - self.obj.Edges = self.edges - return self - def set(self, node): - self.add(node) - return self - def addEdge(self, length, *arrayOfEdges): - if isinstance(length, list): - if len(length) == 2: - self.addEdge2(length[0],length[1],*arrayOfEdges) - else: - self.addEdge2(length[0],length[0],*arrayOfEdges) - else: - self.addEdge2(length,length,*arrayOfEdges) - return self - def addEdge2(self, startLength, endLength, *arrayOfEdges): - for eidx in arrayOfEdges : - self.edges.append((eidx, startLength, endLength)) - # self.obj.Edges = self.edges - return self - def setNb(self, length, nb, node): - self.set(node) - for eidx in range(1, nb+1) : - self.edges.append((eidx, length, length)) - # self.obj.Edges = self.edges - return self - -def chamfer(name=None): - global _idx_EasyNode - node = EasyNodeChamfer() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "chamfer_"+str(_idx_EasyNode) - else: - node.name = name - def createChamfer(node): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Chamfer", node.name) - node.addAction(createChamfer, (node,)) - return node - -def fillet(name=None): - global _idx_EasyNode - node = EasyNodeChamfer() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "fillet_"+str(_idx_EasyNode) - else: - node.name = name - def createFillet(node): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Fillet", node.name) - node.addAction(createFillet, (node,)) - return node - -class EasyNodeMirror(EasyNodeColored): - def __init__(self): - self.edges = []; - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def layout_childs(self, *tupleOfNodes): - if(len(tupleOfNodes)>0): - self.obj.Source = tupleOfNodes[0].obj - if self.obj.Source.ViewObject is not None: - self.obj.Source.ViewObject.Visibility = False - return self - -def mirror(x=0.0,y=0.0,z=0.0,name=None): - (x,y,z) = _getTuple3(x,y,z,0.0) - global _idx_EasyNode - node = EasyNodeMirror() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "mirror_"+str(_idx_EasyNode) - else: - node.name = name - def createMirror(node,x,y,z): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Mirroring", node.name) - node.obj.Base = Base.Vector(0,0,0) - node.obj.Normal = Base.Vector(x,y,z).normalize() - node.addAction(createMirror, (node,x,y,z)) - return node - -def offset(length=_default_size,fillet=True,fusion=True,name=None): - global _idx_EasyNode - node = EasyNodeMirror() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "offset_"+str(_idx_EasyNode) - else: - node.name = name - def createOffset(node, length, fillet, fusion): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Offset", node.name) - node.obj.Value = length - node.obj.Mode = 0 - node.obj.Join = 0 if(fillet) else 2 - node.obj.Intersection = fusion - node.obj.SelfIntersection = False - node.addAction(createOffset, (node, length, fillet, fusion)) - return node - -def offset2D(length=_default_size,fillet=True,fusion=True,name=None): - global _idx_EasyNode - node = EasyNodeMirror() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "offset2D_"+str(_idx_EasyNode) - else: - node.name = name - def createOffset2D(node, length, fillet, fusion): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Offset2D", node.name) - node.obj.Value = length - node.obj.Mode = 0 - node.obj.Join = 0 if(fillet) else 2 - node.obj.Intersection = fusion - node.obj.SelfIntersection = False - node.addAction(createOffset2D, (node, length, fillet, fusion)) - return node - -# group: can't be modified, it's just for storage for cleaning the hierarchy -# move & rotate are passed to the childs. -#note: even less tested than the rest -class EasyNodeGroup(EasyNode): - def layout_childs(self, *tupleOfNodes): - arrayShape = self.obj.Group - for enode in tupleOfNodes : - if(isinstance(enode, EasyNode)): - arrayShape.append(enode.obj) - if enode.obj.ViewObject is not None: - enode.obj.ViewObject.Visibility = True - else: - print("error, trying to layout '"+str(enode)+"' into a union of EsayNode") - self.obj.Group = arrayShape - def move_action(self, x=0,y=0,z=0): - print("moveaction group:",x, y, z) - for enode in self.childs : - if(isinstance(enode, EasyNode)): - print(enode.name+" moveaction") - enode.move_action(x, y, z) - return self - def rotate_action(self, x=0,y=0,z=0): - for enode in self.childs : - if(isinstance(enode, EasyNode)): - enode.rotate_action(x, y, z) - return self -def group(name=None): - global _idx_EasyNode - node = EasyNodeGroup() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "group_"+str(_idx_EasyNode) - else: - node.name = name - def createGroup(node,name): - node.obj = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", node.name) - node.addAction(createGroup, (node,name)) - return node - - -######### 3D objects ######### - - -def cube(size=0.0,y=0.0,z=0.0,center=None,x=0.0, name=None): - (x,y,z,size) = _getTuple4Abs(size,y,z,x,0.0) - if(x==0.0 and size != 0.0): - x = size - if(y==0.0 and z==0.0): - return box(x,x,x,center, name) - else: - return box(x,y if y!=0.0 else _default_size, z if z!=0.0 else _default_size,center, name) - -def box(x=_default_size,y=_default_size,z=_default_size,center=None, name=None): - (x,y,z) = _getTuple3Abs(x,y,z,_default_size) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "cube_"+str(_idx_EasyNode) - else: - node.name = name - def createBox(node, x,y,z,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeBox(x, y, z) - node.centerx = x/2.0 - node.centery = y/2.0 - node.centerz = z/2.0 - useCenter(node, center) - node.addAction(createBox, (node, x,y,z,center)) - return node - -def tri_rect(x=_default_size,y=_default_size,z=_default_size,center=None, name=None): - (x,y,z) = _getTuple3Abs(x,y,z,_default_size) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "tri_rect_"+str(_idx_EasyNode) - else: - node.name = name - def createTriRect(node, x,y,z,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - poly = Part.makePolygon([Base.Vector(0,0,0),Base.Vector(x,0,0),Base.Vector(0,y,0),Base.Vector(0,0,0)]) - node.obj.Shape = Part.Face(poly).extrude(Base.Vector(0,0,z)) - node.centerx = x/2.0 - node.centery = y/2.0 - node.centerz = z/2.0 - useCenter(node, center) - node.addAction(createTriRect, (node, x,y,z,center)) - return node - -def cylinder(r=_default_size,h=_default_size,center=None,d=0.0,r1=0.0,r2=0.0,d1=0.0,d2=0.0,angle=360.0,fn=1,name=None): - r = abs(r) - h = abs(h) - d = abs(d) - if( (r == _default_size or r == 0) and d != 0.0): - r = d/2.0 - if( d1!=0.0 and d1==d2 ): - r = d1/2.0 - d1 = d2 = 0 - if( r1!=0.0 and r1==r2 ): - r = r1 - r1 = r2 = 0 - if( (r == _default_size or r == 0) and (r1!=0.0 or r2!=0.0 or d1!=0.0 or d2!=0.0)): - return cone(r1,r2,h,center,d1,d2,fn) - if(fn>2): - return poly_ext(r=_default_size, nb=fn, h=h,center=center,d=0.0,name=name) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "cylinder_"+str(_idx_EasyNode) - else: - node.name = name - def createCylinder(node, r,h,center,angle): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - if(int(angle) == 360): - node.obj.Shape = Part.makeCylinder(r,h) - else: - node.obj.Shape = Part.makeCylinder(r,h,Base.Vector(0,0,0),Base.Vector(0,0,1),angle) - node.centerx = -r - node.centery = -r - node.centerz = h/2.0 - useCenter(node, center) - node.addAction(createCylinder, (node, r,h,center,angle)) - return node - -def cone(r1=_default_size*2,r2=_default_size,h=_default_size,center=None,d1=0.0,d2=0.0,fn=1,name=None): - r1 = abs(r1) - r2 = abs(r2) - h = abs(h) - d1 = abs(d1) - if( (r1 == _default_size*2 or r1 == 0) and d1 != 0.0): - r1 = d1/2.0 - d2 = abs(d2) - if( (r2 == _default_size or r2 == 0) and d2 != 0.0): - r2 = d2/2.0 - if(fn>2): - #compute extrusion angle FIXME: cone(r1=3,r2=1,h=4,fn=4) it amlost a pyramid! - angle = 0.0 - if(r1>r2): - angle = -math.atan(float(r1-r2)/float(h)) - else: - angle = math.atan(float(r2-r1)/float(h)) - #print(str(r1)+" "+str(fn)+" "+str(angle)) - return linear_extrude(height=h,taper=angle)(poly_reg(r1,fn,center=center)) - # return poly_reg(r1,fn,center=center) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "cone_"+str(_idx_EasyNode) - else: - node.name = name - def createCone(node, r1,r2,h,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeCone(r1,r2,h) - node.centerx = -r1 if r1>r2 else -r2 - node.centery = -r1 if r1>r2 else -r2 - node.centerz = h/2.0 - useCenter(node, center) - node.addAction(createCone, (node, r1,r2,h,center)) - return node - -def sphere(r=_default_size,center=None,d=0.0,fn=1,name=None): - r = abs(r) - d = abs(d) - if( (r == _default_size or r == 0.0) and d != 0.0): - r = d/2.0 - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "sphere_"+str(_idx_EasyNode) - else: - node.name = name - def createSphere(node, r,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeSphere(r) - node.centerx = -r - node.centery = -r - node.centerz = -r - useCenter(node, center) - node.addAction(createSphere, (node, r,center)) - return node - -def torus(r1=_default_size, r2=0.1,center=None,d1=0.0,d2=0.0,name=None): - r1 = abs(r1) - r2 = abs(r2) - d1 = abs(d1) - if( (r1 == _default_size or r1 == 0.0) and d1 != 0.0): - r1 = d1/2.0 - d2 = abs(d2) - if( (r2 == _default_size or r2 == 0.0) and d2 != 0.0): - r2 = d2/2.0 - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "torus_"+str(_idx_EasyNode) - else: - node.name = name - def createTorus(node, r1,r2,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeTorus(r1, r2) - node.centerx = -r1 - node.centery = -r1 - node.centerz = -r2 - useCenter(node, center) - node.addAction(createTorus, (node, r1,r2,center)) - return node - - -def poly_ext(r=_default_size, nb=3, h=_default_size,center=None,d=0.0,name=None): - r = abs(r) - h = abs(h) - nb= max(3, abs(nb)) - d = abs(d) - if( (r ==_default_size or r == 0.0) and d != 0.0): - r = d/2.0 - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "3Dpolygon_"+str(_idx_EasyNode) - else: - node.name = name - def createPolyExt(node, r,nb,h,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - # create polygon - points = [Base.Vector(r,0,0)] - for i in range(1,nb): - points.append(Base.Vector(r * math.cos(2*math.pi*i/nb), r * math.sin(2*math.pi*i/nb),0)) - points.append(Base.Vector(r,0,0)) - temp_poly = Part.makePolygon(points) - node.obj.Shape = Part.Face(temp_poly).extrude(Base.Vector(0,0,h)) - # pol = Draft.makePolygon(nb,radius=r,inscribed=True,face=True,support=None) - # node.obj.Shape = Part.Face(pol).extrude(Base.Vector(0,0,h)) - node.centerx = -r - node.centery = -r - node.centerz = h/2.0 - useCenter(node, center) - node.addAction(createPolyExt, (node, r,nb,h,center)) - return node - -def poly_int(a=_default_size, nb=3, h=_default_size,center=None,d=0.0,name=None): - a = abs(a) - h = abs(h) - nb= max(3, abs(nb)) - d = abs(d) - if( (a == _default_size or a == 0.0) and d != 0.0): - a = d/2.0 - # create polygon with apothem, not radius - radius = a / math.cos(math.radians(180)/nb) - return poly_ext(radius,nb,h,center,name=name) - -def polyhedron(points=[], faces=[],center=None,name=None): - if(not (isinstance(points[0], list) or isinstance(points[0], tuple)) or len(points)<2): - return sphere(1) - if(not (isinstance(faces[0], list) or isinstance(faces[0], tuple)) or len(faces)<2): - return sphere(1) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "polyhedron_"+str(_idx_EasyNode) - else: - node.name = name - def createPolyhedron(node, points,faces): - try: - vectors = [] - for point in points: - vectors.append(Base.Vector(float(point[0]) if len(point)>0 else 0.0, float(point[1]) if len(point)>1 else 0.0, float(point[2]) if len(point)>2 else 0.0)) - polygons = [] - for face in faces: - face_vect = [] - for idx in face: - face_vect.append(vectors[idx]) - face_vect.append(vectors[face[0]]) - print("face : ",face_vect) - polygons.append(Part.Face([Part.makePolygon(face_vect)])) - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.Solid(Part.Shell(polygons)) - node.centerx = 0.0 - node.centery = 0.0 - node.centerz = 0.0 - except: - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeSphere(_default_size) - node.move(_moy_vect(points)) - node.centerx = 0.0 - node.centery = 0.0 - node.centerz = 0.0 - node.addAction(createPolyhedron, (node, points,faces)) - return node - -def polyhedron_wip(points=[], faces=[],center=None,name=None): - if(not (isinstance(points[0], list) or isinstance(points[0], tuple)) or len(points)<2): - return sphere(1) - global _idx_EasyNode - if(name == None or not isinstance(name, str)): - name = "polyhedron_wip_"+str(_idx_EasyNode) - node = group(name) - shapes = [] - if(len(faces)==0): - idx=0 - for p in points: - shapes.append(point(p, name=node.name+"_p_"+str(idx))) - idx+=1 - elif(len(faces)>0): - idx=0 - for p in points: - shapes.append(point(p, name=node.name+"_p_"+str(idx))) - idx+=1 - idx=0 - for face in faces: - face_vect = [] - for idx in face: - if(idx>=len(points)): - face_vect = [] - break; - face_vect.append(points[idx]) - if(len(face_vect)>2): - shapes.append(polygon(face_vect,closed=True, name=node.name+"_face_"+str(idx))) - else: - shapes.append(sphere(_default_size, name=node.name+"_face_"+str(idx))) - idx+=1 - node.add(shapes) - return node - - -def _center_point(points=[]): - center = [0.0,0.0,0.0] - for flpoint in points: - center[0] += flpoint[0] - center[1] += flpoint[1] - center[2] += flpoint[2] - return Base.Vector(center[0]/len(points),center[1]/len(points),center[2]/len(points)) - -def _calc_angle_3D(v1,v2,norm_approx): - # dot = x1*x2 + y1*y2 # dot product - # det = x1*y2 - y1*x2 # determinant - det = 0 - vdet = v1.cross(v2) - if(norm_approx.dot(vdet)<0): - det = -vdet.Length - else: - det = vdet.Length - #det = v1.x*v2.y - v1.y*v2.x - # print("atan2",det, v1.dot(v2), v1.cross(v2)) - angle = math.atan2(det, v1.dot(v2)) # atan2(y, x) or atan2(sin, cos) - if(angle<0): - angle = angle + math.pi*2 - return angle -def _calc_angle_3D_spec(id1, id2, pref, p1, p2, pcenter, vn): - angle2=0 - if(id2<0): - angle2 = _calc_angle_3D(pref-pcenter, p2-pcenter, vn) - math.pi*2 - elif(id2>0): - angle2 = _calc_angle_3D(pref-pcenter, p2-pcenter, vn) - if(angle2!=0): - return angle2 - else: - angle1=0 - if(id1<0): - angle1 = _calc_angle_3D(pref-pcenter, p1-pcenter, vn) - math.pi*2 - elif(id1>0): - angle1 = _calc_angle_3D(pref-pcenter, p1-pcenter, vn) - - return angle1 -def _create_geometry_slices(points = [], centers=[]): - print(len(points), len(centers)) - #first layer: connect them with center - rPoints = [] - centersP=[] - rPoints.append([]) - for flpoint in points[0]: - rPoints[0].append(Base.Vector(flpoint[0],flpoint[1],flpoint[2])) - # Draft.makePoint(flpoint[0],flpoint[1],flpoint[2]) - if(len(centers)==0): - centersP.append(_center_point(points[0])) - print("new center 0 : ",centersP[0]) - else: - centersP.append(Base.Vector(centers[0])) - print("get center 0 : ",centersP[0]) - # Draft.makePoint(centersP[0]) - faces=[] - if(len(points[0])>3): - face = Part.Face(Part.makePolygon([centersP[0],rPoints[0][len(rPoints[0])-1], rPoints[0][0] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - print(faces) - for idp in range(1,len(rPoints[0])): - face = Part.Face(Part.makePolygon([centersP[0],rPoints[0][idp-1], rPoints[0][idp] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - elif(len(points[0])==3): - face = Part.Face(Part.makePolygon([rPoints[0][0],rPoints[0][1], rPoints[0][2] ],True)) - faces.append(face) - - nbLayers = len(points) - for numlayer in range(1,nbLayers): - print(numlayer) - rPoints.append([]) - if(len(centers)<=numlayer): - centersP.append(_center_point(points[numlayer])) - # print("new center "+str(numlayer)+" : ",centersP[numlayer]) - else: - centersP.append(Base.Vector(centers[numlayer])) - # print("get center "+str(numlayer)+" : ",centersP[numlayer], centers[numlayer]) - # Draft.makePoint(centersP[numlayer]) - for flpoint in points[numlayer]: - rPoints[numlayer].append(Base.Vector(flpoint[0],flpoint[1],flpoint[2])) - id_bot = len(rPoints[numlayer-1])-1 - point_bot = rPoints[numlayer-1][id_bot] - id_top = len(rPoints[numlayer])-1 - point_top = rPoints[numlayer][id_top] - id_bot = -1 if(id_bot>0) else 0 - id_top = -1 if(id_top>0) else 0 - while(id_bot angletop or (anglebot == angletop and len(rPoints[numlayer])3): - face = Part.Face(Part.makePolygon([centersP[nbLayers-1],rPoints[nbLayers-1][len(rPoints[nbLayers-1])-1], rPoints[nbLayers-1][0] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - for idp in range(1,len(rPoints[nbLayers-1])): - face = Part.Face(Part.makePolygon([centersP[nbLayers-1],rPoints[nbLayers-1][idp-1], rPoints[nbLayers-1][idp] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - elif(len(points[0])==3): - face = Part.Face(Part.makePolygon([rPoints[nbLayers-1][0],rPoints[nbLayers-1][1], rPoints[nbLayers-1][2] ],True)) - faces.append(face) - - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = Part.Solid(Part.Shell(faces)) - # obj.Shape = (Part.Shell(faces)) - return Part.Solid(Part.Shell(faces)) -_default_size=1 -def solid_slices(points=[],centers=[],center=None,name=None): - if(len(points)<2): - return sphere(_default_size) - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "solid_slices_"+str(_idx_EasyNode) - else: - node.name = name - def createSolidSlices(node, points, centers, center): - print("centersA=",len(centers)) - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - try: - node.obj.Shape = _create_geometry_slices(points, centers) - pcenter = _center_point(points[0]) - node.centerx = pcenter.x - node.centery = pcenter.y - node.centerz = (_center_point(points[len(points)-1]).z-pcenter.z)/2.0 - useCenter(node, center) - except: - node.obj.Shape = Part.makeSphere(_default_size) - node.centerx = 0.0 - node.centery = 0.0 - node.centerz = 0.0 - node.addAction(createSolidSlices, (node, points, list(centers), center)) - return node - -thread_pattern_triangle = [[0,0],[1,1],[0,1]] -thread_pattern_iso_internal = [[0,0],[0,0.125],[1,0.4375],[1,0.6875],[0,1]] -# 375 -> -625 -> 312.5 -thread_pattern_iso_external = [[0,0],[0,0.250],[1,0.5625],[1,0.6875],[0,1]] -def thread(r=_default_size,p=_default_size,nb=_default_size,r2=_default_size*1.5, pattern=[[0,0],[1,0.5],[0,1]],fn=8,center=None,name=None): - #check pattern - if(len(pattern)<1): - return sphere(1) - #check if begin in (0,0) - if(pattern[0][0] !=0 or pattern[0][1] != 0): - pattern.insert(0,[0,0]) - #check if return to x=0 line - if(pattern[len(pattern)-1][0] !=0): - pattern.append([0,1]) - if(nb<1): - nb = 1 - - #create node - global _idx_EasyNode - node = EasyNodeLeaf() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "thread_"+str(_idx_EasyNode) - else: - node.name = name - - #def construct - def createThread(node, r, p, nb, r2, pattern,fn, center): - maxXpat = 0.0 - for ppat in pattern: - maxXpat = max(ppat[0], maxXpat) - scalex = (r2-r)/float(maxXpat) - scaley = p/pattern[len(pattern)-1][1] - #make - faces = [] - pattVect = [] - for pattp in pattern: - pattVect.append(Base.Vector(r+pattp[0]*scalex,0,pattp[1]*scaley)) - previousLine = [] - - #init - zoffsetIncr = p/float(fn) - zoffset = -p - #iterations - rotater = Base.Rotation() - rotater.Axis = Base.Vector(0,0,1) - rotater.Angle = (math.pi*2.0/fn)*(-1) - for pattp in pattVect: - tempvec = rotater.multVec(pattp) - tempvec.z += zoffset - previousLine.append(tempvec) - - #closing faces - rotater.Angle = (math.pi*2.0/fn)*(-2) - closevec = rotater.multVec(pattp) - closevec.z += zoffset - zoffsetIncr - for id in range(1,len(pattVect)): - face = Part.Face(Part.makePolygon([previousLine[id-1], closevec, previousLine[id] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - middlevec = Base.Vector(0,0,-p) - for step in range(0,fn): - rotater.Angle = (math.pi*2.0/fn)*(step-1) - nextclosevec = rotater.multVec(pattp) - nextclosevec.z += zoffset -p + zoffsetIncr * step - face = Part.Face(Part.makePolygon([closevec, middlevec, nextclosevec ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - closevec = nextclosevec - - #iterate - for turn in range(0,nb): - for step in range(0,fn): - zoffset += zoffsetIncr - rotater.Angle = (math.pi*2.0/fn)*step - nextLine = [] - for pattp in pattVect: - tempvec = rotater.multVec(pattp) - tempvec.z += zoffset - nextLine.append(tempvec) - for id in range(1,len(pattVect)): - # face = Part.Face(Part.makePolygon([previousLine[id-1],nextLine[id-1], nextLine[id], previousLine[id] ],True)) - face = Part.Face(Part.makePolygon([previousLine[id-1],nextLine[id-1], previousLine[id] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - face = Part.Face(Part.makePolygon([nextLine[id-1], nextLine[id], previousLine[id] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - previousLine = nextLine - - #closing faces - rotater.Angle = (math.pi*2.0/fn)*(0) - closevec = rotater.multVec(pattp) - closevec.z += zoffset -p + zoffsetIncr - for id in range(1,len(pattVect)): - face = Part.Face(Part.makePolygon([previousLine[id-1], closevec, previousLine[id] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - middlevec = Base.Vector(0,0,(nb)*p) - rotater.Angle = (math.pi*2.0/fn)*(-1) - nextvec = rotater.multVec(pattp) - nextvec.z += zoffset + zoffsetIncr - face = Part.Face(Part.makePolygon([closevec, middlevec, previousLine[id] ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - for step in range(0,fn-1): - rotater.Angle = (math.pi*2.0/fn)*(step+1) - nextclosevec = rotater.multVec(pattp) - nextclosevec.z += zoffset - p + zoffsetIncr * (step+2) - face = Part.Face(Part.makePolygon([closevec, middlevec, nextclosevec ],True)) - faces.append(face) - # obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "obj_"+str(_idx_EasyNode)) - # obj.Shape = face - closevec = nextclosevec - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.Solid(Part.Shell(faces)) - print(node.obj) - - node.centerx = -r - node.centery = -r - node.centerz = p*(nb-1)/2.0 - useCenter(node, center) - - node.addAction(createThread, (node, r, p, nb, r2, pattern,fn, center)) - return node - -def iso_thread(d=1,p=0.25, h=1,internal = False, offset=0.0,name=None, fn=15): - my_pattern = [[0,0],[0,0.250],[1,0.5625],[1,0.6875],[0,1]] - if(internal): - my_pattern = [[0,0],[0,0.125],[1,0.4375],[1,0.6875],[0,1]] - # if(offset!=0): - my_pattern[1][1] = my_pattern[1][1]+offset*2 - my_pattern[2][1] = my_pattern[2][1]+offset*2 - print("internal?", internal, my_pattern) - rOffset = 0.866*p*5/8.0 - rMax = d/2.0 - return cut(name=name)( - thread(r=rMax-rOffset, r2=rMax, p=p, nb=int(h/p +2),fn=fn, pattern = my_pattern).move(z= -offset/2.0), - cube(3*d,3*d,3*p).xy().move(z= -3*p), - cube(3*d,3*d,4*p).xy().move(z=h), - ) - -######### 2D & 1D objects ######### - -class EasyNode2D(EasyNodeLeaf): - def __init__(self): - # super().__init__() - centerz = 0.0 - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def rotate2D(self, x=0.0,y=0.0): - (x,y) = _getTuple2(x,y,0.0) - myMat = Base.Matrix() - myMat.rotateX(y*math.pi/180) - myMat.rotateY(x*math.pi/180) - myMat.rotateZ(0.0) - self.obj.Placement=FreeCAD.Placement(myMat).multiply(self.obj.Placement) - return self - -def circle(r=_default_size,fn=1,closed=True,center=None,d=0.0,name=None): - r=abs(r) - d = abs(d) - if(fn>2): - return poly_reg(r,fn,center) - if( r == 0.0 and d != 0.0): - r = d/2.0 - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "circle_"+str(_idx_EasyNode) - else: - node.name = name - def createCircle(node, r,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - temp_poly = Part.makeCircle(r) - if(closed): - node.obj.Shape = Part.Face(Part.Wire(temp_poly)) - else: - node.obj.Shape = Part.Wire(temp_poly) - node.centerx = -r - node.centery = -r - useCenter(node, center) - node.addAction(createCircle, (node, r,center)) - return node - -def ellipse(r1=0.0,r2=0.0,center=None,d1=0.0,d2=0.0,name=None): - r1 = abs(r1) - r2 = abs(r2) - d1 = abs(d1) - d2 = abs(d2) - if( r1 == 0.0 and d1 != 0.0): - r1 = d1/2.0 - if( r2 == 0.0 and d2 != 0.0): - r2 = d2/2.0 - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "ellipse_"+str(_idx_EasyNode) - else: - node.name = name - def createEllipse(node, r1,r2,center): - # node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", "ellipse_"+str(_idx_EasyNode)) - node.obj = Draft.makeEllipse(r2,r1) - node.obj.Label = node.name - # node.obj.Shape = temp_poly - node.centerx = -r2 - node.centery = -r1 - useCenter(node, center) - node.addAction(createEllipse, (node, r1,r2,center)) - return node - -def poly_reg(r=_default_size,nb=3,center=None,inscr=True,d=0.0,name=None): - r=abs(r) - d = abs(d) - nb=max(3,abs(nb)) - if( r == 0.0 and d != 0.0): - r = d/2.0 - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "reg_ploygon_"+str(_idx_EasyNode) - else: - node.name = name - def createPolygonReg(node, r,nb,center,inscr): - node.obj = Draft.makePolygon(nb,radius=r,inscribed=inscr,face=True,support=None) - node.obj.Label = node.name - # temp_poly = Part.makeCircle(r) - # node.obj.Shape = temp_poly - node.centerx = -r - node.centery = -r - useCenter(node, center) - node.addAction(createPolygonReg, (node, r,nb,center,inscr)) - return node - -def square(size=_default_size,y=0.0,x=0.0,center=None,name=None): - (size,y,x) = _getTuple3Abs(size,y,x,_default_size) - if(y>0.0): - if(x>0.0): - return rectangle(x,y,center=center) - else: - return rectangle(size,y,center=center) - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "square_"+str(_idx_EasyNode) - else: - node.name = name - def createSquare(node, size,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makePlane(size, size) - node.centerx = size/2.0 - node.centery = size/2.0 - useCenter(node, center) - node.addAction(createSquare, (node, size,center)) - return node - -def rectangle(x=_default_size,y=_default_size,center=None,name=None): - (x,y) = _getTuple2Abs(x,y,_default_size) - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "rectangle_"+str(_idx_EasyNode) - else: - node.name = name - def createRectangle(node, x,y,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makePlane(x, y) - node.centerx = x/2.0 - node.centery = y/2.0 - useCenter(node, center) - node.addAction(createRectangle, (node, x,y,center)) - return node - -def polygon(points=[], closed=True,name=None): - if(not (isinstance(points[0], list) or isinstance(points[0], tuple)) or len(points)<2): - return circle(_default_size) - if(len(points)==1 and (isinstance(points[0], list) or isinstance(points[0], tuple))): - points = points[0] - if len(points) < 3: - return circle(_default_size) - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "polygon_"+str(_idx_EasyNode) - else: - node.name = name - def createPolygon(node, points,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - vectors = [] - for point in points: - vectors.append(Base.Vector(float(point[0]) if len(point)>0 else 0.0, float(point[1]) if len(point)>1 else 0.0, float(point[2]) if len(point)>2 else 0.0)) - if(closed): - vectors.append(Base.Vector(float(points[0][0]) if len(points[0])>0 else 0.0, float(points[0][1]) if len(points[0])>1 else 0.0, float(points[0][2]) if len(points[0])>2 else 0.0)) - try: - temp_poly = Part.makePolygon(vectors) - if(closed): - node.obj.Shape = Part.Face(temp_poly) - else: - node.obj.Shape = temp_poly - except: - node.obj.Shape = Part.makeSphere(_default_size) - node.move(_moy_vect(points)) - node.centerx = 0.0 - node.centery = 0.0 - node.addAction(createPolygon, (node, points,center)) - return node - -def bspline(points=[], closed=False,name=None): - if(not (isinstance(points[0], list) or isinstance(points[0], tuple)) or len(points)<2): - return circle(_default_size) - if(len(points)==1 and (isinstance(points[0], list) or isinstance(points[0], tuple))): - points = points[0] - vectors = [] - for point in points: - if isinstance(point, list) or isinstance(point, tuple) : - vectors.append(FreeCAD.Vector(point[0] if len(point)>0 else 0.0, point[1] if len(point)>1 else 0.0, point[2] if len(point)>2 else 0.0)) - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "bspline_"+str(_idx_EasyNode) - else: - node.name = name - def createBspline(node, vectors,center,closed): - node.obj = Draft.makeBSpline(vectors,closed=closed,face=closed,support=None) - node.obj.Label = node.name - node.centerx = 0.0 - node.centery = 0.0 - node.addAction(createBspline, (node, vectors,center,closed)) - return node - -def bezier(points=[], closed=False,name=None): - if(not (isinstance(points[0], list) or isinstance(points[0], tuple)) or len(points)<3): - return circle(_default_size) - if(len(points)==1 and (isinstance(points[0], list) or isinstance(points[0], tuple))): - points = points[0] - vectors = [] - for point in points: - if isinstance(point, list) or isinstance(point, tuple) : - vectors.append(FreeCAD.Vector(point[0] if len(point)>0 else 0.0, point[1] if len(point)>1 else 0.0, point[2] if len(point)>2 else 0.0)) - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "bezier_"+str(_idx_EasyNode) - else: - node.name = name - def createBezier(node, vectors,center,closed): - node.obj = Draft.makeBezCurve(vectors,closed,support=None) - node.obj.Label = node.name - node.centerx = 0.0 - node.centery = 0.0 - node.addAction(createBezier, (node, vectors,center,closed)) - return node - -def helix(r=_default_size,p=_default_size,h=_default_size,center=None,d=0.0,name=None): - r = abs(r) - p = abs(p) - h = abs(h) - d = abs(d) - if( r == 0.0 and d != 0.0): - r = d/2.0 - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "helix_"+str(_idx_EasyNode) - else: - node.name = name - def createHelix(node, r,p,h,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeHelix (p, h, r) - node.centerx = -float(r) - node.centery = -float(r) - useCenter(node, center) - node.addAction(createHelix, (node, r,p,h,center)) - return node - -def point(p=[0.0,0.0,0.0],center=None,name=None): - if(not isinstance(p,list) and not isinstance(p,tuple)): - p = [0.0,0.0,0.0] - p = ( float(p[0]) if len(p)>0 else 0.0, float(p[1]) if len(p)>1 else 0.0, float(p[2]) if len(p)>2 else 0.0 ) - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "line_"+str(_idx_EasyNode) - else: - node.name = name - def createPoint(node, p,center): - node.obj = Draft.makePoint(Base.Vector(p)) - node.centerx = 0 - node.centery = 0 - node.centerz = 0 - useCenter(node, center) - node.addAction(createPoint, (node, p,center)) - return node - -def line(p1=[0.0,0.0,0.0],p2=[_default_size,_default_size,_default_size],center=None,name=None): - if(not isinstance(p1,list) and not isinstance(p1,tuple)): - p1 = [0.0,0.0,0.0] - if(not isinstance(p2,list) and not isinstance(p2,tuple)): - p2 = [_default_size,_default_size,_default_size] - p1 = ( float(p1[0]) if len(p1)>0 else 0.0, float(p1[1]) if len(p1)>1 else 0.0, float(p1[2]) if len(p1)>2 else 0.0 ) - p2 = ( float(p2[0]) if len(p2)>0 else 0.0, float(p2[1]) if len(p2)>1 else 0.0, float(p2[2]) if len(p2)>2 else 0.0 ) - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "line_"+str(_idx_EasyNode) - else: - node.name = name - def createLine(node, p1,p2,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.makeLine(p1,p2) - node.centerx = (p1[0]+p2[0])/2.0 - node.centery = (p1[1]+p2[1])/2.0 - node.centerz = (p1[2]+p2[2])/2.0 - useCenter(node, center) - node.addAction(createLine, (node, p1,p2,center)) - return node - -def arc(p1=[_default_size,0.0,0.0],p2=[_default_size*0.7071,_default_size*0.7071,0.0],p3=[0.0,_default_size,0.0],center=None,name=None): - if(not isinstance(p1,list) and not isinstance(p1,tuple)): - p1 = [_default_size,0.0,0.0] - if(not isinstance(p2,list) and not isinstance(p2,tuple)): - p2 = [_default_size*0.7071,_default_size*0.7071,0.0] - if(not isinstance(p3,list) and not isinstance(p3,tuple)): - p3 = [0.0,_default_size,0.0] - p1 = ( float(p1[0]) if len(p1)>0 else 0.0, float(p1[1]) if len(p1)>1 else 0.0, float(p1[2]) if len(p1)>2 else 0.0 ) - p2 = ( float(p2[0]) if len(p2)>0 else 0.0, float(p2[1]) if len(p2)>1 else 0.0, float(p2[2]) if len(p2)>2 else 0.0 ) - p3 = ( float(p3[0]) if len(p3)>0 else 0.0, float(p3[1]) if len(p3)>1 else 0.0, float(p3[2]) if len(p3)>2 else 0.0 ) - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "arc_"+str(_idx_EasyNode) - else: - node.name = name - def createArc(node, p1,p2,p3,center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.obj.Shape = Part.Shape([Part.Arc(Base.Vector(p1),Base.Vector(p2),Base.Vector(p3))]) - node.centerx = (p1[0]+p3[0])/2.0 - node.centery = (p1[1]+p3[1])/2.0 - node.centerz = (p1[2]+p3[2])/2.0 - useCenter(node, center) - node.addAction(createArc, (node, p1,p2,p3,center)) - return node - -def text(text="Hello", size=_default_size, font="arial.ttf",center=None,name=None): - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "text_"+str(_idx_EasyNode) - else: - node.name = name - def createText(node, text,size,font,center): - node.obj = Draft.makeShapeString(String=text,FontFile=font,Size=size,Tracking=0) - node.obj.Label = node.name - # temp_poly = Draft.makeShapeString(String=text,FontFile=font,Size=size,Tracking=0) - # node.obj.Shape = temp_poly - node.centerx = 0.0 - node.centery = 0.0 - useCenter(node, center) - node.addAction(createText, (node, text,size,font,center)) - return node - -def gear(nb=6, mod=2.5, angle=20.0, external=True, high_precision=False,name=None): - global _idx_EasyNode - node = EasyNode2D() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "gear_"+str(_idx_EasyNode) - else: - node.name = name - nb = int(nb) - def createGear(node, nb,mod,angle,center,high_precision,external): - gear = InvoluteGearFeature.makeInvoluteGear(node.name) - gear.NumberOfTeeth = nb - gear.Modules = str(mod)+' mm' - gear.HighPrecision = high_precision - gear.ExternalGear = external - gear.PressureAngle = angle - node.obj = gear - node.centerx = 0.0 #TODO - node.centery = 0.0 - node.addAction(createGear, (node, nb,mod,angle,center,high_precision,external)) - return node; - -######### Extrusion (2D to 3D) ######### - -class EasyNodeLinear(EasyNodeColored): - def __init__(self): - self.edges = []; - self.childs = [] - self.actions = [] - self.actions_after = [] - self.simple = True - def layout_childs(self, *tupleOfNodes): - if(len(tupleOfNodes)>=1): - self.obj.Base = tupleOfNodes[0].obj - if self.obj.Base.ViewObject is not None: - self.obj.Base.ViewObject.Visibility = False - return self - -def linear_extrude(height=_default_size,center=None,name=None, twist=0, taper=0.0, slices=0,convexity=0): - global _idx_EasyNode - node = EasyNodeLinear() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "lin_extrude_"+str(_idx_EasyNode) - else: - node.name = name - if(twist!=0): - #if twist, we have to create a sweep against an helix but we don't know the parameters of the helix yet - #so we add an action to the sweep node to create the helix and layout the thing - pitch = height * 360.0/abs(twist) - print("pitch="+str(pitch)+", h="+str(height)) - extruder = path_extrude(frenet=True,name=node.name) - def create_helix(node, height, pitch, twist, nameNode): - if(len(node.childs)>0): - pos = node.childs[0].obj.Placement.Base - zpos = pos.z - pos.z = 0.0 - radius = pos.Length - anglerad = math.atan2(pos.x,pos.y) - helix_obj = helix(r=radius,p=pitch,h=height, name=nameNode+"_twist").rotate(0,0,anglerad*180.0/math.pi) - if(twist>0): - helix_obj = mirror(0,1,0, name=nameNode+"_twist_m")(helix_obj) - node.childs.insert(0, helix_obj) - helix_obj.create() - node.layout_childs(*tuple(node.childs)) - extruder.actions_after.append((create_helix, (extruder, height, pitch, twist, node.name))) - return extruder - def createZExtrude(node, height, taper, center): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Extrusion", node.name) - node.obj.DirMode = "Normal" - node.obj.TaperAngle = taper*180.0/math.pi - node.obj.LengthFwd = height - node.obj.Solid = True - node.centerx=0.0 - node.centery=0.0 - node.centerz=height/2.0 - useCenter(node, center) - node.addAction(createZExtrude, (node, height, taper, center)) - return node - -def extrude(x=0.0,y=0.0,z=0.0, taper=0.0,name=None,convexity=0): - (x,y,z) = _getTuple3Abs(x,y,z,0.0) - normal = Base.Vector(x,y,z); - length = normal.Length - global _idx_EasyNode - node = EasyNodeLinear() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "extrude_"+str(_idx_EasyNode) - else: - node.name = name - def createExtrude(node, x,y,z,taper): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Extrusion", node.name) - node.obj.DirMode = "Custom" - node.obj.LengthFwd = length - node.obj.TaperAngle = taper - node.obj.Dir = normal - node.obj.Solid = True - node.addAction(createExtrude, (node, x,y,z,taper)) - return node - -class EasyNodeRotateExtrude(EasyNodeColored): - def layout_childs(self, *tupleOfNodes): - if(len(tupleOfNodes)>=1): - self.obj.Source = tupleOfNodes[0].obj - if self.obj.Source.ViewObject is not None: - self.obj.Source.ViewObject.Visibility = False - return self - -def rotate_extrude(angle=360.0,name=None,convexity=2): - angle = min(abs(angle),360.0) - global _idx_EasyNode - node = EasyNodeRotateExtrude() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "revolution_"+str(_idx_EasyNode) - else: - node.name = name - def createRExtrude(node, angle): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Revolution", node.name) - node.obj.Axis = Base.Vector(0.0,_default_size,0.0) - node.obj.Base = Base.Vector(0.0,0.0,0.0) - node.obj.Angle = float(angle) - node.obj.Solid = True - node.obj.AxisLink = None - node.obj.Symmetric = False - node.rotate(90,0,0) - node.addAction(createRExtrude, (node, angle)) - return node - -class EasyNodeSweep(EasyNode): - base = None - def layout_childs(self, *tupleOfNodes): - if len(tupleOfNodes) == 1 and self.base == None: - self.base = tupleOfNodes[0] - return self - base = None - tool = None - if len(tupleOfNodes) == 2 : - base = tupleOfNodes[0] - tool = tupleOfNodes[1] - elif len(tupleOfNodes) > 2 : - base = tupleOfNodes[0] - tool = tupleOfNodes[1] - elif len(tupleOfNodes) == 1: - base = self.base - tool = tupleOfNodes[0] - else: - # print("Error, wrong number of sweep elements : "+str(len(self.childs)) +" and we need 2") - return self - self.obj.Sections = [tool.obj] - arrayEdge = [] - i=1 - for edge in base.obj.Shape.Edges: - arrayEdge.append("Edge"+str(i)) - i += 1 - self.obj.Spine = (base.obj,arrayEdge) - if base.obj.ViewObject is not None: - base.obj.ViewObject.Visibility = False - if tool.obj.ViewObject is not None: - tool.obj.ViewObject.Visibility = False - return self - -#transition in ["Right corner", "Round corner","Transformed" ] -def path_extrude(frenet=True, transition = "Right corner",name=None): - global _idx_EasyNode - node = EasyNodeSweep() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "sweep_"+str(_idx_EasyNode) - else: - node.name = name - def createPExtrude(node, frenet,transition): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Sweep", node.name) - node.obj.Solid = True - node.obj.Frenet = frenet - node.obj.Transition = transition - node.addAction(createPExtrude, (node, frenet,transition)) - return node - -class EasyNodeAssembleWire(EasyNode): - def layout_childs(self, *tupleOfNodes): - edges = [] - arrayObjChilds = [] - for node in tupleOfNodes: - arrayObjChilds.append(node.obj) - if node.obj.ViewObject is not None: - node.obj.ViewObject.Visibility = False - for edge in node.obj.Shape.Edges: - edges.append(Part.Edge(edge)) - #put childs into a group - obj_group = FreeCAD.ActiveDocument.addObject("App::DocumentObjectGroup", self.name+"_components") - obj_group.Group = arrayObjChilds - if obj_group.ViewObject is not None: - obj_group.ViewObject.Visibility = False - #create wire - if(self.closed): - self.obj.Shape = Part.Face(Part.Wire(edges)) - else: - self.obj.Shape = Part.Wire(edges) - return self - -#transition in ["Right corner", "Round corner","Transformed" ] -def create_wire(closed=False, name=None): - global _idx_EasyNode - node = EasyNodeAssembleWire() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "assemble_"+str(_idx_EasyNode) - else: - node.name = name - def createWire(node): - node.obj = FreeCAD.ActiveDocument.addObject("Part::Feature", node.name) - node.addAction(createWire, (node,)) - node.closed = closed - return node - -######### convenience objects for chaining ######## - -center = EasyNode.center -center_x = EasyNode.x -center_y = EasyNode.y -center_z = EasyNode.z -center_xy = EasyNode.xy -center_xz = EasyNode.xz -center_yz = EasyNode.yz -center_xyz = EasyNode.xyz - -class ApplyNodeFunc(): - def __init__(self, func, args): - self.args = args - self.func = func - self.before = [] - def __call__(self, *nodes): - print(nodes[0]) - goodnodes = [] - if len(self.before)>0: - goodnodes.append(self.before.pop()(*nodes)) - while len(self.before)>0: - goodnodes[0] = self.before.pop()(goodnodes[0]) - else: - for node in nodes: - if(isinstance(node, EasyNode)): - goodnodes.append(node) - if(len(goodnodes)==0): - print("Error, use a function on no node ") - return self - elif(len(goodnodes)==1): - print("do(1) "+self.func.__name__) - if(len(self.args)==2): - self.func(goodnodes[0],self.args[0],self.args[1]) - elif(len(self.args)==3): - self.func(goodnodes[0],self.args[0],self.args[1],self.args[2]) - elif(len(self.args)==4): - self.func(goodnodes[0],self.args[0],self.args[1],self.args[2],self.args[3]) - return goodnodes[0] - else: - print("do(union) "+self.func.__name__) - union_obj = union(goodnodes) - if(len(self.args)==2): - self.func(union_obj,self.args[0],self.args[1]) - elif(len(self.args)==3): - self.func(union_obj,self.args[0],self.args[1],self.args[2]) - elif(len(self.args)==4): - self.func(union_obj,self.args[0],self.args[1],self.args[2],self.args[3]) - return union_obj - #also redefine every function to do the same, - # it avoid the user to type '(' and ')' and replace it with '.' - def translate(self, x=0.0,y=0.0,z=0.0): - self.before.append(ApplyNodeFunc(EasyNode.move, (x,y,z))) - return self - def move(self, x=0.0,y=0.0,z=0.0): - self.before.append(ApplyNodeFunc(EasyNode.move, (x,y,z))) - return self - def rotate(self, x=0.0,y=0.0,z=0.0): - self.before.append(ApplyNodeFunc(EasyNode.rotate, (x,y,z))) - return self - def rotate2D(self, x=0.0,y=0.0): - self.before.append(ApplyNodeFunc(EasyNode2D.rotate2D, (x,y))) - return self - def scale(self, x=0.0,y=0.0,z=0.0): - self.before.append(ApplyNodeFunc(EasyNode.scale, (x,y,z))) - return self - def color(self, r=0.0,v=0.0,b=0,a=0.0): - self.before.append(ApplyNodeFunc(magic_color, (r,v,b,a))) - return self - def union(self,name=None): - return self(union(name)) - def inter(self,name=None): - return self(inter(name)) - def intersection(self,name=None): - return self(inter(name)) - def cut(self,name=None): - return self(cut(name)) - def difference(self,name=None): - return self(cut(name)) - def chamfer(self,name=None): - return self(chamfer(name)) - def fillet(self,name=None): - return self(fillet(name)) - def mirror(self,x=0.0,y=0.0,z=0.0,name=None): - return self(mirror(x,y,z,name)) - def offset(self,length=0.0,fillet=True,fusion=True,name=None): - return self(offset(length=length,fillet=fillet,fusion=fusion,name=name)) - def offset2D(self,length=0.0,fillet=True,fusion=True,name=None): - return self(offset2D(length=length,fillet=fillet,fusion=fusion,name=name)) - def group(self,name=None): - return self(group(name)) - def cube(self,size=0.0,y=0.0,z=0.0,center=None,x=0.0, name=None): - return self(cube(size,y,z,center,x, name)) - def box(self,x=_default_size,y=_default_size,z=_default_size,center=None, name=None): - return self(box(x,y,z,center, name)) - def tri_rect(self,x=_default_size,y=_default_size,z=_default_size,center=None, name=None): - return self(tri_rect(x,y,z,center, name)) - def cylinder(self,r=0.0,h=_default_size,center=None,d=0.0,r1=0.0,r2=0.0,d1=0.0,d2=0.0,angle=360.0,fn=1,name=None): - return self(cylinder(r,h,center,d,r1,r2,d1,d2,angle,fn,name)) - def cone(self,r1=_default_size,r2=_default_size,h=_default_size,center=None,d1=0.0,d2=0.0,fn=1,name=None): - return self(cone(r1,r2,h,center,d1,d2,fn,name)) - def sphere(self,r=_default_size,center=None,d=0.0,fn=1,name=None): - return self(sphere(r,center,d,fn,name)) - def torus(self,r1=_default_size, r2=0.1,center=None,d1=0.0,d2=0.0,name=None): - return self(torus(r1, r2,center,d1,d2,name)) - def poly_ext(self,r=_default_size, nb=3, h=_default_size,center=None,d=0.0,name=None): - return self(poly_ext(r, nb, h,center,d,name)) - def polyhedron(self,points=[],faces=[],center=None,name=None): - return self(polyhedron(points, faces,center,name)) - def polyhedron_wip(self,points=[],faces=[],center=None,name=None): - return self(polyhedron_wip(points, faces,center,name)) - def solid_slices(self,points=[],centers=[],center=None,name=None): - return self(solid_slices(points, centers,center,name)) - def createPolyExt(self,node, r,nb,h,center): - return self(createPolyExt(node, r,nb,h,center)) - def poly_int(self,a=_default_size, nb=3, h=_default_size,center=None,d=0.0,name=None): - return self(poly_int(a, nb, h,center,d,name)) - def circle(self,r=_default_size,center=None,d=0.0,fn=1,name=None): - return self(circle(r,center,d,fn,name)) - def ellipse(self,r1=0.0,r2=0.0,center=None,d1=0.0,d2=0.0,name=None): - return self(ellipse(r1,r2,center,d1,d2,name)) - def poly_reg(self,r=_default_size,nb=3,center=None,inscr=True,d=0.0,name=None): - return self(poly_reg(r,nb,center,inscr,d,name)) - def square(self,size=_default_size,y=0.0,x=0.0,center=None,name=None): - return self(square(size,y,x,center,name)) - def rectangle(self,x=_default_size,y=_default_size,center=None,name=None): - return self(rectangle(x,y,center,name)) - def polygon(self,points=[], closed=True,name=None): - return self(polygon(points, closed,name)) - def bspline(self,points=[], closed=False,name=None): - return self(bspline(points, closed,name)) - def bezier(self,points=[], closed=False,name=None): - return self(bezier(points, closed,name)) - def helix(self,r=_default_size,p=_default_size,h=_default_size,center=None,d=0.0,name=None): - return self(helix(r,p,h,center,d,name)) - def gear(self,nb=6, mod=2.5, angle=20.0, external=True, high_precision=False,name=None): - return self(gear(nb, mod, angle, external, high_precision,name)) - def line(self,p1=[0.0,0.0,0.0],p2=[_default_size,_default_size,_default_size],center=None,name=None): - return self(line(p1,p2,center,name)) - def text(self,text="Hello", size=_default_size, font="arial.ttf",center=None,name=None): - return self(text(text, size, font,center,name)) - def linear_extrude(self,length=_default_size, angle=0.0): - return self(linear_extrude(length, angle)) - def extrude(self,x=0.0,y=0.0,z=0.0, angle=0.0,name=None): - return self(linear_extrude(x,y,z, angle,name)) - def rotate_extrude(self,angle=360.0,name=None): - return self(rotate_extrude(angle,name)) - def path_extrude(self,frenet=True, transition = "Right corner",name=None): - return self(path_extrude(frenet,transition,name)) - def create_wire(self,name=None): - return self(create_wire(name)) - def importSvg(self,filename="./None.svg", ids=[],name=None): - return self(importSvg(filename,ids,name)) - def importStl(self,filename="./None.stl", ids=[],name=None): - return self(importStl(filename,ids,name)) - -def translate(x=0.0,y=0.0,z=0.0): - return ApplyNodeFunc(EasyNode.move, (x,y,z)) - -def move(x=0.0,y=0.0,z=0.0): - return ApplyNodeFunc(EasyNode.move, (x,y,z)) - -def rotate(x=0.0,y=0.0,z=0.0): - return ApplyNodeFunc(EasyNode.rotate, (x,y,z)) - -def scale(x=0.0,y=0.0,z=0.0): - return ApplyNodeFunc(EasyNode.scale, (x,y,z)) - -def color(r=0.0,v=0.0,b=0,a=0.0): - return ApplyNodeFunc(magic_color, (r,v,b,a)) - -######### import & export ######### - -def importSvg(filename="./None.svg",ids=[],name=None): - global _idx_EasyNode - node = EasyNode() - _idx_EasyNode += 1 - if(name == None or not isinstance(name, str)): - node.name = "svg_"+str(_idx_EasyNode) - else: - node.name = name - node.simple= False - def importSvg_action(filename,ids=[]): - objects_before = FreeCAD.ActiveDocument.Objects - importSVG.insert(filename,FreeCAD.ActiveDocument.Name) - objects_inserted = FreeCAD.ActiveDocument.Objects - for obj in objects_before: - objects_inserted.remove(obj) - if(len(ids)==0): - if(len(objects_inserted)==1): - node.obj = objects_inserted[0] - else: - node.obj = FreeCAD.ActiveDocument.addObject("Part::MultiFuse", "union_"+str(_idx_EasyNode)) - for obj in objects_inserted: - node(_easyNodeStub(obj,"svg")) - else: - to_unionise = [] - for idx in ids: - if(idx @@ -14,9 +15,11 @@ //C++11 +#include #include #include -#include +#include +#include #include #include #include @@ -34,6 +37,62 @@ static wxSize get_screen_size(wxWindow* window) namespace Slic3r { namespace GUI { + //TODO: auto tab + + // Downloads a file (http get operation). Cancels if the Updater is being destroyed. + bool get_file_from_web(const std::string &url, const boost::filesystem::path &target_path) + { + bool res = false; + boost::filesystem::path tmp_path = target_path; + tmp_path += (boost::format(".%1%%2%") % get_current_pid() % ".download").str(); + + BOOST_LOG_TRIVIAL(info) << boost::format("Get: `%1%`\n\t-> `%2%`\n\tvia tmp path `%3%`") + % url + % target_path.string() + % tmp_path.string(); + + Slic3r::Http::get(url) + .on_progress([](Http::Progress, bool &cancel) { + }) + .on_error([&](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % url + % http_status + % error; + }) + .on_complete([&](std::string body, unsigned /* http_status */) { + boost::filesystem::fstream file(tmp_path, std::ios::out | std::ios::binary | std::ios::trunc); + file.write(body.c_str(), body.size()); + file.close(); + boost::filesystem::rename(tmp_path, target_path); + res = true; + }) + .perform_sync(); + + return res; + } + + // Downloads a file (http get operation). Cancels if the Updater is being destroyed. + void get_string_from_web_async(const std::string &url, FreeCADDialog* free, std::function listener) + { + + Slic3r::Http::get(url) + .on_progress([](Http::Progress, bool &cancel) { + }) + .on_error([&url](std::string body, std::string error, unsigned http_status) { + (void)body; + BOOST_LOG_TRIVIAL(error) << boost::format("Error getting: `%1%`: HTTP %2%, %3%") + % url + % http_status + % error; + }) + .on_complete([free, listener](std::string body, unsigned http_status ) { + listener(free, body); + }) + .perform(); + } + std::string create_help_text() { std::stringstream ss; @@ -75,7 +134,7 @@ std::string create_help_text() { } FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe) - : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("FreeCAD script engine")), + : DPIDialog(NULL, wxID_ANY, wxString(SLIC3R_APP_NAME) + " - " + _(L("FreePySCAD : script engine for FreeCAD")), #if ENABLE_SCROLLABLE wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) #else @@ -87,13 +146,76 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe) this->main_frame = mainframe; SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW)); + //def + PyCommand py1{ "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" }; + PyCommand py2 = { "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" }; + commands.emplace_back(PyCommand{ "cube", PyCommandType::pctOBJECT, {"x","y","z"}, "cube(x,y,z)\ncube(l)" }); + commands.emplace_back(PyCommand{"cylinder", PyCommandType::pctOBJECT, { "r","h","fn=","angle=","d=","r1=","r2=","d1=","d2=" }, + "cylinder(r,h)\ncylinder(d=,h=,[fn=,angle=])\ncylinder(r1=,r2=,h=)\ncylinder(d1=,d2=,h=)"}); + commands.emplace_back(PyCommand{"move", PyCommandType::pctMODIFIER, { "x","y","z" }, "move(x,y,z)"}); + commands.emplace_back(PyCommand{"rotate", PyCommandType::pctMODIFIER, { "x","y","z" }, "rotate(x,y,z)"}); + commands.emplace_back(PyCommand{"cut", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER,"cut()(...obj)"}); + commands.emplace_back(PyCommand{"union", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "union()(...obj)"}); + commands.emplace_back(PyCommand{"intersection", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "intersection()(...obj)"}); + commands.emplace_back(PyCommand{"linear_extrude", PyCommandType::pctOPERATION,"linear_extrude(height,[twist=,taper=,slices=,convexity=])(2D_obj)"}); + commands.emplace_back(PyCommand{"rotate_extrude", PyCommandType::pctOPERATION, "rotate_extrude(angle,[convexity])(2D_obj)"}); + commands.emplace_back(PyCommand{"path_extrude", PyCommandType::pctOPERATION, "path_extrude(frenet,transition)(2D_obj)"}); + commands.emplace_back(PyCommand{"mirror", PyCommandType::pctOPERATION, { "x","y","z" }, "mirror(x,y,z)(obj)"}); + commands.emplace_back(PyCommand{"offset", PyCommandType::pctOPERATION, { "length","fillet" }, "offset(length,fillet)(...obj)"}); + commands.emplace_back(PyCommand{"chamfer", PyCommandType::pctOPERATION, { "l" }, "chamfer(l)(...obj)"}); + commands.emplace_back(PyCommand{"fillet", PyCommandType::pctOPERATION, { "l" }, "fillet(l)(...obj)"}); + commands.emplace_back(PyCommand{"poly_ext", PyCommandType::pctOBJECT, {"r", "nb", "h", "d="}, "poly_ext(r,nb,h)\npoly_ext(d=,nb=,h=)"}); + commands.emplace_back(PyCommand{"poly_int", PyCommandType::pctOBJECT, { "a", "nb", "h", "d=" }, "poly_int(a,nb,h)\npoly_int(d=,nb=,h=)"}); + commands.emplace_back(PyCommand{"triangle", PyCommandType::pctOBJECT, { "x","y","z" }, "triangle(x,y,z)"}); + commands.emplace_back(PyCommand{ "iso_thread", PyCommandType::pctOBJECT, {"d","p","h","internal","offset","fn="}, + "iso_thread(d,p,h,internal, offset,[fn=])\nm3 screw: iso_thread(3,0.5,10,False,0)\nm3 nut: cut()(...,iso_thread(3,0.5,3,True,0.15))" }); + commands.emplace_back(PyCommand{"text", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"gear", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"importStl", PyCommandType::pctOBJECT, "importStl(filename,ids)"}); + commands.emplace_back(PyCommand{"solid_slices", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"create_wire", PyCommandType::pctOPERATION, { "closed" }, "create_wire(closed)(...1D_obj)"}); + commands.emplace_back(PyCommand{"line", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"arc", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"circle", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"polygon", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"bezier", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"square", PyCommandType::pctOBJECT}); + commands.emplace_back(PyCommand{"importSvg", PyCommandType::pctOBJECT, "importSvg(filename,ids)"}); + commands.emplace_back(PyCommand{"xy", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"z", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"center", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"x", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"y", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"xz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"yz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + commands.emplace_back(PyCommand{"xyz", PyCommandType::pctMODIFIER | PyCommandType::pctNO_PARAMETER}); + // alias + commands.emplace_back(PyCommand{"difference", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, "difference()(...obj)"}); + commands.emplace_back(PyCommand{"translate", PyCommandType::pctMODIFIER, "translate(x,y,z)"}); + commands.emplace_back(PyCommand{"extrude", PyCommandType::pctOPERATION, "extrude(x,y,z,taper,[convexity=])"}); + //redraw + commands.emplace_back(PyCommand{"redraw", PyCommandType::pctOPERATION | PyCommandType::pctNO_PARAMETER, + "redraw(...obj3D)\nEvery object inside this command\nwill be added into SuperSlicer.\n"}); + // beta / buggy + commands.emplace_back(PyCommand{"scale", PyCommandType::pctMODIFIER | PyCommandType::pctDO_NOT_SHOW}); + + word_regex = std::regex("[a-z]+"); + // fonts const wxFont& font = wxGetApp().normal_font(); const wxFont& bold_font = wxGetApp().bold_font(); SetFont(font); + wxIcon *freecad_icon = new wxIcon(); + freecad_icon->LoadFile( (boost::filesystem::path(Slic3r::resources_dir()) / "icons" / "Freecad.svg").generic_string()); + //sadly, this do nothing in dialog + //this->SetIcon(freecad_icon); - auto main_sizer = new wxGridBagSizer(5,5); //(int rows, int cols, int vgap, int hgap) + auto main_sizer = new wxGridBagSizer(1,1); //(int vgap, int hgap) + // | |_icon_| + // |editor_| help | + // |_err___|______| + // |__bts_________| //main view createSTC(); @@ -106,20 +228,35 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe) wxDefaultPosition, wxSize(200, 500), wxTE_MULTILINE); m_help->SetEditable(false); + //wxBoxSizer *m_icons = new wxBoxSizer(wxHORIZONTAL); + //m_icons->Add(16,16,0,0,0,freecad_icon); + //wxSizerItem* Add(wxSizer * sizer, const wxGBPosition & pos, const wxGBSpan & span = wxDefaultSpan, int flag = 0, int border = 0, wxObject * userData = NULL) //wxSizerItem* Add(wxSizer * sizer, int proportion = 0, int flag = 0, int border = 0, wxObject * userData = NULL) - main_sizer->Add(m_text, wxGBPosition(1,1), wxGBSpan(1,1), wxEXPAND | wxALL, 5); - main_sizer->Add(m_help, wxGBPosition(1, 2), wxGBSpan(2, 1), wxEXPAND | wxVERTICAL, 5); - main_sizer->Add(m_errors, wxGBPosition(2, 1), wxGBSpan(1, 1), 0, 5); - /*main_sizer->Add(m_text, 1, wxEXPAND | wxALL, 5); - main_sizer->Add(m_errors, 1, wxALL, 5); - main_sizer->Add(m_help, 1, wxALL, 5);*/ - + main_sizer->Add(m_text, wxGBPosition(1,1), wxGBSpan(2,1), wxEXPAND | wxALL, 2); + //main_sizer->Add(m_icons, wxGBPosition(1, 2), wxGBSpan(1, 1), 0, 2); + //main_sizer->Add(m_help, wxGBPosition(2, 2), wxGBSpan(2, 1), wxEXPAND | wxVERTICAL, 2); + main_sizer->Add(m_help, wxGBPosition(1, 2), wxGBSpan(3, 1), wxEXPAND | wxVERTICAL, 2); + main_sizer->Add(m_errors, wxGBPosition(3, 1), wxGBSpan(1, 1), 0, 2); + wxStdDialogButtonSizer* buttons = new wxStdDialogButtonSizer(); - wxButton* bt = new wxButton(this, wxID_FILE1, _(L("Generate"))); - bt->Bind(wxEVT_BUTTON, &FreeCADDialog::create_geometry, this); - buttons->Add(bt); + wxButton* bt_create_geometry = new wxButton(this, wxID_FILE1, _(L("Generate"))); + bt_create_geometry->Bind(wxEVT_BUTTON, &FreeCADDialog::create_geometry, this); + buttons->Add(bt_create_geometry); + wxButton* bt_new = new wxButton(this, wxID_FILE3, _(L("New"))); + bt_new->Bind(wxEVT_BUTTON, &FreeCADDialog::new_script, this); + buttons->Add(bt_new); + wxButton* bt_load = new wxButton(this, wxID_FILE2, _(L("Load"))); + bt_load->Bind(wxEVT_BUTTON, &FreeCADDialog::load_script, this); + buttons->Add(bt_load); + wxButton* bt_save = new wxButton(this, wxID_FILE3, _(L("Save"))); + bt_save->Bind(wxEVT_BUTTON, &FreeCADDialog::save_script, this); + buttons->Add(bt_save); + wxButton* bt_quick_save = new wxButton(this, wxID_FILE4, _(L("Quick Save"))); + bt_quick_save->Bind(wxEVT_BUTTON, &FreeCADDialog::quick_save, this); + bt_quick_save->Hide(); + buttons->Add(bt_quick_save); wxButton* close = new wxButton(this, wxID_CLOSE, _(L("Close"))); close->Bind(wxEVT_BUTTON, &FreeCADDialog::closeMe, this); @@ -128,19 +265,318 @@ FreeCADDialog::FreeCADDialog(GUI_App* app, MainFrame* mainframe) close->SetFocus(); SetAffirmativeId(wxID_CLOSE); buttons->Realize(); - main_sizer->Add(buttons, wxGBPosition(3, 1), wxGBSpan(1, 2), wxEXPAND | wxALL, 5); + main_sizer->Add(buttons, wxGBPosition(4, 1), wxGBSpan(1, 2), wxEXPAND | wxALL, 5); SetSizer(main_sizer); main_sizer->SetSizeHints(this); + //set keyboard shortcut + wxAcceleratorEntry entries[2]; + //entries[0].Set(wxACCEL_CTRL, (int) 'X', bt_create_geometry->GetId()); + //entries[2].Set(wxACCEL_SHIFT, (int) 'W', wxID_FILE1); + entries[0].Set(wxACCEL_NORMAL, WXK_ESCAPE, wxID_CLOSE); + entries[1].Set(wxACCEL_NORMAL, WXK_F5, bt_create_geometry->GetId()); // wxID_FILE1); + entries[1].Set(wxACCEL_CTRL, (int) 'N', bt_new->GetId()); + entries[1].Set(wxACCEL_CTRL | wxACCEL_SHIFT, (int) 'S', bt_save->GetId()); + entries[1].Set(wxACCEL_CTRL, (int) 'S', bt_quick_save->GetId()); + this->SetAcceleratorTable(wxAcceleratorTable(2, entries)); + } void FreeCADDialog::closeMe(wxCommandEvent& event_args) { + bool ok = this->write_text_in_file(m_text->GetText(), boost::filesystem::path(Slic3r::data_dir()) / "temp" / "current_pyscad.py"); this->gui_app->change_calibration_dialog(this, nullptr); this->Destroy(); } +void FreeCADDialog::load_script(wxCommandEvent& event_args) { + wxFileDialog dialog(this, + _(L("Choose one file (py):")), + gui_app->app_config->get_last_dir(), + "", + "FreePySCAD files (*.py)|*.py", + wxFD_OPEN | wxFD_FILE_MUST_EXIST); + if (dialog.ShowModal() == wxID_OK) { + opened_file = boost::filesystem::path(dialog.GetPath().wx_str()); + load_text_from_file(opened_file); + } +} + +bool FreeCADDialog::load_text_from_file(const boost::filesystem::path &path) { + if (boost::filesystem::exists(path)) { + try { + std::locale loc = boost::locale::generator()("en_US.UTF-8"); + // Open the stream to 'lock' the file. + boost::filesystem::ifstream in; + in.imbue(loc); + in.open(path); + // Obtain the size of the file. + const uintmax_t sz = boost::filesystem::file_size(path); + // Create a buffer. + std::string result(sz, '\0'); + // Read the whole file into the buffer. + in.read(result.data(), sz); + m_text->SetTextRaw(result.c_str()); + in.close(); + } + catch (std::exception ex) { + //TODO: emit error meessage on std / boost + return false; + } + return true; + } + return false; +} + +void FreeCADDialog::save_script(wxCommandEvent& event_args) { + wxFileDialog dialog(this, + _(L("Choose one file (py):")), + gui_app->app_config->get_last_dir(), + "", + "FreePySCAD files (*.py)|*.py", + wxFD_SAVE); + + if (dialog.ShowModal() == wxID_OK) { + opened_file = boost::filesystem::path(dialog.GetPath().wx_str()); + write_text_in_file(m_text->GetText(), opened_file); + } +} +void FreeCADDialog::quick_save(wxCommandEvent& event_args) { + + if (boost::filesystem::exists(opened_file) ){ + write_text_in_file(m_text->GetText(), opened_file); + } +} + +bool FreeCADDialog::write_text_in_file(const wxString &towrite, const boost::filesystem::path &file) { + try { + //add text if the saved file exist + boost::filesystem::create_directories(file.parent_path()); + std::locale loc = boost::locale::generator()("en_US.UTF-8"); + // Open the stream to 'lock' the file. + boost::filesystem::ofstream out; + out.imbue(loc); + out.open(file); + out << towrite; + out.close(); + } + catch (std::exception ex) { + return false; + } + return true; +} + +const PyCommand* FreeCADDialog::get_command(const wxString &str) const { + const PyCommand *command = nullptr; + for (const PyCommand &cmd : commands) { + if (cmd.name == str) { + command = &cmd; + break; + } + } + return command; +} + +void FreeCADDialog::on_autocomp_complete(wxStyledTextEvent& event) { + //ad extra characters after autocomplete + const PyCommand *command = get_command(event.GetString()); + if (command == nullptr) + return; + wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject(); + int currentPos = stc->GetCurrentPos(); + bool has_already_parenthese = stc->GetCharAt(currentPos) == '('; + if ( ((command->type & PyCommandType::pctOPERATION) != 0) && !has_already_parenthese) { + stc->InsertText(currentPos, "()(),"); + if(((command->type & PyCommandType::pctNO_PARAMETER) != 0)) + stc->GotoPos(currentPos + 3); + else + stc->GotoPos(currentPos + 1); + } else if (((command->type & PyCommandType::pctOBJECT) != 0) && !has_already_parenthese) { + stc->InsertText(currentPos, "(),"); + if (((command->type & PyCommandType::pctNO_PARAMETER) != 0)) + stc->GotoPos(currentPos + 2); + else + stc->GotoPos(currentPos + 1); + } else if (((command->type & PyCommandType::pctMODIFIER) != 0) && !has_already_parenthese) { + stc->InsertText(currentPos, "()"); + if (((command->type & PyCommandType::pctNO_PARAMETER) != 0)) + stc->GotoPos(currentPos + 2); + else + stc->GotoPos(currentPos + 1); + //TODO: check if it's a object.modif(), a modif()(object) a modif().object ? + } + if (!command->tooltip.empty()) + stc->CallTipShow(currentPos, command->tooltip); +} + +void FreeCADDialog::on_word_change_for_autocomplete(wxStyledTextEvent& event) { + wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject(); + // Find the word start + int current_pos = stc->GetCurrentPos(); + int word_start_pos = stc->WordStartPosition(current_pos, true); + int len_entered = current_pos - word_start_pos; + const wxString str = stc->GetTextRange(word_start_pos, current_pos + 1); + const wxString event_string = event.GetString(); + if ((event.GetModificationType() & (wxSTC_MOD_INSERTTEXT | wxSTC_PERFORMED_USER)) != (wxSTC_MOD_INSERTTEXT | wxSTC_PERFORMED_USER)) { + return; // not our event + } + //std::cout << "word_change "<GetCharAt(current_pos) << " with len_entered " << len_entered + //<< ", event_string='"<< event_string << "' (last='"<< (event_string.empty()?-1:event_string.Last().GetValue()) <<"') ; Str is '" << str << "' with length " << str.length() + // << "' chars are (c-1)='" << stc->GetCharAt(current_pos - 1) << "' : (c)='" << stc->GetCharAt(current_pos) + //<< "', text length=" << stc->GetTextLength() << ", currentPos=" << current_pos << " , " << int('\n') << "\n"; + //std::cout << "test: " << (!(stc->GetCharAt(current_pos - 1) <= '\n')) << ", 2=" << (len_entered >= 0) << ", 3=" << (!str.empty()) + // << ", 4=" << (std::regex_match(str.ToStdString(), word_regex)) + // <<", Mod5="<<((event.GetModificationType() & wxSTC_STARTACTION) != 0) + // <<", 6="<< (current_pos <= 1 || str != ".")<<", 6b="<< (str == ".") + // << "\n"; + + if ((event.GetModificationType() & wxSTC_STARTACTION) != 0 && (str.empty() || str.Last() != '.')) + return; + //if (!event_string.empty() && !str.empty() && int(str[str.length() - 1]) != event_string.Last().GetValue()) std::cout << "removecall?\n"; + if (len_entered >= 0 && !str.empty() && std::regex_match(str.ToStdString(), word_regex)) { + //check for possible words + //todo: check for '.' to filter for modifiers + int nb_words = 0; + wxString possible; + for (const PyCommand &cmd : commands) { + if (cmd.name == str) return; + if (cmd.name.StartsWith(str) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0) ) { + nb_words++; possible += possible.empty() ? cmd.name : (" " + cmd.name); + } + } + // Display the autocompletion list + if (nb_words >= 1) + stc->AutoCompShow(len_entered, possible); + } else if (!str.empty() && str.Last() == '.') { + //wxString possible; + //for(const wxString &str : modif_words) + // possible += possible.empty() ? str : (" " + str); + wxString possible; + for (const PyCommand &cmd : commands) { + if (((cmd.type & PyCommandType::pctMODIFIER) != 0) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) { + possible += possible.empty() ? cmd.name : (" " + cmd.name); + } + } + //std::cout << "autocomplete: modifier: '"<< possible.ToStdString() <<"'\n"; + if (possible.length() >= 1) + stc->AutoCompShow(0, possible); + } +} + +void FreeCADDialog::on_char_add(wxStyledTextEvent& event) { + //if (event.GetUpdated() != wxSTC_UPDATE_CONTENT) return; + wxStyledTextCtrl* stc = (wxStyledTextCtrl*)event.GetEventObject(); + // Find the word start + int current_pos = stc->GetCurrentPos(); + int word_start_pos = stc->WordStartPosition(current_pos, true); + int len_entered = current_pos - word_start_pos; + const wxString str = stc->GetTextRange(word_start_pos, current_pos + 1); + //if(current_pos>1) + // std::cout << "char typed: " << (char)stc->GetCharAt(current_pos)<<" with length "<< len_entered + // << ", str is "<< str << "chars are '"<< stc->GetCharAt(current_pos - 1) << "' : '" << stc->GetCharAt(current_pos) + // <<"', text length="<< stc->GetTextLength()<<", currentPos="<< current_pos<<" , "< 2 && stc->GetCharAt(current_pos-1) == '\n'){ + //TODO: check that we are really in a freepyscad section. + int lastpos = current_pos - 2; + if (stc->GetCharAt(lastpos) == '\r') + lastpos--; + // do we need to move the ',' ? + if (stc->GetCharAt(current_pos) == ',') { + stc->SetTargetStart(current_pos); + stc->SetTargetEnd(current_pos + 1); + stc->ReplaceTarget(""); + } + //do we need to add the ','? + if (stc->GetCharAt(lastpos) == ')') + stc->InsertText(lastpos + 1, ","); + } else if (stc->GetTextLength() > current_pos && event.GetKey() == int(',') && stc->GetCharAt(current_pos - 1) == ',' && stc->GetCharAt(current_pos) == ',') { + stc->SetTargetStart(current_pos); + stc->SetTargetEnd(current_pos + 1); + stc->ReplaceTarget(""); + } else if (stc->GetTextLength() > current_pos && event.GetKey() == int(')') && stc->GetCharAt(current_pos - 1) == ')' && stc->GetCharAt(current_pos) == ')') { + stc->SetTargetStart(current_pos); + stc->SetTargetEnd(current_pos + 1); + stc->ReplaceTarget(""); + } else if (stc->GetTextLength() > current_pos && event.GetKey() == int('"') && stc->GetCharAt(current_pos - 1) == '"' && stc->GetCharAt(current_pos) == '"') { + stc->SetTargetStart(current_pos); + stc->SetTargetEnd(current_pos + 1); + stc->ReplaceTarget(""); + } else if (stc->GetTextLength() > current_pos && event.GetKey() == int('"') && stc->GetCharAt(current_pos - 1) == '"' + && (stc->GetCharAt(current_pos) == ')' || stc->GetCharAt(current_pos) == ',') ) { + stc->InsertText(current_pos, "\""); + } +} + +// note: this works on KEY, not on CHAR, so be sure the key is the right one for all keyboard layout. +// space, back, del are ok but no ascii char +void FreeCADDialog::on_key_type(wxKeyEvent& event) +{ + if (event.GetKeyCode() == WXK_SPACE && event.GetModifiers() == wxMOD_CONTROL) + { + //get word, if any + int current_pos = m_text->GetCurrentPos(); + int word_start_pos = m_text->WordStartPosition(current_pos, true); + const wxString str = m_text->GetTextRange(word_start_pos, current_pos); + //std::cout << "ctrl-space! " << event.GetEventType() << " '" << str.ToStdString() << "' " << int(m_text->GetCharAt(current_pos - 1)) << "\n"; + if (current_pos > 0 && m_text->GetCharAt(current_pos - 1) == '.') { + //only modifiers + wxString possible; + for (const PyCommand &cmd : commands) { + if (((cmd.type & PyCommandType::pctMODIFIER) != 0) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) { + possible += possible.empty() ? cmd.name : (" " + cmd.name); + } + } + m_text->AutoCompShow(0, possible); + return; + } + //propose words + int nb_words = 0; + wxString possible; + for (const PyCommand &cmd : commands) { + if (str.IsEmpty() || cmd.name.StartsWith(str) && ((cmd.type & PyCommandType::pctDO_NOT_SHOW) == 0)) { + nb_words++; possible += possible.empty() ? cmd.name : (" " + cmd.name); + } + } + //std::cout << "space autocomplete: find " << nb_words << " forstring '" << str << "'\n"; + // Display the autocompletion list + if (nb_words >= 1) + m_text->AutoCompShow(str.length(), possible); + }else if (event.GetKeyCode() == WXK_BACK && event.GetModifiers() == wxMOD_NONE) + { + + //check if we can delete the whole word in one go + //see if previous word is a command + int current_pos = m_text->GetCurrentPos(); + if (m_text->GetCharAt(current_pos - 1) == '(' && m_text->GetCharAt(current_pos) == ')') + current_pos--; + while (m_text->GetCharAt(current_pos - 2) == '(' && m_text->GetCharAt(current_pos -1) == ')') + current_pos -= 2; + int word_start_pos = m_text->WordStartPosition(current_pos, true); + wxString str = m_text->GetTextRange(word_start_pos, current_pos); + if (str.length() > 2) { + int del_more = 0; + if (m_text->GetCharAt(current_pos) == '(' && m_text->GetCharAt(current_pos + 1) == ')') { + del_more += 2; + } + if (m_text->GetCharAt(current_pos+2) == '(' && m_text->GetCharAt(current_pos + 3) == ')') { + del_more += 2; + } + const PyCommand *cmd = get_command(str); + if (cmd != nullptr) { + //delete the whole word + m_text->SetTargetStart(current_pos - str.length()); + m_text->SetTargetEnd(current_pos + del_more); + m_text->ReplaceTarget(""); + return; // don't use the backdel event, it's already del + } + } + event.Skip(true); + } else { + event.Skip(true); + } + //event.SetWillBeProcessedAgain(); +} void FreeCADDialog::createSTC() { m_text = new wxStyledTextCtrl(this, wxID_ANY, @@ -151,59 +587,51 @@ void FreeCADDialog::createSTC() m_text->StyleSetBackground(wxSTC_STYLE_LINENUMBER, wxColour(220, 220, 220)); //m_text->SetMarginType(MARGIN_LINE_NUMBERS, wxSTC_MARGIN_NUMBER); + m_text->SetTabWidth(4); + m_text->SetIndent(4); + m_text->SetUseTabs(true); + m_text->SetIndentationGuides(wxSTC_IV_LOOKFORWARD); + m_text->SetBackSpaceUnIndents(true); + m_text->SetTabIndents(true); + m_text->SetWrapMode(wxSTC_WRAP_WORD); m_text->StyleClearAll(); m_text->SetLexer(wxSTC_LEX_PYTHON); - /* - - -#define wxSTC_P_DEFAULT 0 -#define wxSTC_P_COMMENTLINE 1 -#define wxSTC_P_NUMBER 2 -#define wxSTC_P_STRING 3 -#define wxSTC_P_CHARACTER 4 -#define wxSTC_P_WORD 5 -#define wxSTC_P_TRIPLE 6 -#define wxSTC_P_TRIPLEDOUBLE 7 -#define wxSTC_P_CLASSNAME 8 -#define wxSTC_P_DEFNAME 9 -#define wxSTC_P_OPERATOR 10 -#define wxSTC_P_IDENTIFIER 11 -#define wxSTC_P_COMMENTBLOCK 12 -#define wxSTC_P_STRINGEOL 13 -#define wxSTC_P_WORD2 14 -#define wxSTC_P_DECORATOR 15 - */ - m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(255, 0, 0)); - m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(255, 0, 0)); - m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(255, 0, 0)); - m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(0, 150, 0)); - m_text->StyleSetForeground(wxSTC_H_TAGUNKNOWN, wxColour(0, 150, 0)); - m_text->StyleSetForeground(wxSTC_H_ATTRIBUTE, wxColour(0, 0, 150)); - m_text->StyleSetForeground(wxSTC_H_ATTRIBUTEUNKNOWN, wxColour(0, 0, 150)); - m_text->StyleSetForeground(wxSTC_H_COMMENT, wxColour(150, 150, 150)); - m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(128, 128, 128)); - m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(0, 128, 0)); - m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(0, 128, 128)); - m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(128, 0, 128)); - m_text->StyleSetItalic(wxSTC_P_STRING,true), - m_text->StyleSetForeground(wxSTC_P_CHARACTER, wxColour(128, 0, 128)); - m_text->StyleSetItalic(wxSTC_P_CHARACTER, true), + //m_text->Bind(wxEVT_STC_CHANGE, &FreeCADDialog::on_word_change_for_autocomplete, this); + m_text->Bind(wxEVT_STC_MODIFIED, &FreeCADDialog::on_word_change_for_autocomplete, this); + m_text->Bind(wxEVT_STC_CHARADDED, &FreeCADDialog::on_char_add, this); + m_text->Bind(wxEVT_KEY_DOWN, &FreeCADDialog::on_key_type, this); + m_text->Bind(wxEVT_STC_AUTOCOMP_COMPLETED, &FreeCADDialog::on_autocomp_complete, this); + m_text->Connect(wxID_ANY, + wxEVT_KEY_DOWN, + wxKeyEventHandler(FreeCADDialog::on_key_type), + (wxObject*)NULL, + this); + + m_text->StyleSetForeground(wxSTC_P_DEFAULT, wxColour(0, 0, 0)); + m_text->StyleSetForeground(wxSTC_P_COMMENTLINE, wxColour(128, 255, 128)); // comment, grennsish + m_text->StyleSetForeground(wxSTC_P_COMMENTBLOCK, wxColour(128, 255, 128)); // comment, grennsish + m_text->StyleSetForeground(wxSTC_P_NUMBER, wxColour(255, 128, 0)); // number red-orange + m_text->StyleSetForeground(wxSTC_P_STRING, wxColour(128, 256, 0)); // string, light green + m_text->StyleSetBackground(wxSTC_P_STRINGEOL, wxColour(255, 0, 0)); // End of line where string is not closed + m_text->StyleSetForeground(wxSTC_P_CHARACTER, wxColour(128, 256, 0)); m_text->StyleSetForeground(wxSTC_P_WORD, wxColour(0, 0, 128)); m_text->StyleSetBold(wxSTC_P_WORD, true), - m_text->StyleSetForeground(wxSTC_P_TRIPLE, wxColour(128, 0, 0)); - m_text->StyleSetForeground(wxSTC_P_TRIPLEDOUBLE, wxColour(128, 0, 0)); - m_text->StyleSetForeground(wxSTC_P_CLASSNAME, wxColour(0, 0, 255)); - m_text->StyleSetBold(wxSTC_P_CLASSNAME, true), - m_text->StyleSetForeground(wxSTC_P_DEFNAME, wxColour(0, 128, 128)); + m_text->StyleSetForeground(wxSTC_P_WORD2, wxColour(0, 0, 128)); + m_text->StyleSetForeground(wxSTC_P_TRIPLE, wxColour(128, 0, 0)); // triple quote + m_text->StyleSetForeground(wxSTC_P_TRIPLEDOUBLE, wxColour(128, 0, 0)); //triple double quote + m_text->StyleSetForeground(wxSTC_P_DEFNAME, wxColour(0, 128, 128)); // Function or method name definition m_text->StyleSetBold(wxSTC_P_DEFNAME, true), - m_text->StyleSetForeground(wxSTC_P_OPERATOR, wxColour(0, 0, 0)); + m_text->StyleSetForeground(wxSTC_P_OPERATOR, wxColour(255, 0, 0)); m_text->StyleSetBold(wxSTC_P_OPERATOR, true), - m_text->StyleSetForeground(wxSTC_P_IDENTIFIER, wxColour(128, 128, 128)); - m_text->StyleSetForeground(wxSTC_P_COMMENTBLOCK, wxColour(128, 128, 128)); - m_text->StyleSetForeground(wxSTC_P_STRINGEOL, wxColour(0, 0, 0)); - m_text->StyleSetBackground(wxSTC_P_STRINGEOL, wxColour(224, 192, 224)); + + m_text->StyleSetForeground(wxSTC_P_IDENTIFIER, wxColour(255, 64, 255)); // function call and almost all defined words in the language, violet + + //add text if the saved file exist + boost::filesystem::path temp_file(Slic3r::data_dir()); + temp_file = temp_file / "temp" / "current_pyscad.py"; + load_text_from_file(temp_file); } @@ -215,63 +643,177 @@ void FreeCADDialog::on_dpi_changed(const wxRect& suggested_rect) Fit(); Refresh(); } + +time_t parse_iso_time(std::string &str) { + int y, M, d, h, m; + float s; + sscanf(str.c_str(), "%d-%d-%dT%d:%d:%fZ", &y, &M, &d, &h, &m, &s); + tm time; + time.tm_year = y - 1900; // Year since 1900 + time.tm_mon = M - 1; // 0-11 + time.tm_mday = d; // 1-31 + time.tm_hour = h; // 0-23 + time.tm_min = m; // 0-59 + time.tm_sec = (int)s; // 0-61 (0-60 in C++11) + return mktime(&time); +} + +void FreeCADDialog::test_update_script_file(std::string &json) { + //check if it's more recent + std::stringstream ss; + ss << json; + boost::property_tree::ptree root; + boost::property_tree::read_json(ss, root); + const boost::filesystem::path pyscad_path(boost::filesystem::path(Slic3r::data_dir()) / "scripts" / "FreePySCAD"); + try { + std::string str_date; + for (const auto &entry : root.get_child("commit.committer")) { + if (entry.first == "date") { + str_date = entry.second.data(); + break; + } + } + using namespace boost::locale; + boost::locale::generator gen; + std::locale loc = gen.generate(""); // or "C", "en_US.UTF-8" etc. + std::locale::global(loc); + std::cout.imbue(loc); + + std::cout << "root.commit.committer.date=" << str_date << "\n"; + std::time_t commit_time = parse_iso_time(str_date); + std::cout << "github time_t = "< last_modif) { + std::cout << "have to update!!\n"; + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/__init__.py", pyscad_path / "__init__.py"); + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/Init.py", pyscad_path / "Init.py"); + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/freepyscad.py", pyscad_path / "freepyscad.py"); + } + } + catch (std::exception ex) { + std::cerr << "Error, cannot parse https://api.github.com/repos/supermerill/FreePySCAD/commits/master: " << ex.what() << "\n"; + } +} + +bool FreeCADDialog::init_start_python() { + //reinit + exec_var.reset(new ExecVar()); + + // Get the freecad path (python path) + boost::filesystem::path pythonpath(gui_app->app_config->get("freecad_path")); + pythonpath = pythonpath / "python.exe"; + if (!exists(pythonpath)) { + m_errors->AppendText("Error, cannot find the freecad (version 0.19 or higher) python at '" + pythonpath.string() + "', please update your freecad bin directory in the preferences."); + return false; + } + + const boost::filesystem::path scripts_path(boost::filesystem::path(Slic3r::data_dir()) / "scripts"); + boost::filesystem::create_directories(scripts_path / "FreePySCAD"); + + if (!boost::filesystem::exists(scripts_path / "FreePySCAD" / "freepyscad.py")) { + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/__init__.py", scripts_path / "FreePySCAD" / "__init__.py"); + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/Init.py", scripts_path / "FreePySCAD" / "Init.py"); + get_file_from_web("https://raw.githubusercontent.com/supermerill/FreePySCAD/master/freepyscad.py", scripts_path / "FreePySCAD" / "freepyscad.py"); + }else if (!update_done){ + update_done = true; + //try to check last version on website + //it's async so maybe you won't update it in time, but it's not the end of the world. + const boost::filesystem::path pyscad_path = scripts_path / "FreePySCAD"; + std::function truc = &FreeCADDialog::test_update_script_file; + get_string_from_web_async("https://api.github.com/repos/supermerill/FreePySCAD/commits/master", this, &FreeCADDialog::test_update_script_file); + } + + exec_var->process.reset(new boost::process::child(pythonpath.string() + " -u -i", boost::process::std_in < exec_var->pyin, + boost::process::std_out > exec_var->data_out, boost::process::std_err > exec_var->data_err, exec_var->ios)); + exec_var->pyin << "import FreeCAD" << std::endl; + exec_var->pyin << "import Part" << std::endl; + exec_var->pyin << "import Draft" << std::endl; + exec_var->pyin << "import sys" << std::endl; + exec_var->pyin << "sys.path.append('" << scripts_path.generic_string() << "')" << std::endl; + //std::cout << "sys.path.append('" << pyscad_path.generic_string() << "')" << std::endl; + exec_var->pyin << "from FreePySCAD.freepyscad import *" << std::endl; + //std::cout << "from FreePySCAD.freepyscad import *" << std::endl; + exec_var->pyin << "App.newDocument(\"document\")" << std::endl; +#ifdef __WINDOWS__ + exec_var->pyin << "set_font_dir(\"C:/Windows/Fonts/\")" << std::endl; +#endif +#ifdef __APPLE__ + exec_var->pyin << "set_font_dir([\"/System/Library/Fonts/\", \"~/Library/Fonts/\"])" << std::endl; +#endif +#ifdef __linux__ + exec_var->pyin << "set_font_dir([\"/usr/share/fonts/\",\"~/.fonts/\"])" << std::endl; + // also add +#endif + + return true; +} + +bool FreeCADDialog::end_python() { + exec_var->pyin << "quit()" << std::endl; + exec_var->process->wait(); + exec_var->ios.run(); + return true; +} + void FreeCADDialog::create_geometry(wxCommandEvent& event_args) { //Create the script - std::string program = "C:/Program Files/FreeCAD 0.18/bin/python.exe"; - // cleaning - boost::filesystem::path object_path(Slic3r::resources_dir()); - object_path = object_path / "generation" / "freecad" / "temp.amf"; + boost::filesystem::path object_path(Slic3r::data_dir()); + object_path = object_path / "temp" / "temp.amf"; m_errors->Clear(); if (exists(object_path)) { remove(object_path); } //create synchronous pipe streams - boost::process::opstream pyin; //boost::process::ipstream pyout; //boost::process::ipstream pyerr; - boost::asio::io_service ios; - std::future dataOut; - std::future dataErr; + if (!init_start_python()) return; - boost::filesystem::path pythonpath(gui_app->app_config->get("freecad_path")); - pythonpath = pythonpath / "python.exe"; - if (!exists(pythonpath)) { - m_errors->AppendText("Error, cannot find the freecad python at '"+ pythonpath.string()+"', please update your freecad bin directory in the preferences."); + //create file + //search for scene().redraw(...), add it if not present (without defs) + boost::filesystem::path temp_file(Slic3r::data_dir()); + temp_file = temp_file / "temp"; + boost::filesystem::create_directories(temp_file); + temp_file = temp_file / "exec_temp.py"; + wxString text = m_text->GetText(); + if (text.find("scene().redraw(") == std::string::npos) { + int redraw_pos = text.find("redraw()"); + if (redraw_pos == std::string::npos) { + text = "scene().redraw(\n" + text + "\n)"; + } else { + text = text.substr(0, redraw_pos) + "scene()." + text.substr(redraw_pos); + } + } + if (!this->write_text_in_file(text, temp_file)) { + m_errors->AppendText("Error, cannot write into "+ temp_file.string() + "!"); return; } - boost::filesystem::path pyscad_path(Slic3r::resources_dir()); - pyscad_path = pyscad_path / "generation" / "freecad"; - boost::process::child c(pythonpath.string() +" -u -i", boost::process::std_in < pyin, boost::process::std_out > dataOut, boost::process::std_err > dataErr, ios); - pyin << "import FreeCAD" << std::endl; - pyin << "import Part" << std::endl; - pyin << "import Draft" << std::endl; - pyin << "import sys" << std::endl; - pyin << "sys.path.append('"<< pyscad_path.generic_string() <<"')"<GetText() <<")" << std::endl; - pyin << "Mesh.export(App.ActiveDocument.RootObjects, u\"" << object_path.generic_string() << "\")" << std::endl; - pyin << "print('exported!')" << std::endl; - pyin << "quit()" << std::endl; - c.wait(); - ios.run(); - std::cout << "exit with code " << c.exit_code() << "\n"; + + //std::cout<< "scene().redraw(" << boost::replace_all_copy(boost::replace_all_copy(m_text->GetText(), "\r", ""), "\n", "") << ")" << std::endl; + //exec_var->pyin << "scene().redraw("<< boost::replace_all_copy(boost::replace_all_copy(m_text->GetText(), "\r", ""), "\n", "") <<")" << std::endl; + exec_var->pyin << ("exec(open('" + temp_file.generic_string() + "').read())\n"); + exec_var->pyin << "Mesh.export(App.ActiveDocument.RootObjects, u\"" << object_path.generic_string() << "\")" << std::endl; + exec_var->pyin << "print('exported!')" << std::endl; + exec_var->pyin << "App.ActiveDocument.RootObjects" << std::endl; + + end_python(); std::string pyout_str_hello; std::cout << "==cout==\n"; - std::cout << dataOut.get(); + std::cout << exec_var->data_out.get(); std::cout << "==cerr==\n"; - std::string errStr = dataErr.get(); + std::string errStr = exec_var->data_err.get(); std::cout << errStr << "\n"; std::string cleaned = boost::replace_all_copy(boost::replace_all_copy(errStr, ">>> ", ""),"\r",""); + boost::replace_all(cleaned, "QWaitCondition: Destroyed while threads are still waiting\n", ""); + boost::replace_all(cleaned, "Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.\n", ""); + boost::replace_all(cleaned, "\n\n", "\n"); m_errors->AppendText(cleaned); - std::cout << std::count(cleaned.begin(), cleaned.end(), '\n') << "\n"; - std::cout << std::count(cleaned.begin(), cleaned.end(), '\r') << "\n"; if (!exists(object_path)) { m_errors->AppendText("\nError, no object generated."); @@ -281,7 +823,7 @@ void FreeCADDialog::create_geometry(wxCommandEvent& event_args) { Plater* plat = this->main_frame->plater(); Model& model = plat->model(); plat->reset(); - std::vector objs_idx = plat->load_files(std::vector{ object_path.generic_string() }, true, false); + std::vector objs_idx = plat->load_files(std::vector{ object_path.generic_string() }, true, false, false); if (objs_idx.empty()) return; /// --- translate --- const DynamicPrintConfig* printerConfig = this->gui_app->get_tab(Preset::TYPE_PRINTER)->get_config(); diff --git a/src/slic3r/GUI/FreeCADDialog.hpp b/src/slic3r/GUI/FreeCADDialog.hpp index 9263beeea..94db33d31 100644 --- a/src/slic3r/GUI/FreeCADDialog.hpp +++ b/src/slic3r/GUI/FreeCADDialog.hpp @@ -12,10 +12,35 @@ #include #include #include +#include namespace Slic3r { namespace GUI { + +enum PyCommandType : uint16_t { + pctNONE = 0x0, + pctOPERATION = 0x1 << 0, + pctOBJECT = 0x1 << 1, + pctMODIFIER = 0x1 << 2, + pctNO_PARAMETER = 0x1 << 3, + pctDO_NOT_SHOW = 0x1 << 4 +}; + +class PyCommand { +public: + wxString name; + PyCommandType type; + wxString tooltip; + std::vector args; + PyCommand(wxString lbl, PyCommandType modifier) : name(lbl), type(modifier) { auto lol = { "Z","z" }; } + PyCommand(wxString lbl, uint16_t modifier) : name(lbl), type(PyCommandType(modifier)) {} + PyCommand(wxString lbl, PyCommandType modifier, std::string tooltip) : name(lbl), type(modifier), tooltip(tooltip) {} + PyCommand(wxString lbl, uint16_t modifier, std::string tooltip) : name(lbl), type(PyCommandType(modifier)), tooltip(tooltip) {} + PyCommand(wxString lbl, PyCommandType modifier, std::initializer_list args, std::string tooltip) : name(lbl), type(modifier), tooltip(tooltip) { for(const char* arg: args) this->args.emplace_back(arg); } + PyCommand(wxString lbl, uint16_t modifier, std::initializer_list args, std::string tooltip) : name(lbl), type(PyCommandType(modifier)), tooltip(tooltip) { for (const char* arg : args) this->args.emplace_back(arg); } +}; + class FreeCADDialog : public DPIDialog { @@ -26,14 +51,50 @@ public: protected: void closeMe(wxCommandEvent& event_args); void createSTC(); + + bool init_start_python(); void create_geometry(wxCommandEvent& event_args); + bool end_python(); + + void new_script(wxCommandEvent& event_args) { m_text->ClearAll(); } + void load_script(wxCommandEvent& event_args); + void save_script(wxCommandEvent& event_args); + void quick_save(wxCommandEvent& event_args); void on_dpi_changed(const wxRect& suggested_rect) override; + void on_word_change_for_autocomplete(wxStyledTextEvent& event); + void on_char_add(wxStyledTextEvent& event); + void on_key_type(wxKeyEvent& event); + void on_autocomp_complete(wxStyledTextEvent& event); + bool write_text_in_file(const wxString &towrite, const boost::filesystem::path &file); + bool load_text_from_file(const boost::filesystem::path &file); + void test_update_script_file(std::string &json); + wxStyledTextCtrl* m_text; wxTextCtrl* m_errors; wxTextCtrl* m_help; MainFrame* main_frame; GUI_App* gui_app; + + std::vector commands; + + std::regex word_regex; + bool update_done = false; + + boost::filesystem::path opened_file; + struct ExecVar { + boost::process::opstream pyin; + boost::asio::io_service ios; + std::future data_out; + std::future data_err; + std::unique_ptr process; + }; + std::unique_ptr exec_var; + + bool ready = false; + +protected: + const PyCommand* get_command(const wxString &name) const; }; } // namespace GUI diff --git a/src/slic3r/Utils/PresetUpdater.cpp b/src/slic3r/Utils/PresetUpdater.cpp index 9886c1deb..40b5a35f4 100644 --- a/src/slic3r/Utils/PresetUpdater.cpp +++ b/src/slic3r/Utils/PresetUpdater.cpp @@ -300,12 +300,12 @@ void PresetUpdater::priv::sync_config(const VendorMap vendors) const auto idx_url = vendor.config_update_url + "/" + INDEX_FILENAME; const std::string idx_path = (cache_path / (vendor.id + ".idx")).string(); const std::string idx_path_temp = idx_path + "-update"; - //check if idx_url is leading to our site - if (! boost::starts_with(idx_url, "http://files.prusa3d.com/wp-content/uploads/repository/")) - { - BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; - continue; - } + //check if idx_url is leading to a safe site + //if (! boost::starts_with(idx_url, "http://files.my_company.com/wp-content/uploads/repository/")) + //{ + // BOOST_LOG_TRIVIAL(warning) << "unsafe url path for vendor \"" << vendor.name << "\" rejected: " << idx_url; + // continue; + //} if (!get_file(idx_url, idx_path_temp)) { continue; } if (cancel) { return; }