Don't like ads? PRO users don't see any ads ;-)
Guest

Untitled

By: a guest on Aug 5th, 2012  |  syntax: None  |  size: 34.43 KB  |  hits: 10  |  expires: Never
download  |  raw  |  embed  |  report abuse  |  print
Text below is selected. Please press Ctrl+C to copy to your clipboard. (⌘+C on Mac)
  1. # -*- coding: utf8 -*-
  2.  
  3. #***************************************************************************
  4. #*                                                                         *
  5. #*   Copyright (c) 2012 Keith Sloan <keith@sloan-home.co.uk>               *
  6. #*                                                                         *
  7. #*   This program is free software; you can redistribute it and/or modify  *
  8. #*   it under the terms of the GNU General Public License (GPL)            *
  9. #*   as published by the Free Software Foundation; either version 2 of     *
  10. #*   the License, or (at your option) any later version.                   *
  11. #*   for detail see the LICENCE text file.                                 *
  12. #*                                                                         *
  13. #*   This program is distributed in the hope that it will be useful,       *
  14. #*   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  15. #*   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  16. #*   GNU Library General Public License for more details.                  *
  17. #*                                                                         *
  18. #*   You should have received a copy of the GNU Library General Public     *
  19. #*   License along with this program; if not, write to the Free Software   *
  20. #*   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  *
  21. #*   USA                                                                   *
  22. #*                                                                         *
  23. #*   Acknowledgements :                                                    *
  24. #*                                                                         *
  25. #*     Thanks to shoogen on the FreeCAD forum and Peter Li                 *
  26. #*     for programming advice and some code.                               *
  27. #*                                                                         *
  28. #*                                                                         *
  29. #***************************************************************************
  30. __title__="FreeCAD Draft Workbench - CSG importer Version 0.05c"
  31. __author__ = "Keith Sloan <keith@sloan-home.co.uk>"
  32. __url__ = ["http://www.sloan-home.co.uk/ImportCSG"]
  33.  
  34. import FreeCAD, os, sys
  35. import ply.lex as lex
  36. import ply.yacc as yacc
  37. import Part
  38.  
  39. pythonopen = open # to distinguish python built-in open function from the one declared here
  40.  
  41. # Get the token map from the lexer.  This is required.
  42. import tokrules
  43. from tokrules import tokens
  44.  
  45. #Globals
  46. dxfcache = {}
  47.  
  48. def open(filename):
  49.     "called when freecad opens a file."
  50.     global doc
  51.     global pathName
  52.     pathName = os.path.dirname(os.path.normpath(filename))
  53.     docname = os.path.splitext(os.path.basename(filename))[0]
  54.     doc = FreeCAD.newDocument(docname)
  55.     processcsg(filename)
  56.     return doc
  57.  
  58. def insert(filename,docname):
  59.     "called when freecad imports a file"
  60.     groupname = os.path.splitext(os.path.basename(filename))[0]
  61.     try:
  62.         doc=FreeCAD.getDocument(docname)
  63.     except:
  64.         doc=FreeCAD.newDocument(docname)
  65.     importgroup = doc.addObject("App::DocumentObjectGroup",groupname)
  66.     processcsg(filename)
  67.  
  68. def processcsg(filename):
  69.     global doc
  70.    
  71.     print 'ImportCSG Version 0.5c'
  72.     # Build the lexer
  73.     print 'Start Lex'
  74.     lex.lex(module=tokrules)
  75.     print 'End Lex'
  76.  
  77.     # Build the parser  
  78.     print 'Load Parser'
  79.     # No debug out otherwise Linux has protection exception
  80.     #parser = yacc.yacc(debug=0)
  81.     parser = yacc.yacc(debug=0)
  82.     print 'Parser Loaded'
  83.     # Give the lexer some input
  84.     #f=open('test.scad', 'r')
  85.     f = pythonopen(filename, 'r')
  86.     #lexer.input(f.read())
  87.  
  88.     print 'Start Parser'
  89.     # Swap statements to enable Parser debugging
  90.     #result = parser.parse(f.read(),debug=1)
  91.     result = parser.parse(f.read())
  92.     print 'End Parser'
  93.     print result  
  94.     FreeCAD.Console.PrintMessage('End processing CSG file')
  95.     doc.recompute()
  96. #    colorcodeshapes(doc.Objects)
  97.    
  98. def p_block_list_(p):
  99.     '''
  100.     block_list : statement
  101.                | block_list statement
  102.     '''
  103.     print "Block List"
  104.     print p[1]
  105.     if(len(p) > 2) :
  106.         print p[2]
  107.         p[0] = p[1] + p[2]
  108.     else :
  109.         p[0] = p[1]
  110.     print "End Block List"    
  111.    
  112. def p_group_action1(p):
  113.     'group_action1 : group LPAREN RPAREN OBRACE block_list EBRACE'
  114.     print "Group"
  115.     p[0] = p[5]
  116.  
  117. def p_group_action2(p) :
  118.     'group_action2 : group LPAREN RPAREN SEMICOL'
  119.     print "Group2"
  120.     p[0] = []
  121.    
  122. def p_boolean(p) :
  123.     '''
  124.     boolean : true
  125.             | false
  126.     '''
  127.     p[0] = p[1]
  128.  
  129. def p_layer_string(p) :
  130.     '''
  131.     layer_string : ID
  132.                  | NUMBER
  133.     '''
  134.    
  135. #    print str(p[0]) + ' : ' + str(p[1])
  136.     p[0] = p[1]
  137.  
  138. def p_file_name(p):
  139.     'file_name : QUOTE ID DOT ID QUOTE'
  140.     print p[2]
  141.     print p[4]
  142.     p[0] = [p[2],p[4]]
  143.  
  144. #def p_string(p):
  145. #    'string : QUOTE ID QUOTE'
  146. #    p[0] = p[2]
  147.  
  148. def p_statement(p):
  149.     '''statement : part
  150.                  | operation
  151.                  | multmatrix_action
  152.                  | group_action1
  153.                  | group_action2
  154.                  | color_action
  155.                  | not_supported
  156.     '''
  157.     p[0] = p[1]
  158.  
  159. def p_part(p):
  160.     '''
  161.     part : sphere_action
  162.          | cylinder_action
  163.          | cube_action
  164.          | circle_action
  165.          | square_action
  166.          | polygon_action_nopath
  167.          | polygon_action_plus_path
  168.          | polyhedron_action
  169.          '''
  170.     p[0] = p[1]
  171.  
  172. def p_2d_point(p):
  173.     '2d_point : OSQUARE NUMBER COMMA NUMBER ESQUARE'
  174.     global points_list
  175.     print "2d Point"
  176.     p[0] = [float(p[2]),float(p[4])]
  177.  
  178. def p_points_list_2d(p):
  179.     '''
  180.     points_list_2d : 2d_point COMMA
  181.                    | points_list_2d 2d_point COMMA
  182.                    | points_list_2d 2d_point    
  183.                    '''
  184.     if p[2] == ',' :
  185.         print "Start List"
  186.         print p[1]
  187.         p[0] = [p[1]]
  188.     else :
  189.         print p[1]
  190.         print p[2]
  191.         p[1].append(p[2])
  192.         p[0] = p[1]    
  193.     print p[0]
  194.  
  195. def p_3d_point(p):
  196.     '3d_point : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
  197.     global points_list
  198.     print "3d point"
  199.     p[0] = [p[2],p[4],p[6]]
  200.    
  201.    
  202. def p_points_list_3d(p):
  203.     '''
  204.     points_list_3d : 3d_point COMMA
  205.                | points_list_3d 3d_point COMMA
  206.                | points_list_3d 3d_point
  207.                '''
  208.     if p[2] == ',' :
  209.         print "Start List"
  210.         print p[1]
  211.         p[0] = [p[1]]
  212.     else :
  213.         print p[1]
  214.         print p[2]
  215.         p[1].append(p[2])
  216.         p[0] = p[1]    
  217.     print p[0]
  218.    
  219. def p_path_points(p):
  220.     '''
  221.     path_points : NUMBER COMMA
  222.                 | path_points NUMBER COMMA
  223.                 | path_points NUMBER
  224.                 '''
  225.     print "Path point"            
  226.     if p[2] == ',' :
  227.         print 'Start list'
  228.         print p[1]
  229.         p[0] = [int(p[1])]
  230.     else :
  231.         print p[1]
  232.         print len(p[1])
  233.         print p[2]
  234.         p[1].append(int(p[2]))
  235.         p[0] = p[1]    
  236.     print p[0]
  237.    
  238.        
  239. def p_path_list(p):
  240.     'path_list : OSQUARE path_points ESQUARE'
  241.     print 'Path List '
  242.     print p[2]
  243.     p[0] = p[2]
  244.  
  245. def p_path_set(p) :
  246.     '''
  247.     path_set : path_list
  248.              | path_set COMMA path_list
  249.              '''
  250.     print 'Path Set'
  251.     print len(p)
  252.     if len(p) == 2 :
  253.         p[0] = [p[1]]
  254.     else :
  255.         p[1].append(p[3])
  256.         p[0] = p[1]
  257.     print p[0]
  258.                
  259. def p_operation(p):
  260.     '''
  261.     operation : difference_action
  262.               | intersection_action
  263.               | union_action
  264.               | rotate_extrude_action
  265.               | linear_extrude_action1
  266.               | linear_extrude_action2
  267.               | rotate_extrude_file
  268.               | import_file1
  269.               | import_file2
  270.               | projection_action
  271.               '''
  272.     p[0] = p[1]
  273.  
  274. def p_not_supported(p):
  275.     '''
  276.     not_supported : hull
  277.                   | minkowski
  278.                   '''
  279.     from PyQt4 import QtGui
  280.     QtGui.QMessageBox.critical(None, "Unsupported Functon : "+p[1], "Press OK")
  281.    
  282. def p_size_vector(p):
  283.     'size_vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
  284.     print "size vector"
  285.     p[0] = [p[2],p[4],p[6]]
  286.  
  287.  
  288.    
  289.  
  290.    
  291. def p_assign(p):
  292.     'assign : ID EQ NUMBER'
  293.     print "Assignment"
  294.     print p[1] + ' : ' + p[3]
  295.     p[0] = p[3]
  296.  
  297. def p_color_action(p):
  298.     'color_action : color LPAREN vector RPAREN OBRACE block_list EBRACE'
  299.     print "Color"
  300.     p[0] = p[6]
  301.    
  302. # Error rule for syntax errors
  303. def p_error(p):
  304.     print "Syntax error in input!"
  305.     print p    
  306.  
  307. def fuse(list,name):
  308.     global doc
  309.     print "Fuse"
  310.     print list
  311.     # Is this Multi Fuse
  312.     if ( len(list) > 2):
  313.        print "Multi Fuse"
  314.        myfuse = doc.addObject('Part::MultiFuse',name)
  315.        myfuse.Shapes = list
  316.        for subobj in myfuse.Shapes:
  317.            subobj.ViewObject.hide()    
  318.     else :
  319.        print "Single Fuse"
  320.        myfuse = doc.addObject('Part::Fuse',name)
  321.        myfuse.Base = list[0]
  322.        myfuse.Tool = list[1]
  323.        myfuse.Base.ViewObject.hide()
  324.        myfuse.Tool.ViewObject.hide()
  325.     return(myfuse)
  326.  
  327. def p_union_action(p):
  328.     'union_action : union LPAREN RPAREN OBRACE block_list EBRACE'
  329.     print "union"
  330.     newpart = fuse(p[5],p[1])
  331.     print "Push Union Result"
  332.     p[0] = [newpart]
  333.     print "End Union"
  334.    
  335. def p_difference_action(p):  
  336.     'difference_action : difference LPAREN RPAREN OBRACE block_list EBRACE'
  337.  
  338.     print "difference"
  339.     print len(p[5])
  340.     print p[5]
  341.        
  342.     mycut = doc.addObject('Part::Cut',p[1])
  343. # Cut using Fuse    
  344.     mycut.Base = p[5][0]
  345. #    Can only Cut two objects do we need to fuse extras
  346.     if (len(p[5]) > 2 ):
  347.        print "Need to Fuse Extra First"
  348.        mycut.Tool = fuse(p[5][1:],'union')
  349.     else :  
  350.        mycut.Tool = p[5][1]
  351.     mycut.Base.ViewObject.hide()
  352.     mycut.Tool.ViewObject.hide()      
  353.     print "Push Resulting Cut"
  354.     p[0] = [mycut]
  355.     print "End Cut"    
  356.  
  357. def p_intersection_action(p):
  358.     'intersection_action : intersection LPAREN RPAREN OBRACE block_list EBRACE'
  359.    
  360.     print "intersection"
  361.     # Is this Multi Common
  362.     if (len(p[5]) > 2):    
  363.        print "Multi Common"
  364.        mycommon = doc.addObject('Part::MultiCommon',p[1])
  365.        mycommon.Shapes = p[5]
  366.        for subobj in mycommon.Shapes:
  367.            subobj.ViewObject.hide()    
  368.     else :
  369.        print "Single Common"
  370.        mycommon = doc.addObject('Part::Common',p[1])
  371.        mycommon.Base = p[5][0]
  372.        mycommon.Tool = p[5][1]
  373.        mycommon.Base.ViewObject.hide()
  374.        mycommon.Tool.ViewObject.hide()
  375.  
  376.     p[0] = [mycommon]
  377.     print "End Intersection"
  378.  
  379. class RefineShape:
  380.     '''return a refined shape'''
  381.     def __init__(self, obj,child=None):
  382.         obj.addProperty("App::PropertyLink","Base","Base",
  383.                         "The base object that must be refined")
  384.         obj.Proxy = self
  385.         obj.ViewObject.Proxy = 0
  386.         obj.Base = child
  387.  
  388.     def onChanged(self, fp, prop):
  389.         "Do something when a property has changed"
  390.         pass
  391.  
  392.     def execute(self, fp):
  393.         if fp.Base:
  394.             #self.Base = fp.Base
  395.             #self.Placement = fp.Placement
  396.             #fp.Shape=fp.Base.Shape.Wires[0]
  397.             #fp.Shape=fp.Base.Shape.removeSplitter().transformGeometry(fp.Base.Placement.toMatrix())
  398.             fp.Base.Shape.check() # rather raise an exception then make FreeCAD crash
  399.             sh=fp.Base.Shape.removeSplitter()
  400.             sh.transformShape(fp.Base.Placement.toMatrix())
  401.             #sh.transformGeometry(fp.Base.Placement.toMatrix())
  402.             fp.Shape=sh            
  403.  
  404. def process_rotate_extrude(obj):
  405.     myrev = doc.addObject("Part::Revolution","RotateExtrude")
  406.     myrev.Source = obj
  407.     myrev.Axis = (0.00,1.00,0.00)
  408.     myrev.Base = (0.00,0.00,0.00)
  409.     myrev.Angle = 360.00
  410.     myrev.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90))
  411.     obj.ViewObject.hide()
  412.     newobj=doc.addObject("Part::FeaturePython",'RefineRotateExtrude')
  413.     RefineShape(newobj,myrev)
  414.     myrev.ViewObject.hide()
  415.     return(newobj)
  416.  
  417. def p_rotate_extrude_action(p):
  418.     'rotate_extrude_action : rotate_extrude LPAREN assign COMMA assign COMMA assign COMMA assign RPAREN OBRACE block_list EBRACE'
  419.     print "Rotate Extrude"
  420.     p[0] = [process_rotate_extrude(p[12][0])]
  421.     print "End Rotate Extrude"
  422.  
  423. def p_rotate_extrude_file(p):
  424.     'rotate_extrude_file : rotate_extrude LPAREN file EQ file_name COMMA layer EQ QUOTE layer_string QUOTE COMMA origin EQ 2d_point COMMA assign \
  425.     COMMA assign COMMA assign COMMA assign COMMA assign RPAREN SEMICOL'
  426.     print "Rotate Extrude File"
  427.     obj = process_import_file(p[5][0],p[5][1],p[10])
  428.     p[0] = [process_rotate_extrude(obj)]
  429.     print "End Rotate Extrude File"
  430.  
  431. def process_linear_extrude(obj,h) :
  432.     mylinear = doc.addObject("Part::Extrusion","LinearExtrude")
  433.     mylinear.Base = obj
  434.     mylinear.Dir = (0,0,h)
  435.     mylinear.Placement=FreeCAD.Placement()
  436.     try:
  437.         mylinear.Solid = True
  438.     except:
  439.         a = 1 # Any old null statement
  440.     obj.ViewObject.hide()
  441.     newobj=doc.addObject("Part::FeaturePython",'RefineLinearExtrude')
  442.     RefineShape(newobj,mylinear)
  443.     mylinear.ViewObject.hide()
  444.     return(newobj)
  445.  
  446. def p_linear_extrude_action1(p):
  447.     'linear_extrude_action1 : linear_extrude LPAREN assign COMMA center EQ boolean COMMA assign COMMA assign COMMA assign COMMA \
  448.      assign COMMA assign COMMA assign RPAREN OBRACE block_list EBRACE'
  449.     print "Linear Extrude 1"
  450.     h = float(p[3])
  451.     obj = p[22][0]
  452.     p[0] = [process_linear_extrude(obj,h)]
  453.     if p[7]=='true' :
  454.        center(obj,0,0,h)
  455.     print "End Linear Extrude 1"
  456.    
  457. def p_linear_extrude_action2(p):
  458.     'linear_extrude_action2 : linear_extrude LPAREN assign COMMA center EQ boolean COMMA assign COMMA assign COMMA assign COMMA \
  459.      assign RPAREN OBRACE block_list EBRACE'
  460.     print "Linear Extrude 2"
  461.     h = float(p[3])
  462.     obj = p[18][0]
  463.     p[0] = [process_linear_extrude(obj,h)]
  464.     if p[7]=='true' :
  465.        center(obj,0,0,h)
  466.     print "End Linear Extrude 2"
  467.  
  468. def p_import_file1(p):
  469.     'import_file1 : import LPAREN file EQ file_name COMMA layer EQ QUOTE layer_string QUOTE COMMA origin EQ 2d_point  COMMA assign COMMA assign COMMA \
  470.     assign COMMA assign COMMA assign RPAREN SEMICOL'
  471.     print "Import File"
  472.     p[0] = [process_import_file(p[5][0],p[5][1],p[10])]
  473.     print "End Import File"
  474.  
  475. def p_import_file2(p):
  476.     'import_file2 : import LPAREN file EQ file_name COMMA layer EQ QUOTE QUOTE COMMA origin EQ 2d_point  COMMA assign COMMA assign COMMA \
  477.     assign COMMA assign COMMA assign RPAREN SEMICOL'
  478.     print "Import File"
  479.     p[0] = [process_import_file(p[5][0],p[5][1],"")]
  480.     print "End Import File"    
  481.  
  482. def process_import_file(fname,ext,layer):
  483.     print "Importing : "+fname+"."+ext+" Layer : "+layer
  484.     if ext.lower() in reverseimporttypes()['Mesh']:
  485.         obj=process_mesh_file(fname,ext)
  486.     elif ext=='dxf' :
  487.         obj=processDXF(fname,layer)
  488.     else :
  489.         print "Unsupported file extension"
  490.     return(obj)
  491.  
  492. def reverseimporttypes():
  493.     '''allows to search for supported filetypes by module'''
  494.  
  495.     def getsetfromdict(dict1,index):
  496.         if index in dict1:
  497.             return dict1[index]
  498.         else:
  499.             set1=set()
  500.             dict1[index]=set1
  501.             return set1
  502.  
  503.     importtypes={}
  504.     import FreeCAD
  505.     for key,value in FreeCAD.getImportType().iteritems():
  506.         if type(value) is str:
  507.             getsetfromdict(importtypes,value).add(key)
  508.         else:
  509.             for vitem in value:
  510.                 getsetfromdict(importtypes,vitem).add(key)
  511.     return importtypes
  512.  
  513. def process_mesh_file(fname,ext):
  514.     import Mesh
  515.     fullname = fname+'.'+ext
  516.     filename = os.path.join(pathName,fullname)
  517.     mesh1 = doc.getObject(fname) #reuse imported object
  518.     if not mesh1:
  519.         Mesh.insert(filename)
  520.         mesh1=doc.getObject(fname)
  521.     mesh1.ViewObject.hide()
  522.     sh=Part.Shape()
  523.     sh.makeShapeFromMesh(mesh1.Mesh.Topology,0.1)
  524.     solid = Part.Solid(sh)
  525.     obj=doc.addObject('Part::Feature',"Mesh")
  526.     #ImportObject(obj,mesh1) #This object is not mutable from the GUI
  527.     #ViewProviderTree(obj.ViewObject)
  528.     solid=solid.removeSplitter()
  529.     if solid.Volume < 0:
  530.         #sh.reverse()
  531.         #sh = sh.copy()
  532.         solid.complement()
  533.     obj.Shape=solid#.removeSplitter()
  534.     return(obj)
  535.  
  536. def processDXF(fname,layer):
  537.     global doc
  538.     global pathName
  539.     print "Process DXF file"
  540.     print "File Name : "+fname
  541.     print "Layer : "+layer
  542.     print "PathName : "+pathName
  543.     dxfname = fname+'.dxf'
  544.     filename = os.path.join(pathName,dxfname)
  545.     print "DXF Full path : "+filename
  546.     #featname='import_dxf_%s_%s'%(objname,layera)
  547.     # reusing an allready imported object does not work if the
  548.     #shape in not yet calculated
  549.     import importDXF
  550.     global dxfcache
  551.     layers=dxfcache.get(id(doc),[])
  552.     print "Layers : "+str(layers)
  553.     if layers:
  554.         try:
  555.             groupobj=[go for go in layers if (not layer) or go.Label == layer]
  556.         except:
  557.             groupobj= None
  558.     else:
  559.         groupobj= None
  560.     if not groupobj:
  561.         print "Importing Layer"
  562.         layers = importDXF.processdxf(doc,filename) or importDXF.layers
  563.         dxfcache[id(doc)] = layers[:]
  564.         for l in layers:
  565.             for o in l.Group:
  566.                 o.ViewObject.hide()
  567.             l.ViewObject.hide()
  568.         groupobj=[go for go in layers if (not layer) or go.Label == layer]
  569.     edges=[]
  570.     for shapeobj in groupobj[0].Group:
  571.         edges.extend(shapeobj.Shape.Edges)
  572.     #taken from Drafttools
  573.     from draftlibs import fcvec, fcgeo
  574.     #print "Edges"
  575.     #print edges
  576.     #wires = fcgeo.findWires(edges)
  577.     wires = edgestowires(edges)
  578.     facel=[]
  579.     #print "Process Wires"
  580.     #print wires
  581.     for w in wires:
  582.         #assert(len(w.Edges)>1)
  583.         if not w.isClosed():
  584.             p0 = w.Vertexes[0].Point
  585.             p1 = w.Vertexes[-1].Point
  586.             edges2 = w.Edges
  587.             edges2.append(Part.Line(p1,p0).toShape())
  588.             w = Part.Wire(fcgeo.sortEdges(edges2))
  589.         facel.append(Part.Face(w))
  590.     f=subtractfaces(facel)
  591.     #obj=doc.addObject("Part::FeaturePython",'import_dxf_%s_%s'%(objname,layera))
  592.     obj=doc.addObject('Part::Feature',"dxf")
  593.     #ImportObject(obj,groupobj[0]) #This object is not mutable from the GUI
  594.     #ViewProviderTree(obj.ViewObject)
  595.     obj.Shape=f
  596.     print "DXF Diagnostics"
  597.     print obj.Shape.ShapeType
  598.     print "Closed : "+str(f.isClosed())
  599.     print f.check()
  600.     print [w.isClosed() for w in obj.Shape.Wires]
  601.     return(obj)
  602.  
  603. def edgestowires(edgelist,eps=0.00001):
  604.     '''takes list of edges and returns a list of wires'''
  605.     import Part, Draft
  606.     # todo remove double edges
  607.     wirelist=[]
  608.     for path in findConnectedEdges(edgelist,eps=eps):
  609.         try:
  610.             wirelist.append(Part.Wire(path))
  611.         except:
  612.             comp=Part.Compound(path)
  613.             wirelist.append(comp.connectEdgesToWires(False,eps).Wires[0])
  614.     return wirelist
  615.  
  616. def findConnectedEdges(edgelist,eps=1e-6):
  617.     '''returns a list of list of connected edges'''
  618.    
  619.     def vertequals(v1,v2,eps=1e-6):
  620.         '''check two vertices for equality'''
  621.         return all([abs(c1-c2)<eps for c1,c2 in zip(v1.Point,v2.Point)])
  622.  
  623.     def vertindex(forward):
  624.         '''return index of last or first element'''
  625.         return -1 if forward else 0
  626.  
  627.     freeedges = edgelist[:]
  628.     retlist = []
  629.     debuglist = []
  630.     while freeedges:
  631.         startwire = freeedges.pop(0)
  632.         forward = True
  633.         newedge = [(startwire,True)]
  634.         for forward in (True, False):
  635.             found = True
  636.             while found:
  637.                 lastvert = newedge[vertindex(forward)][0].Vertexes[vertindex(forward == newedge[vertindex(forward)][1])]
  638.                 for ceindex, checkedge in enumerate(freeedges):
  639.                     found = False
  640.                     for cvindex, cvert in enumerate([checkedge.Vertexes[0],checkedge.Vertexes[-1]]):
  641.                         if vertequals(lastvert,cvert,eps):
  642.                             if forward:
  643.                                 newedge.append((checkedge,cvindex == 0))
  644.                             else:
  645.                                 newedge.insert(0,(checkedge,cvindex == 1))
  646.                             del freeedges[ceindex]
  647.                             found = True
  648.                             break
  649.                     else:
  650.                         found = False
  651.                     if found:
  652.                         break
  653.                 else:
  654.                     found = False
  655.         #we are finished for this edge
  656.         debuglist.append(newedge)
  657.         retlist.append([item[0] for item in newedge]) #strip off direction
  658.     #print debuglist
  659.     return retlist
  660.  
  661. def processSTL(fname):
  662.     print "Process STL file"
  663.  
  664. def fcsubmatrix(m):
  665.     """Extracts the 3x3 Submatrix from a freecad Matrix Object
  666.     as a list of row vectors"""
  667.     return [[m.A11,m.A12,m.A13],[m.A21,m.A22,m.A23],[m.A31,m.A32,m.A33]]
  668.  
  669. def multiplymat(l,r):
  670.     """multiply matrices given as lists of row vectors"""
  671.     rt=zip(*r) #transpose r
  672.     mat=[]
  673.     for y in range(len(rt)):
  674.         mline=[]
  675.         for x in range(len(l)):
  676.             mline.append(sum([le*re for le,re in zip(l[y],rt[x])]))
  677.         mat.append(mline)
  678.     return mat
  679.  
  680. def isorthogonal(submatrix,precision=4):
  681.     """checking if 3x3 Matrix is ortogonal (M*Transp(M)==I)"""
  682.     prod=multiplymat(submatrix,zip(*submatrix))
  683.     return [[round(f,precision) for f in line] for line in prod]==[[1,0,0],[0,1,0],[0,0,1]]    
  684.  
  685. def detsubmatrix(s):
  686.     """get the determinant of a 3x3 Matrix given as list of row vectors"""
  687.     return s[0][0]*s[1][1]*s[2][2]+s[0][1]*s[1][2]*s[2][0]+s[0][2]*s[1][0]*s[2][1]\
  688.           -s[2][0]*s[1][1]*s[0][2]-s[2][1]*s[1][2]*s[0][0]-s[2][2]*s[1][0]*s[0][1]
  689.  
  690. def isspecialorthogonaldeterminant(submat,precision=4):
  691.     return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision)==1
  692.  
  693. def isspecialorthogonal(mat,precision=4):
  694.     return abs(mat.submatrix(3).isOrthogonal(10**(-precision))-1.0) < 10**(-precision) and \
  695.             abs(mat.submatrix(3).determinant()-1.0) < 10**(-precision)
  696.        
  697. def p_multmatrix_action(p):
  698.     'multmatrix_action : multmatrix LPAREN matrix RPAREN OBRACE block_list EBRACE'
  699.     print "MultMatrix"
  700.     transform_matrix = FreeCAD.Matrix()
  701.     print "Multmatrix"
  702.     print p[3]
  703.     transform_matrix.A11 = round(float(p[3][0][0]),12)
  704.     transform_matrix.A12 = round(float(p[3][0][1]),12)
  705.     transform_matrix.A13 = round(float(p[3][0][2]),12)
  706.     transform_matrix.A14 = round(float(p[3][0][3]),12)
  707.     transform_matrix.A21 = round(float(p[3][1][0]),12)
  708.     transform_matrix.A22 = round(float(p[3][1][1]),12)
  709.     transform_matrix.A23 = round(float(p[3][1][2]),12)
  710.     transform_matrix.A24 = round(float(p[3][1][3]),12)
  711.     transform_matrix.A31 = round(float(p[3][2][0]),12)
  712.     transform_matrix.A32 = round(float(p[3][2][1]),12)
  713.     transform_matrix.A33 = round(float(p[3][2][2]),12)
  714.     transform_matrix.A34 = round(float(p[3][2][3]),12)
  715.     print transform_matrix
  716.     print "Apply Multmatrix"
  717. #   If more than one object on the stack for multmatrix fuse first
  718.     if (len(p[6]) > 1) :
  719.         part = fuse(p[6],"Matrix Union")
  720.     else :                  
  721.         part = p[6][0]
  722. #    part = new_part.transformGeometry(transform_matrix)
  723. #    part = new_part.copy()
  724. #    part.transformShape(transform_matrix)
  725.     if (isspecialorthogonaldeterminant(fcsubmatrix(transform_matrix))) :
  726.        print "Orthogonal"
  727.        part.Placement=FreeCAD.Placement(transform_matrix).multiply(part.Placement)
  728.        new_part = part
  729.     else :
  730.         print "Transform Geometry"
  731. #       Need to recompute to stop transformGeometry causing a crash        
  732.         doc.recompute()
  733.         new_part = doc.addObject("Part::Feature","Matrix Deformation")
  734.       #  new_part.Shape = part.Base.Shape.transformGeometry(transform_matrix)
  735.         new_part.Shape = part.Shape.transformGeometry(transform_matrix)
  736.         part.ViewObject.hide()
  737.     if False :  
  738. #   Does not fix problemfile or beltTighener although later is closer      
  739.         newobj=doc.addObject("Part::FeaturePython",'RefineMultMatrix')
  740.         RefineShape(newobj,new_part)
  741.         new_part.ViewObject.hide()  
  742.         p[0] = [newobj]
  743.     else :
  744.         p[0] = [new_part]
  745.     print "Multmatrix applied"
  746.    
  747. def p_matrix(p):
  748.     'matrix : OSQUARE vector COMMA vector COMMA vector COMMA vector ESQUARE'
  749.     print "Matrix"
  750.     p[0] = [p[2],p[4],p[6],p[8]]
  751.  
  752. def p_vector(p):
  753.     'vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
  754.     print "Vector"
  755.     p[0] = [p[2],p[4],p[6],p[8]]
  756.  
  757. def center(obj,x,y,z):
  758.     obj.Placement = FreeCAD.Placement(\
  759.         FreeCAD.Vector(-x/2.0,-y/2.0,-z/2.0),\
  760.         FreeCAD.Rotation(0,0,0,1))
  761.    
  762. def p_sphere_action(p):
  763.     'sphere_action : sphere LPAREN assign COMMA assign COMMA assign COMMA assign RPAREN SEMICOL'
  764.     print "Sphere : "+p[9]
  765.     r = float(p[9])
  766.     mysphere = doc.addObject("Part::Sphere",p[1])
  767.     mysphere.Radius = r
  768.     print "Push Sphere"
  769.     p[0] = [mysphere]
  770.     print "End Sphere"
  771.  
  772. def myPolygon(n,r1):
  773.     # Adapted from Draft::_Polygon
  774.     import math
  775.     print "My Polygon"
  776.     angle = math.pi*2/n
  777.     nodes = [FreeCAD.Vector(r1,0,0)]
  778.     for i in range(n-1) :
  779.         th = (i+1) * angle
  780.         nodes.append(FreeCAD.Vector(r1*math.cos(th),r1*math.sin(th),0))
  781.     nodes.append(nodes[0])
  782.     polygonwire = Part.makePolygon(nodes)
  783.  
  784.     polygon = doc.addObject("Part::Feature","Polygon")
  785.     polygon.Shape = Part.Face(polygonwire)
  786.     return(polygon)
  787.  
  788.  
  789.     mycyl.Base = myPolygon(n,r)
  790.     mycyl.Dir = (0,0,h)
  791.     mycyl.Base.ViewObject.hide()
  792.     return(mycyl)
  793.  
  794. def p_cylinder_action(p):
  795.     'cylinder_action : cylinder LPAREN assign COMMA assign COMMA assign COMMA assign COMMA assign COMMA assign COMMA center EQ boolean RPAREN SEMICOL'
  796.     print "Cylinder"
  797.     h = float(p[9])
  798.     r1 = float(p[11])
  799.     r2 = float(p[13])
  800.     print p[9] + ' : ' + p[11] + ' : ' + p[13]
  801.     if ( r1 == r2 ):  
  802.         print "Make Cylinder"
  803.         n = int(p[3])
  804.         if n < 3 :  
  805.             mycyl=doc.addObject("Part::Cylinder",p[1])
  806.             mycyl.Height = h
  807.             mycyl.Radius = r1
  808.         else :
  809.             print "Make Prism"
  810.             mycyl=doc.addObject("Part::Extrusion","prism")
  811.             mycyl.Dir = (0,0,h)
  812.             try :
  813.                 import Draft
  814.                 mycyl.Base = Draft.makePolygon(n,r1)
  815.             except :
  816.                 # If Draft can't import (probably due to lack of Pivy on Mac and
  817.                 # Linux builds of FreeCAD), this is a fallback.
  818.                 # or old level of FreeCAD
  819.                 print "Draft makePolygon Failed, falling back on manual polygon"
  820.                 mycyl.Base = myPolygon(n,r1)
  821.  
  822.             else :
  823.                 pass
  824.                    
  825.             mycyl.Base.ViewObject.hide()
  826.             mycyl.Solid = True
  827.            
  828.     else:
  829.         print "Make Cone"
  830.         mycyl=doc.addObject("Part::Cone",p[1])
  831.         mycyl.Height = h
  832.         mycyl.Radius1 = r1
  833.         mycyl.Radius2 = r2
  834.     print "Center = "+str(p[17])
  835.     if p[17]=='true' :
  836.        center(mycyl,0,0,h)  
  837.     if False :  
  838. #   Does not fix problemfile or beltTighener although later is closer      
  839.         newobj=doc.addObject("Part::FeaturePython",'RefineCylinder')
  840.         RefineShape(newobj,mycyl)
  841.         mycyl.ViewObject.hide()  
  842.         p[0] = [newobj]
  843.     else :
  844.         p[0] = [mycyl]
  845.     print "End Cylinder"
  846.    
  847.    
  848. def p_cube_action(p):
  849.     'cube_action : cube LPAREN size EQ size_vector COMMA center EQ boolean RPAREN SEMICOL'
  850.     global doc
  851.     l = float(p[5][0])
  852.     w = float(p[5][1])
  853.     h = float(p[5][2])
  854.     print "cube : "+p[5][0] + ' : ' + p[5][1] +' : '+ p[5][2]
  855.     mycube=doc.addObject('Part::Box',p[1])
  856.     mycube.Length=l
  857.     mycube.Width=w
  858.     mycube.Height=h
  859.     print "Center = "+str(p[9])
  860.     if p[9]=='true' :
  861.        center(mycube,l,w,h);                                  
  862.     p[0] = [mycube]
  863.     print "End Cube"
  864.  
  865. def p_circle_action(p) :
  866.     'circle_action : circle LPAREN assign COMMA assign COMMA assign COMMA assign RPAREN SEMICOL'
  867.     print "Circle : "+str(p[9])
  868.     r = float(p[9])
  869.     n = int(p[3])
  870.     if n == 0 :
  871.        mycircle = doc.addObject('Part::Circle',p[1])
  872.        mycircle.Radius = r
  873.     else :
  874.        import Draft
  875.        mycircle = Draft.makePolygon(n,r)
  876.        # Bug in FreeCAD waiting for fix
  877.     print "Push Circle"  
  878.     p[0] = [mycircle]
  879.  
  880. def p_square_action(p) :
  881.     'square_action : square LPAREN size EQ 2d_point COMMA center EQ boolean RPAREN SEMICOL'
  882.     print "Square"
  883.     x = float(p[5][0])
  884.     y = float(p[5][1])
  885.     mysquare = doc.addObject('Part::Plane',p[1])
  886.     mysquare.Length=x
  887.     mysquare.Width=y
  888.     p[0] = [mysquare]
  889.  
  890. def subtractfaces(faces):
  891.     if len(faces)==1:
  892.         return faces[0]
  893.     else:
  894.         facelist=sorted(faces,key=(lambda shape: shape.Area),reverse=True)
  895.         base=facelist[0]
  896.         tool=reduce(lambda p1,p2: p1.fuse(p2),facelist[1:])
  897.         return base.cut(tool)
  898.  
  899. def convert_points_list_to_vector(l):
  900.     v = []
  901.     for i in l :
  902.         print i
  903.         v.append(FreeCAD.Vector(i[0],i[1]))
  904.     print v
  905.     return(v)
  906.    
  907.  
  908. def p_polygon_action_nopath(p) :
  909.     'polygon_action_nopath : polygon LPAREN points EQ OSQUARE points_list_2d ESQUARE COMMA paths EQ undef COMMA assign RPAREN SEMICOL'
  910.     print "Polygon"
  911.     print p[6]
  912.     v = convert_points_list_to_vector(p[6])
  913.     mypolygon = doc.addObject('Part::Feature',p[1])
  914.     print "Make Parts"
  915.     # Close Polygon
  916.     v.append(v[0])
  917.     parts = Part.makePolygon(v)
  918.     print "update object"
  919.     mypolygon.Shape = parts
  920.     p[0] = [mypolygon]      
  921.    
  922. def p_polygon_action_plus_path(p) :
  923.     'polygon_action_plus_path : polygon LPAREN points EQ OSQUARE points_list_2d ESQUARE COMMA paths EQ OSQUARE path_set ESQUARE COMMA assign RPAREN SEMICOL'
  924.     print "Polygon with Path"
  925.     print p[6]
  926.     v = convert_points_list_to_vector(p[6])                
  927.     print "Path Set List"
  928.     print p[12]
  929.     for i in p[12] :
  930.          print i
  931.          mypolygon = doc.addObject('Part::Feature','wire')
  932.          path_list = []
  933.          for j in i :
  934.              j = int(j)
  935.              print j
  936.              path_list.append(v[j])
  937. #        Close path
  938.          path_list.append(v[int(i[0])])
  939.          print 'Path List'
  940.          print path_list
  941.          wire = Part.makePolygon(path_list)                          
  942.          mypolygon.Shape = wire
  943.          p[0] = [mypolygon]
  944. #        This only pushes last polygon  
  945.  
  946. def make_face(v1,v2,v3):
  947.     wire = Part.makePolygon([v1,v2,v3,v1])
  948.     face = Part.Face(wire)
  949.     return face
  950.  
  951. def p_polyhedron_action(p) :
  952.     'polyhedron_action : polyhedron LPAREN points EQ OSQUARE points_list_3d ESQUARE COMMA triangles EQ OSQUARE points_list_3d ESQUARE COMMA assign RPAREN SEMICOL'
  953.     print "Polyhedron Points"
  954.     v = []
  955.     for i in p[6] :
  956.         print i
  957.         v.append(FreeCAD.Vector(float(i[0]),float(i[1]),float(i[2])))
  958.     print v
  959.     print "Polyhedron triangles"
  960.     print p[12]
  961.     faces_list = []    
  962.     mypolyhed = doc.addObject('Part::Feature',p[1])
  963.     for i in p[12] :  
  964.         print i
  965.         f = make_face(v[int(i[0])],v[int(i[1])],v[int(i[2])])
  966.         faces_list.append(f)
  967.     shell=Part.makeShell(faces_list)
  968.     mypolyhed.Shape=Part.Solid(shell)
  969.     p[0] = [mypolyhed]      
  970.    
  971.  
  972. def p_projection_action(p) :
  973.     'projection_action : projection LPAREN cut EQ boolean COMMA assign RPAREN OBRACE block_list EBRACE'
  974.     print 'Projection'
  975.     from PyQt4 import QtGui
  976.     QtGui.QMessageBox.critical(None, "Projection Not yet Coded waiting for Peter Li"," Press OK")
  977.  
  978. def shapedict(shapelst):
  979.     return dict([(shape.hashCode(),shape) for shape in shapelst])
  980.  
  981. def shapeset(shapelst):
  982.     return set([shape.hashCode() for shape in shapelst])
  983.  
  984. def mostbasiccompound(comp):
  985.     '''searches fo the most basic shape in a Compound'''
  986.     solids=shapeset(comp.Solids)
  987.     shells=shapeset(comp.Shells)
  988.     faces=shapeset(comp.Faces)
  989.     wires=shapeset(comp.Wires)
  990.     edges=shapeset(comp.Edges)
  991.     vertexes=shapeset(comp.Vertexes)
  992.     #FreeCAD.Console.PrintMessage('%s\n' % (str((len(solids),len(shells),len(faces),len(wires),len(edges),len(vertexes)))))
  993.     for shape in comp.Solids:
  994.         shells -= shapeset(shape.Shells)
  995.         faces -= shapeset(shape.Faces)
  996.         wires -= shapeset(shape.Wires)
  997.         edges -= shapeset(shape.Edges)
  998.         vertexes -= shapeset(shape.Vertexes)
  999.     for shape in comp.Shells:
  1000.         faces -= shapeset(shape.Faces)
  1001.         wires -= shapeset(shape.Wires)
  1002.         edges -= shapeset(shape.Edges)
  1003.         vertexes -= shapeset(shape.Vertexes)
  1004.     for shape in comp.Faces:
  1005.         wires -= shapeset(shape.Wires)
  1006.         edges -= shapeset(shape.Edges)
  1007.         vertexes -= shapeset(shape.Vertexes)
  1008.     for shape in comp.Wires:
  1009.         edges -= shapeset(shape.Edges)
  1010.         vertexes -= shapeset(shape.Vertexes)
  1011.     for shape in comp.Edges:
  1012.         vertexes -= shapeset(shape.Vertexes)
  1013.     #FreeCAD.Console.PrintMessage('%s\n' % (str((len(solids),len(shells),len(faces),len(wires),len(edges),len(vertexes)))))
  1014.     #return len(solids),len(shells),len(faces),len(wires),len(edges),len(vertexes)
  1015.     if vertexes:
  1016.         return "Vertex"
  1017.     elif edges:
  1018.         return "Edge"
  1019.     elif wires:
  1020.         return "Wire"
  1021.     elif faces:
  1022.         return "Face"
  1023.     elif shells:
  1024.         return "Shell"
  1025.     elif solids:
  1026.         return "Solid"
  1027.  
  1028. def colorcodeshapes(objs):
  1029.     shapecolors={
  1030.     "Compound":(0.3,0.3,0.4),
  1031.     "CompSolid":(0.1,0.5,0.0),
  1032.     "Solid":(0.0,0.8,0.0),
  1033.     "Shell":(0.8,0.0,0.0),
  1034.     "Face":(0.6,0.6,0.0),
  1035.     "Wire":(0.1,0.1,0.1),
  1036.     "Edge":(1.0,1.0,1.0),
  1037.     "Vertex":(8.0,8.0,8.0),
  1038.     "Shape":(0.0,0.0,1.0)}
  1039.  
  1040.     for obj in objs:
  1041.         try:
  1042.             if obj.Shape.isNull():
  1043.                 continue
  1044.             if not obj.Shape.isValid():
  1045.                 color=(1.0,0.4,0.4)            
  1046.             else:
  1047.                 st=obj.Shape.ShapeType
  1048.                 if st in ["Compound","CompSolid"]:
  1049.                     st = mostbasiccompound(obj.Shape)
  1050.             color=shapecolors[st]
  1051.             obj.ViewObject.ShapeColor = color
  1052.         except:
  1053.             raise
  1054.    
  1055.     #colorcodeshapes(App.ActiveDocument.Objects)