# -*- coding: utf8 -*- #*************************************************************************** #* Copyright (c) 2012 Keith Sloan * #* * #* 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 * #* * #* Acknowledgements: * #* * #* Thanks to shoogen on the FreeCAD forum and Peter Li * #* for programming advice and some code. * #* * #* * #*************************************************************************** __title__ = "FreeCAD OpenSCAD Workbench - CSG importer" __author__ = "Keith Sloan " __url__ = ["http://www.sloan-home.co.uk/ImportCSG"] printverbose = False import io import os import xml.sax import FreeCAD import Part import Draft from OpenSCADFeatures import * from OpenSCADUtils import * import ply.lex as lex import ply.yacc as yacc params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") printverbose = params.GetBool('printVerbose', False) if FreeCAD.GuiUp: gui = True else: if printverbose: print("FreeCAD Gui not present.") gui = False hassetcolor = [] alreadyhidden = [] original_root_objects = [] # Get the token map from the lexer. This is required. import tokrules from tokrules import tokens from builtins import open as pyopen translate = FreeCAD.Qt.translate def shallHide(subject): for obj in subject.OutListRecursive: if "Matrix_Union" in str(obj.FullName): return False if "Extrude" in str(obj.FullName): return True return False def setColorRecursively(obj, color, transp): ''' For some reason a part made by cutting or fusing other parts do not have a color unless its constituents are also colored. This code sets colors for those constituents unless already set elsewhere. ''' obj.ViewObject.ShapeColor = color obj.ViewObject.Transparency = transp # Add any other relevant features to this list boolean_features = ["Part::Fuse", "Part::MultiFuse", "Part::Cut", "Part::Common", "Part::MultiCommon"] if obj.TypeId in boolean_features: for currentObject in obj.OutList: if printverbose: print(f"Fixing up colors for: {currentObject.FullName}") if currentObject not in hassetcolor: setColorRecursively(currentObject, color, transp) def fixVisibility(): # After an import, only the remaining root objects that we created should be visible, not any # of their individual component objects. But make sure to only handle the ones we just imported, # not anything that already existed. And objects that exist at the toplevel without any # children are ignored. for root_object in FreeCAD.ActiveDocument.RootObjects: if root_object not in original_root_objects: root_object.ViewObject.Visibility = True for obj in root_object.OutListRecursive: obj.ViewObject.Visibility = False def open(filename): "called when freecad opens a file." global doc global pathName docname = os.path.splitext(os.path.basename(filename))[0] doc = FreeCAD.newDocument(docname) if filename.lower().endswith('.scad'): tmpfile = callopenscad(filename) pathName = os.path.dirname(os.path.normpath(filename)) processcsg(tmpfile) try: os.unlink(tmpfile) except OSError: pass else: pathName = os.path.dirname(os.path.normpath(filename)) processcsg(filename) return doc def insert(filename, docname): "called when freecad imports a file" global doc global pathName groupname_unused = os.path.splitext(os.path.basename(filename))[0] try: doc = FreeCAD.getDocument(docname) for obj in doc.RootObjects: original_root_objects.append(obj) except NameError: doc = FreeCAD.newDocument(docname) #importgroup = doc.addObject("App::DocumentObjectGroup",groupname) if filename.lower().endswith('.scad'): tmpfile = callopenscad(filename) pathName = os.path.dirname(os.path.normpath(filename)) processcsg(tmpfile) try: os.unlink(tmpfile) except OSError: pass else: pathName = os.path.dirname(os.path.normpath(filename)) processcsg(filename) def processcsg(filename): global doc if printverbose: print('ImportCSG Version 0.6a') # Build the lexer if printverbose: print('Start Lex') lex.lex(module=tokrules) if printverbose: print('End Lex') # Build the parser if printverbose: print('Load Parser') # Disable generation of debug ('parser.out') and table cache ('parsetab.py'), # as it requires a writable location parser = yacc.yacc(debug=False, write_tables=False) if printverbose: print('Parser Loaded') # Give the lexer some input #f=open('test.scad', 'r') f = io.open(filename, 'r', encoding="utf8") #lexer.input(f.read()) if printverbose: print('Start Parser') # Swap statements to enable Parser debugging #result = parser.parse(f.read(),debug=1) result = parser.parse(f.read()) f.close() if printverbose: print('End Parser') print(result) if gui: fixVisibility() hassetcolor.clear() alreadyhidden.clear() FreeCAD.Console.PrintMessage('End processing CSG file\n') doc.recompute() def p_block_list_(p): ''' block_list : statement | block_list statement | statementwithmod | block_list statementwithmod ''' #if printverbose: print("Block List") #if printverbose: print(p[1]) if(len(p) > 2): if printverbose: print(p[2]) p[0] = p[1] + p[2] else: p[0] = p[1] #if printverbose: print("End Block List") def p_render_action(p): 'render_action : render LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Render (ignored)") p[0] = p[6] def p_group_action1(p): 'group_action1 : group LPAREN RPAREN OBRACE block_list EBRACE' if printverbose: print("Group") # Test need for implicit fuse if p[5] is None: p[0] = [] return if len(p[5]) > 1: if printverbose: print('Fuse Group') for obj in p[5]: checkObjShape(obj) p[0] = [fuse(p[5], "Group")] else: if printverbose: print(f"Group {p[5]} type {type(p[5])}") checkObjShape(p[5]) p[0] = p[5] def p_group_action2(p): 'group_action2 : group LPAREN RPAREN SEMICOL' if printverbose: print("Group2") p[0] = [] def p_boolean(p): ''' boolean : true | false ''' p[0] = p[1] #def p_string(p): # 'string : QUOTE ID QUOTE' # p[0] = p[2] def p_stripped_string(p): 'stripped_string : STRING' p[0] = p[1].strip('"') def p_statement(p): '''statement : part | operation | multmatrix_action | group_action1 | group_action2 | color_action | render_action | not_supported ''' p[0] = p[1] def p_anymodifier(p): '''anymodifier : MODIFIERBACK | MODIFIERDEBUG | MODIFIERROOT | MODIFIERDISABLE ''' # just return the plain modifier for now # has to be changed when the modifiers are implemented # please note that disabled objects usually are stripped of the CSG output during compilation p[0] = p[1] def p_statementwithmod(p): '''statementwithmod : anymodifier statement''' # ignore the modifiers but add them to the label modifier = p[1] obj = p[2] if hasattr(obj, 'Label'): obj.Label = modifier + obj.Label p[0] = obj def p_part(p): ''' part : sphere_action | cylinder_action | cube_action | circle_action | square_action | text_action | polygon_action_nopath | polygon_action_plus_path | polyhedron_action ''' p[0] = p[1] def p_2d_point(p): '2d_point : OSQUARE NUMBER COMMA NUMBER ESQUARE' global points_list if printverbose: print("2d Point") p[0] = [float(p[2]), float(p[4])] def p_points_list_2d(p): ''' points_list_2d : 2d_point COMMA | points_list_2d 2d_point COMMA | points_list_2d 2d_point ''' if p[2] == ',': #if printverbose: # print("Start List") # print(p[1]) p[0] = [p[1]] else: if printverbose: print(p[1]) print(p[2]) p[1].append(p[2]) p[0] = p[1] #if printverbose: print(p[0]) def p_3d_point(p): '3d_point : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE' global points_list if printverbose: print("3d point") p[0] = [p[2], p[4], p[6]] def p_points_list_3d(p): ''' points_list_3d : 3d_point COMMA | points_list_3d 3d_point COMMA | points_list_3d 3d_point ''' if p[2] == ',': if printverbose: print("Start List") if printverbose: print(p[1]) p[0] = [p[1]] else: if printverbose: print(p[1]) if printverbose: print(p[2]) p[1].append(p[2]) p[0] = p[1] if printverbose: print(p[0]) def p_path_points(p): ''' path_points : NUMBER COMMA | path_points NUMBER COMMA | path_points NUMBER ''' #if printverbose: print("Path point") if p[2] == ',': #if printverbose: print('Start list') #if printverbose: print(p[1]) p[0] = [int(p[1])] else: #if printverbose: print(p[1]) #if printverbose: print(len(p[1])) #if printverbose: print(p[2]) p[1].append(int(p[2])) p[0] = p[1] #if printverbose: print(p[0]) def p_path_list(p): 'path_list : OSQUARE path_points ESQUARE' #if printverbose: print('Path List ') #if printverbose: print(p[2]) p[0] = p[2] def p_path_set(p): ''' path_set : path_list | path_set COMMA path_list ''' #if printverbose: print('Path Set') #if printverbose: print(len(p)) if len(p) == 2: p[0] = [p[1]] else: p[1].append(p[3]) p[0] = p[1] #if printverbose: print(p[0]) def p_operation(p): ''' operation : difference_action | intersection_action | union_action | rotate_extrude_action | linear_extrude_with_transform | rotate_extrude_file | import_file1 | resize_action | surface_action | projection_action | hull_action | minkowski_action | offset_action ''' p[0] = p[1] def placeholder(name, children, arguments): from OpenSCADFeatures import OpenSCADPlaceholder newobj=doc.addObject("Part::FeaturePython",name) OpenSCADPlaceholder(newobj, children, str(arguments)) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 #don't hide the children return newobj def CGALFeatureObj(name, children,arguments=[]): myobj=doc.addObject("Part::FeaturePython", name) CGALFeature(myobj, name, children, str(arguments)) if gui: for subobj in children: subobj.ViewObject.hide() if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(myobj.ViewObject) else: myobj.ViewObject.Proxy = 0 return myobj def p_offset_action(p): 'offset_action : offset LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' subobj = None if len(p[6]) == 0: newobj = placeholder('group',[],'{}') elif (len(p[6]) == 1 ): #single object subobj = p[6][0] else: subobj = fuse(p[6],"Offset Union") if 'r' in p[3]: offset = float(p[3]['r']) if 'delta' in p[3]: offset = float(p[3]['delta']) checkObjShape(subobj) if subobj.Shape.Volume == 0 : newobj = doc.addObject("Part::Offset2D",'Offset2D') newobj.Source = subobj newobj.Value = offset if 'r' in p[3]: newobj.Join = 0 else: newobj.Join = 2 else: newobj = doc.addObject("Part::Offset",'offset') newobj.Shape = subobj[0].Shape.makeOffset(offset) newobj.Document.recompute() if gui: subobj.ViewObject.hide() # if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ # GetBool('useViewProviderTree'): # from OpenSCADFeatures import ViewProviderTree # ViewProviderTree(newobj.ViewObject) # else: # newobj.ViewObject.Proxy = 0 p[0] = [newobj] def checkObjShape(obj): if printverbose: print('Check Object Shape') if hasattr(obj, 'Shape'): if obj.Shape.isNull(): if printverbose: print('Shape is Null - recompute') obj.recompute() if obj.Shape.isNull(): print(f'Recompute failed : {obj.Name}') else: if hasattr(obj, 'Name'): print(f"obj {obj.Name} has no Shape") else: print(f"obj {obj} has no Name & Shape") def p_hull_action(p): 'hull_action : hull LPAREN RPAREN OBRACE block_list EBRACE' p[0] = [ CGALFeatureObj(p[1],p[5]) ] def p_minkowski_action(p): ''' minkowski_action : minkowski LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE''' p[0] = [ CGALFeatureObj(p[1],p[6],p[3]) ] def p_resize_action(p): ''' resize_action : resize LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE ''' new_size = p[3]['newsize'] auto = p[3]['auto'] p[6][0].recompute() if p[6][0].Shape.isNull(): doc.recompute() p[6][0].Shape.tessellate(0.05) old_bbox = p[6][0].Shape.BoundBox old_size = [old_bbox.XLength, old_bbox.YLength, old_bbox.ZLength] for r in range(0,3): if auto[r] == '1': new_size[r] = new_size[0] if new_size[r] == '0': new_size[r] = str(old_size[r]) # Calculate a transform matrix from the current bounding box to the new one: transform_matrix = FreeCAD.Matrix() scale = FreeCAD.Vector(float(new_size[0])/old_size[0], float(new_size[1])/old_size[1], float(new_size[2])/old_size[2]) transform_matrix.scale(scale) new_part=doc.addObject("Part::FeaturePython",'Matrix Deformation') new_part.Shape = p[6][0].Shape.transformGeometry(transform_matrix) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(new_part.ViewObject) else: new_part.ViewObject.Proxy = 0 p[6][0].ViewObject.hide() p[0] = [new_part] def p_not_supported(p): ''' not_supported : glide LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE | subdiv LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE ''' if gui and not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('usePlaceholderForUnsupported'): from PySide import QtGui QtGui.QMessageBox.critical(None, translate('OpenSCAD',"Unsupported Function")+" : "+p[1],translate('OpenSCAD',"Press OK")) else: p[0] = [placeholder(p[1],p[6],p[3])] def p_size_vector(p): 'size_vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE' if printverbose: print("size vector") p[0] = [p[2],p[4],p[6]] def p_keywordargument(p): '''keywordargument : ID EQ boolean | ID EQ NUMBER | ID EQ size_vector | ID EQ vector | ID EQ 2d_point | text EQ stripped_string | ID EQ stripped_string ''' p[0] = (p[1],p[3]) if printverbose: print(p[0]) def p_keywordargument_list(p): ''' keywordargument_list : keywordargument | keywordargument_list COMMA keywordargument ''' if len(p) == 2: p[0] = {p[1][0] : p[1][1]} else: p[1][p[3][0]] = p[3][1] p[0]=p[1] def p_color_action(p): 'color_action : color LPAREN vector RPAREN OBRACE block_list EBRACE' import math if printverbose: print("Color") color = tuple([float(f) for f in p[3][:3]]) #RGB transp = 100 - int(math.floor(100*float(p[3][3]))) #Alpha if gui: for obj in p[6]: if shallHide(obj): if "Group" in obj.FullName: obj.ViewObject.Visibility=False alreadyhidden.append(obj) setColorRecursively(obj, color, transp) hassetcolor.append(obj) p[0] = p[6] # Error rule for syntax errors def p_error(p): if printverbose: print("Syntax error in input!") if printverbose: print(p) def fuse(lst,name): global doc if printverbose: print("Fuse") if printverbose: print(lst) if len(lst) == 0: myfuse = placeholder('group',[],'{}') elif len(lst) == 1: return lst[0] # Is this Multi Fuse elif len(lst) > 2: if printverbose: print("Multi Fuse") myfuse = doc.addObject('Part::MultiFuse',name) myfuse.Shapes = lst if gui: for subobj in myfuse.Shapes: subobj.ViewObject.hide() else: if printverbose: print("Single Fuse") myfuse = doc.addObject('Part::Fuse',name) myfuse.Base = lst[0] myfuse.Tool = lst[1] checkObjShape(myfuse.Base) checkObjShape(myfuse.Tool) myfuse.Shape = myfuse.Base.Shape.fuse(myfuse.Tool.Shape) if gui: myfuse.Base.ViewObject.hide() myfuse.Tool.ViewObject.hide() myfuse.Placement = FreeCAD.Placement() return myfuse def p_empty_union_action(p): 'union_action : union LPAREN RPAREN SEMICOL' if printverbose: print("empty union") newpart = fuse([],p[1]) if printverbose: print("Push Union Result") p[0] = [newpart] if printverbose: print("End Union") def p_union_action(p): 'union_action : union LPAREN RPAREN OBRACE block_list EBRACE' if printverbose: print("union") newpart = fuse(p[5],p[1]) if printverbose: print("Push Union Result") p[0] = [newpart] if printverbose: print("End Union") def p_difference_action(p): 'difference_action : difference LPAREN RPAREN OBRACE block_list EBRACE' if printverbose: print("difference") if printverbose: print(len(p[5])) if printverbose: print(p[5]) if (len(p[5]) == 0 ): #nochild mycut_unused = placeholder('group',[],'{}') elif (len(p[5]) == 1 ): #single object p[0] = p[5] else: # Cut using Fuse mycut = doc.addObject('Part::Cut',p[1]) mycut.Base = p[5][0] # Can only Cut two objects do we need to fuse extras if (len(p[5]) > 2 ): if printverbose: print("Need to Fuse Extra First") mycut.Tool = fuse(p[5][1:],'union') else : mycut.Tool = p[5][1] checkObjShape(mycut.Tool) if gui: mycut.Base.ViewObject.hide() mycut.Tool.ViewObject.hide() if printverbose: print("Push Resulting Cut") p[0] = [mycut] if printverbose: print("End Cut") def p_intersection_action(p): 'intersection_action : intersection LPAREN RPAREN OBRACE block_list EBRACE' if printverbose: print("intersection") # Is this Multi Common if (len(p[5]) > 2): if printverbose: print("Multi Common") mycommon = doc.addObject('Part::MultiCommon',p[1]) mycommon.Shapes = p[5] if gui: for subobj in mycommon.Shapes: subobj.ViewObject.hide() elif (len(p[5]) == 2): if printverbose: print("Single Common") mycommon = doc.addObject('Part::Common',p[1]) mycommon.Base = p[5][0] mycommon.Tool = p[5][1] checkObjShape(mycommon.Base) checkObjShape(mycommon.Tool) if gui: mycommon.Base.ViewObject.hide() mycommon.Tool.ViewObject.hide() elif (len(p[5]) == 1): mycommon = p[5][0] else : # 1 child mycommon = placeholder('group',[],'{}') mycommon.Shape = mycommon.Base.Shape.common(mycommon.Tool.Shape) p[0] = [mycommon] if printverbose: print("End Intersection") def process_rotate_extrude(obj, angle): newobj=doc.addObject("Part::FeaturePython",'RefineRotateExtrude') RefineShape(newobj,obj) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 obj.ViewObject.hide() myrev = doc.addObject("Part::Revolution","RotateExtrude") myrev.Source = newobj myrev.Axis = (0.00,1.00,0.00) myrev.Base = (0.00,0.00,0.00) myrev.Angle = angle myrev.Placement = FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90)) if gui: newobj.ViewObject.hide() return myrev def process_rotate_extrude_prism(obj, angle, n): newobj=doc.addObject("Part::FeaturePython",'PrismaticToroid') PrismaticToroid(newobj, obj, angle, n) newobj.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90)) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 obj.ViewObject.hide() return newobj def p_rotate_extrude_action(p): 'rotate_extrude_action : rotate_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Rotate Extrude") angle = 360.0 if 'angle' in p[3]: angle = float(p[3]['angle']) n = int(round(float(p[3]['$fn']))) fnmax = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetInt('useMaxFN', 16) if (len(p[6]) > 1) : part = fuse(p[6],"Rotate Extrude Union") else : part = p[6][0] if n < 3 or fnmax != 0 and n > fnmax: p[0] = [process_rotate_extrude(part,angle)] else: p[0] = [process_rotate_extrude_prism(part,angle,n)] if printverbose: print("End Rotate Extrude") def p_rotate_extrude_file(p): 'rotate_extrude_file : rotate_extrude LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Rotate Extrude File") angle = 360.0 if 'angle' in p[3]: angle = float(p[3]['angle']) filen,ext = p[3]['file'] .rsplit('.',1) obj = process_import_file(filen,ext,p[3]['layer']) n = int(round(float(p[3]['$fn']))) fnmax = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetInt('useMaxFN', 16) if n < 3 or fnmax != 0 and n > fnmax: p[0] = [process_rotate_extrude(obj,angle)] else: p[0] = [process_rotate_extrude_prism(obj,angle,n)] if printverbose: print("End Rotate Extrude File") def process_linear_extrude(obj,h) : #if gui: newobj = doc.addObject("Part::FeaturePython",'RefineLinearExtrude') RefineShape(newobj,obj)#mylinear) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 obj.ViewObject.hide() #mylinear.ViewObject.hide() mylinear = doc.addObject("Part::Extrusion","LinearExtrude") mylinear.Base = newobj #obj mylinear.Dir = (0,0,h) mylinear.Placement=FreeCAD.Placement() # V17 change to False mylinear.Solid = True mylinear.Solid = False if gui: newobj.ViewObject.hide() return mylinear def process_linear_extrude_with_transform(base,height,twist,scale) : newobj = doc.addObject("Part::FeaturePython",'transform_extrude') # base is a FreeCAD Object, height & twist are floats, scale is a two-component vector of floats Twist(newobj,base,height,-twist,scale) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 #import ViewProviderTree from OpenSCADFeatures #ViewProviderTree(obj.ViewObject) return newobj def p_linear_extrude_with_transform(p): 'linear_extrude_with_transform : linear_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Linear Extrude With Transform") h = float(p[3]['height']) if printverbose: print("Height : ",h) s = [1.0,1.0] t = 0.0 if 'scale' in p[3]: if isinstance(p[3]['scale'], str): s = [float(p[3]['scale']), float(p[3]['scale'])] else: s = [float(p[3]['scale'][0]), float(p[3]['scale'][1])] if printverbose: print ("Scale: " + str(s)) if 'twist' in p[3]: t = float(p[3]['twist']) if printverbose: print("Twist : ",t) # Test if null object like from null text if (len(p[6]) == 0) : p[0] = [] return if (len(p[6]) > 1) : obj = fuse(p[6],"Linear Extrude Union") else : obj = p[6][0] checkObjShape(obj) if t != 0.0 or s[0] != 1.0 or s[1] != 1.0: newobj = process_linear_extrude_with_transform(obj,h,t,s) else: newobj = process_linear_extrude(obj,h) if p[3].get('center','false')=='true' : center(newobj,0,0,h) p[0] = [newobj] if gui: obj.ViewObject.hide() if printverbose: print("End Linear Extrude with Transform") def p_import_file1(p): 'import_file1 : import LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Import File") filen,ext = p[3]['file'].rsplit('.',1) p[0] = [process_import_file(filen,ext,p[3]['layer'])] if printverbose: print("End Import File") def p_surface_action(p): 'surface_action : surface LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Surface") obj = doc.addObject("Part::Feature",'surface') obj.Shape,xoff,yoff=makeSurfaceVolume(p[3]['file']) if p[3].get('center','false') == 'true' : center(obj,xoff,yoff,0.0) p[0] = [obj] if printverbose: print("End surface") def process_import_file(fname,ext,layer): if printverbose: print("Importing : "+fname+"."+ext+" Layer : "+layer) if ext.lower() in reverseimporttypes()['Mesh']: obj = process_mesh_file(fname,ext) elif ext.lower() == 'dxf' : obj = processDXF(fname,layer) elif ext.lower() == 'svg': obj = processSVG(fname, ext) else: raise ValueError("Unsupported file extension %s" % ext) return obj def processSVG(fname, ext): from importSVG import svgHandler if printverbose: print("SVG Handler") doc = FreeCAD.ActiveDocument docSVG = FreeCAD.newDocument(fname+'_tmp') FreeCAD.ActiveDocument = docSVG # Set up the parser parser = xml.sax.make_parser() parser.setFeature(xml.sax.handler.feature_external_ges, False) parser.setContentHandler(svgHandler()) parser._cont_handler.doc = docSVG # pathName is a Global filename = os.path.join(pathName,fname+'.'+ext) # Use the native Python open which was saved as `pyopen` parser.parse(pyopen(filename)) #combine SVG objects into one shapes = [] for obj in FreeCAD.ActiveDocument.Objects: if printverbose: print(obj.Name) if printverbose: print(obj.Shape) shapes.append(obj.Shape) #compoundSVG = Part.makeCompound(shapes) #compoundSVG = Draft.join(objects) FreeCAD.closeDocument(docSVG.Name) FreeCAD.ActiveDocument=doc obj=doc.addObject('Part::Feature',fname) obj.Shape=Part.Compound(shapes) return obj def process_mesh_file(fname,ext): import Mesh import Part fullname = fname+'.'+ext filename = os.path.join(pathName,fullname) objname = os.path.split(fname)[1] mesh1 = doc.getObject(objname) #reuse imported object if not mesh1: Mesh.insert(filename) mesh1 = doc.getObject(objname) if mesh1 is not None: if gui: mesh1.ViewObject.hide() sh = Part.Shape() sh.makeShapeFromMesh(mesh1.Mesh.Topology,0.1) solid = Part.Solid(sh) obj = doc.addObject('Part::Feature',"Mesh") #ImportObject(obj,mesh1) #This object is not mutable from the GUI #ViewProviderTree(obj.ViewObject) solid = solid.removeSplitter() if solid.Volume < 0: #sh.reverse() #sh = sh.copy() solid.complement() obj.Shape = solid#.removeSplitter() else: #mesh1 is None FreeCAD.Console.PrintError('Mesh not imported %s.%s %s\n' % \ (objname,ext,filename)) import Part obj = doc.addObject('Part::Feature',"FailedMeshImport") obj.Shape = Part.Compound([]) return obj def processTextCmd(t): from OpenSCADUtils import callopenscadstring tmpfilename = callopenscadstring(t,'dxf') from OpenSCAD2Dgeom import importDXFface face = importDXFface(tmpfilename,None,None) obj = doc.addObject('Part::Feature','text') obj.Shape = face try: os.unlink(tmpfilename) except OSError: pass return obj def processDXF(fname,layer): global doc global pathName from OpenSCAD2Dgeom import importDXFface if printverbose: print("Process DXF file") if printverbose: print("File Name : "+fname) if printverbose: print("Layer : "+layer) if printverbose: print("PathName : "+pathName) dxfname = fname+'.dxf' filename = os.path.join(pathName,dxfname) shortname = os.path.split(fname)[1] if printverbose: print("DXF Full path : "+filename) face = importDXFface(filename,layer,doc) obj=doc.addObject('Part::Feature','dxf_%s_%s' % (shortname,layer or "all")) obj.Shape=face if printverbose: print("DXF Diagnostics") if printverbose: print(obj.Shape.ShapeType) if printverbose: print("Closed : "+str(obj.Shape.isClosed())) if printverbose: print(obj.Shape.check()) if printverbose: print([w.isClosed() for w in obj.Shape.Wires]) return obj def processSTL(fname): if printverbose: print("Process STL file") def p_multmatrix_action(p): 'multmatrix_action : multmatrix LPAREN matrix RPAREN OBRACE block_list EBRACE' if printverbose: print("MultMatrix") transform_matrix = FreeCAD.Matrix() if printverbose: print("Multmatrix") if printverbose: print(p[3]) if gui and p[6]: parentcolor=p[6][0].ViewObject.ShapeColor parenttransparency=p[6][0].ViewObject.Transparency m1l=sum(p[3],[]) if any('x' in me for me in m1l): #hexfloats m1l=[float.fromhex(me) for me in m1l] matrixisrounded=False elif max((len(me) for me in m1l)) >= 14: #might have double precision m1l=[float(me) for me in m1l] # assume precise output m1l=[(0 if (abs(me) < 1e-15) else me) for me in m1l] matrixisrounded=False else: #trucanted numbers m1l=[round(float(me),12) for me in m1l] #round matrixisrounded=True transform_matrix = FreeCAD.Matrix(*tuple(m1l)) if printverbose: print(transform_matrix) if printverbose: print("Apply Multmatrix") # If more than one object on the stack for multmatrix fuse first if (len(p[6]) == 0) : part = placeholder('group',[],'{}') elif (len(p[6]) > 1) : part = fuse(p[6],"Matrix Union") else : part = p[6][0] if (isspecialorthogonalpython(fcsubmatrix(transform_matrix))) : if printverbose: print("special orthogonal") if matrixisrounded: if printverbose: print("rotation rounded") plm=FreeCAD.Placement(transform_matrix) plm=FreeCAD.Placement(plm.Base,roundrotation(plm.Rotation)) part.Placement=plm.multiply(part.Placement) else: part.Placement=FreeCAD.Placement(transform_matrix).multiply(\ part.Placement) new_part = part elif isrotoinversionpython(fcsubmatrix(transform_matrix)): if printverbose: print("orthogonal and inversion") cmat,axisvec = decomposerotoinversion(transform_matrix) new_part=doc.addObject("Part::Mirroring",'mirr_%s'%part.Name) new_part.Source=part new_part.Normal=axisvec if matrixisrounded: if printverbose: print("rotation rounded") plm=FreeCAD.Placement(cmat) new_part.Placement=FreeCAD.Placement(plm.Base,roundrotation(plm.Rotation)) else: new_part.Placement=FreeCAD.Placement(cmat) new_part.Label="mirrored %s" % part.Label if gui: part.ViewObject.hide() elif FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useMultmatrixFeature'): from OpenSCADFeatures import MatrixTransform new_part=doc.addObject("Part::FeaturePython",'Matrix Deformation') MatrixTransform(new_part,transform_matrix,part) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(new_part.ViewObject) else: new_part.ViewObject.Proxy = 0 part.ViewObject.hide() else : if printverbose: print("Transform Geometry") part.recompute() if part.Shape.isNull(): doc.recompute() new_part = doc.addObject("Part::Feature","Matrix Deformation") new_part.Shape = part.Shape.transformGeometry(transform_matrix) if gui: part.ViewObject.hide() if False : # Does not fix problemfile or beltTighener although later is closer newobj=doc.addObject("Part::FeaturePython",'RefineMultMatrix') RefineShape(newobj,new_part) if gui: newobj.ViewObject.Proxy = 0 new_part.ViewObject.hide() p[0] = [newobj] else : p[0] = [new_part] if gui and p[6]: new_part.ViewObject.ShapeColor=parentcolor new_part.ViewObject.Transparency = parenttransparency if printverbose: print("Multmatrix applied") def p_matrix(p): 'matrix : OSQUARE vector COMMA vector COMMA vector COMMA vector ESQUARE' if printverbose: print("Matrix") p[0] = [p[2],p[4],p[6],p[8]] def p_vector(p): 'vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER ESQUARE' if printverbose: print("Vector") p[0] = [p[2],p[4],p[6],p[8]] def center(obj,x,y,z): obj.Placement = FreeCAD.Placement(\ FreeCAD.Vector(-x/2.0,-y/2.0,-z/2.0),\ FreeCAD.Rotation(0,0,0,1)) def p_sphere_action(p): 'sphere_action : sphere LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Sphere : ",p[3]) r = float(p[3]['r']) mysphere = doc.addObject("Part::Sphere",p[1]) mysphere.Radius = r if printverbose: print("Push Sphere") p[0] = [mysphere] if printverbose: print("End Sphere") def myPolygon(n,r1): # Adapted from Draft::_Polygon import math if printverbose: print("My Polygon") angle = math.pi*2/n nodes = [FreeCAD.Vector(r1,0,0)] for i in range(n-1) : th = (i+1) * angle nodes.append(FreeCAD.Vector(r1*math.cos(th),r1*math.sin(th),0)) nodes.append(nodes[0]) polygonwire = Part.makePolygon(nodes) polygon = doc.addObject("Part::Feature","Polygon") polygon.Shape = Part.Face(polygonwire) return polygon def p_cylinder_action(p): 'cylinder_action : cylinder LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Cylinder") tocenter = p[3].get('center','false') h = float(p[3]['h']) r1 = float(p[3]['r1']) r2 = float(p[3]['r2']) #n = int(p[3]['$fn']) n = int(round(float(p[3]['$fn']))) fnmax = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetInt('useMaxFN', 16) if printverbose: print(p[3]) if h > 0: if ( r1 == r2 and r1 > 0): if printverbose: print("Make Cylinder") if n < 3 or fnmax != 0 and n > fnmax: mycyl=doc.addObject("Part::Cylinder",p[1]) mycyl.Height = h mycyl.Radius = r1 else : if printverbose: print("Make Prism") if False: #user Draft Polygon mycyl=doc.addObject("Part::Extrusion","prism") mycyl.Dir = (0,0,h) try : import Draft mycyl.Base = Draft.makePolygon(n,r1,face=True) except Exception: # If Draft can't import (probably due to lack of Pivy on Mac and # Linux builds of FreeCAD), this is a fallback. # or old level of FreeCAD if printverbose: print("Draft makePolygon Failed, falling back on manual polygon") mycyl.Base = myPolygon(n,r1) # mycyl.Solid = True else : pass if gui: mycyl.Base.ViewObject.hide() else: #Use Part::Prism primitive mycyl=doc.addObject("Part::Prism","prism") mycyl.Polygon = n mycyl.Circumradius = r1 mycyl.Height = h elif (r1 != r2): if n < 3 or fnmax != 0 and n > fnmax: if printverbose: print("Make Cone") mycyl=doc.addObject("Part::Cone",p[1]) mycyl.Height = h mycyl.Radius1 = r1 mycyl.Radius2 = r2 else: if printverbose: print("Make Frustum") mycyl=doc.addObject("Part::FeaturePython",'frustum') Frustum(mycyl,r1,r2,n,h) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(mycyl.ViewObject) else: mycyl.ViewObject.Proxy = 0 else: # r1 == r2 == 0 FreeCAD.Console.PrintWarning('cylinder with radius zero\n') mycyl=doc.addObject("Part::Feature","emptycyl") mycyl.Shape = Part.Compound([]) else: # h == 0 FreeCAD.Console.PrintWarning('cylinder with height <= zero\n') mycyl=doc.addObject("Part::Feature","emptycyl") mycyl.Shape = Part.Compound([]) if printverbose: print("Center = ",tocenter) if tocenter=='true' : center(mycyl,0,0,h) if False : # Does not fix problemfile or beltTighener although later is closer newobj=doc.addObject("Part::FeaturePython",'RefineCylinder') RefineShape(newobj,mycyl) if gui: if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('useViewProviderTree'): from OpenSCADFeatures import ViewProviderTree ViewProviderTree(newobj.ViewObject) else: newobj.ViewObject.Proxy = 0 mycyl.ViewObject.hide() p[0] = [newobj] else : p[0] = [mycyl] if printverbose: print("End Cylinder") def p_cube_action(p): 'cube_action : cube LPAREN keywordargument_list RPAREN SEMICOL' global doc l,w,h = [float(str1) for str1 in p[3]['size']] if (l > 0 and w > 0 and h >0): if printverbose: print("cube : ",p[3]) mycube=doc.addObject('Part::Box',p[1]) mycube.Length=l mycube.Width=w mycube.Height=h else: FreeCAD.Console.PrintWarning('cube with radius zero\n') mycube=doc.addObject("Part::Feature","emptycube") mycube.Shape = Part.Compound([]) if p[3].get('center','false')=='true' : center(mycube,l,w,h) p[0] = [mycube] if printverbose: print("End Cube") def p_circle_action(p) : 'circle_action : circle LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Circle : "+str(p[3])) r = float(p[3]['r']) # Avoid zero radius if r == 0 : r = 0.00001 n = int(p[3]['$fn']) fnmax = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetInt('useMaxFN',16) # Alter Max polygon to control if polygons are circles or polygons # in the modules preferences import Draft if n == 0 or fnmax != 0 and n >= fnmax: mycircle = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",'circle') Draft._Circle(mycircle) mycircle.Radius = r mycircle.MakeFace = True mycircle = Draft.makeCircle(r,face=True) # would call doc.recompute FreeCAD.ActiveDocument.recompute() #mycircle = doc.addObject('Part::Circle',p[1]) #would not create a face #mycircle.Radius = r else : #mycircle = Draft.makePolygon(n,r) # would call doc.recompute mycircle = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",'polygon') Draft._Polygon(mycircle) mycircle.FacesNumber = n mycircle.Radius = r mycircle.DrawMode = "inscribed" mycircle.MakeFace = True if gui: Draft._ViewProviderDraft(mycircle.ViewObject) if printverbose: print("Push Circle") p[0] = [mycircle] def p_square_action(p) : 'square_action : square LPAREN keywordargument_list RPAREN SEMICOL' if printverbose: print("Square") size = p[3]['size'] x = float(size[0]) y = float(size[1]) mysquare = doc.addObject('Part::Plane',p[1]) mysquare.Length=x mysquare.Width=y if p[3].get('center','false')=='true' : center(mysquare,x,y,0) p[0] = [mysquare] def addString(t,s,p): return(t + ', ' +s+' = "'+p[3][s]+'"') def addValue(t,v,p): return(t + ', ' +v+' = '+p[3][v]) def p_text_action(p) : 'text_action : text LPAREN keywordargument_list RPAREN SEMICOL' # If text string is null ignore if p[3]['text'] == "" or p[3]['text'] == " " : p[0] = [] return t = 'text ( text="'+p[3]['text']+'"' t = addValue(t,'size',p) t = addString(t,'spacing',p) t = addString(t,'font',p) t = addString(t,'direction',p) t = addString(t,'language',p) if "script" in p[3]: t = addString(t,'script',p) else: t += ', script="latin"' t = addString(t,'halign',p) t = addString(t,'valign',p) t = addValue(t,'$fn',p) t = addValue(t,'$fa',p) t = addValue(t,'$fs',p) t = t+');' FreeCAD.Console.PrintMessage("textmsg : "+t+"\n") p[0] = [processTextCmd(t)] def convert_points_list_to_vector(l): v = [] for i in l : if printverbose: print(i) v.append(FreeCAD.Vector(i[0],i[1])) if printverbose: print(v) return v def p_polygon_action_nopath(p) : 'polygon_action_nopath : polygon LPAREN points EQ OSQUARE points_list_2d ESQUARE COMMA paths EQ undef COMMA keywordargument_list RPAREN SEMICOL' if printverbose: print("Polygon") if printverbose: print(p[6]) v = convert_points_list_to_vector(p[6]) mypolygon = doc.addObject('Part::Feature',p[1]) if printverbose: print("Make Parts") # Close Polygon v.append(v[0]) parts = Part.makePolygon(v) if printverbose: print("update object") mypolygon.Shape = Part.Face(parts) p[0] = [mypolygon] def p_polygon_action_plus_path(p) : 'polygon_action_plus_path : polygon LPAREN points EQ OSQUARE points_list_2d ESQUARE COMMA paths EQ OSQUARE path_set ESQUARE COMMA keywordargument_list RPAREN SEMICOL' if printverbose: print("Polygon with Path") if printverbose: print(p[6]) v = convert_points_list_to_vector(p[6]) if printverbose: print("Path Set List") if printverbose: print(p[12]) for i in p[12] : if printverbose: print(i) mypolygon = doc.addObject('Part::Feature','wire') path_list = [] for j in i : j = int(j) if printverbose: print(j) path_list.append(v[j]) # Close path path_list.append(v[int(i[0])]) if printverbose: print('Path List') if printverbose: print(path_list) wire = Part.makePolygon(path_list) mypolygon.Shape = Part.Face(wire) p[0] = [mypolygon] # This only pushes last polygon def make_face(v1,v2,v3): wire = Part.makePolygon([v1,v2,v3,v1]) face = Part.Face(wire) return face def p_polyhedron_action(p) : '''polyhedron_action : polyhedron LPAREN points EQ OSQUARE points_list_3d ESQUARE COMMA faces EQ OSQUARE path_set ESQUARE COMMA keywordargument_list RPAREN SEMICOL | polyhedron LPAREN points EQ OSQUARE points_list_3d ESQUARE COMMA triangles EQ OSQUARE points_list_3d ESQUARE COMMA keywordargument_list RPAREN SEMICOL''' if printverbose: print("Polyhedron Points") v = [] for i in p[6] : if printverbose: print(i) v.append(FreeCAD.Vector(float(i[0]),float(i[1]),float(i[2]))) if printverbose: print(v) print ("Polyhedron "+p[9]) print (p[12]) faces_list = [] mypolyhed = doc.addObject('Part::Feature',p[1]) for i in p[12] : if printverbose: print(i) v2 = FreeCAD.Vector pp =[v2(v[k]) for k in i] # Add first point to end of list to close polygon pp.append(pp[0]) try: w = Part.makePolygon(pp) f = Part.Face(w) except Exception: secWireList = w.Edges[:] f = Part.makeFilledFace(Part.__sortEdges__(secWireList)) #f = make_face(v[int(i[0])],v[int(i[1])],v[int(i[2])]) faces_list.append(f) shell=Part.makeShell(faces_list) solid=Part.Solid(shell).removeSplitter() if solid.Volume < 0: solid.reverse() mypolyhed.Shape=solid p[0] = [mypolyhed] def p_projection_action(p) : 'projection_action : projection LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print('Projection') doc.recompute() p[6][0].Shape.tessellate(0.05) # Ensure the bounding box calculation is not done with the splines, which can give a bad result bbox = p[6][0].Shape.BoundBox for shape in p[6]: shape.Shape.tessellate(0.05) bbox.add(shape.Shape.BoundBox) plane = doc.addObject("Part::Plane","xy_plane_used_for_projection") plane.Length = bbox.XLength plane.Width = bbox.YLength plane.Placement = FreeCAD.Placement(FreeCAD.Vector(\ bbox.XMin,bbox.YMin,0),FreeCAD.Rotation()) if gui: plane.ViewObject.hide() if p[3]['cut'] == 'true' : obj = doc.addObject('Part::MultiCommon','projection_cut') if (len(p[6]) > 1): subobj = [fuse(p[6],"projection_cut_implicit_group")] else: subobj = p[6] obj.Shapes = [plane] + subobj if gui: subobj[0].ViewObject.hide() p[0] = [obj] else: # cut == 'false' => true projection if gui and not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetBool('usePlaceholderForUnsupported'): from PySide import QtGui QtGui.QMessageBox.critical(None, translate('OpenSCAD',"Unsupported Function") + " : " + p[1],translate('OpenSCAD',"Press OK")) else: p[0] = [placeholder(p[1],p[6],p[3])]