View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0002267 | File formats | General | public | 2015-09-16 15:46 | 2017-07-05 17:04 |
Reporter | l3iggs | Assigned To | keithsloan52 | ||
Priority | high | Severity | minor | Reproducibility | always |
Status | closed | Resolution | fixed | ||
Platform | linux | OS | Arch Linux | ||
Product Version | 0.15 | ||||
Summary | 0002267: failure to open/import CSG | ||||
Description | FreeCAD fails to import my .csg model file produced by OpenSCAD with the following error: Traceback (most recent call last): File "<string>", line 1, in <module> File "/usr/share/freecad/Mod/OpenSCAD/importCSG.py", line 85, in open processcsg(filename) File "/usr/share/freecad/Mod/OpenSCAD/importCSG.py", line 136, in processcsg result = parser.parse(f.read()) File "/usr/share/freecad/Mod/OpenSCAD/ply/yacc.py", line 265, in parse return self.parseopt_notrack(input,lexer,debug,tracking,tokenfunc) File "/usr/share/freecad/Mod/OpenSCAD/ply/yacc.py", line 971, in parseopt_notrack p.callable(pslice) File "/usr/share/freecad/Mod/OpenSCAD/importCSG.py", line 155, in p_block_list_ p[0] = p[1] + p[2] <type 'exceptions.TypeError'>: can only concatenate list (not "NoneType") to list | ||||
Steps To Reproduce | Open plate1.csg | ||||
Additional Information | File attached | ||||
Tags | CSG, DXF | ||||
FreeCAD Information | |||||
related to | 0003072 | new | FreeCAD | Please add a function exportDXF to TopoShape | |
related to | 0003035 | closed | keithsloan52 | FreeCAD | exportDXF won't export LWPOLYLINE correctly |
|
|
|
I can reproduce on FreeCAD-0.17.git201612261451.glibc2.17-x86_64.AppImage
Here is the Report output:
|
|
Forum thread: https://forum.freecadweb.org/viewtopic.php?f=8&t=22382 |
|
@l3iggs please check out https://forum.freecadweb.org/viewtopic.php?f=8&t=22382&p=173814#p173811 |
|
With the latest version of freecad-daily it no longer crashes at line 158 but still has a problem. I have reduced the file to a much smaller and simpler one that still has the same problem |
|
Okay I think I have tracked down the problem a bit more. When importCSG.py encounters a minkowski request and the two objects it is to operate on are 2D it exports the 2D objects as DXF files and then calls OpenSCAD to perform the minikowski operation requesting the output as a dxf file. i.e. Something like minkowski(){import(file = "fc-11954-672761-000016.dxf"); import(file = "fc-11954-672761-000017.dxf");} In the case of this file the dxf input files are just single lines and OpenSCAD is not producing any output file. Note sure there is anyway to fix this |
|
Okay I created a simple file with just an offset plate3.csg. If open this with FreeCAD and save it I get plate3.fcstd. If I then export that file as dxf I get plate3.dxf. All well and good but if I look at the dxf file created by importCSG using the function export from the library importDXF I get a file that looks like fc-12862-44183-000002.dxf i.e. a single line which is not right. Don't know if @yorik can help |
|
Okay I have done some further digging. The DXF file is created in OpenSCADUtils.py in function process2D_ObjectsViaOpenSCADShape using library importDXF using function export from the Draft Mod workbench. If I dump the object as a BREP file I get debugFC2.brep which is a polygon, where as the same object as a DXF file fc-03100-455637-000017.dxf does not correspond. So I conclude that there must be a bug in the importDXF export function. Now my understanding which may well be wrong is that importDXF uses the Old FreeCAD DXF library. I tried looking at the code and got nowhere. Its totally un-impenetrable as far as my coding skills go. I also understand that FreeCAD now uses a new DXF library and indeed if I export the BREP file as DXF I get a correct DXF file. If there is a way of using the new DXF library with a FreeCAD object or shape than I will gladly change the OpenSCAD code. I believe @yorik looks after the DXF library but he is a very busy person ( Does he ever sleep given the amount he does? ) maybe he could advise. See enhancement request 0003072 @Kunda1 |
|
@keithsloan52 could you give me a step-by step procedure to reproduce the problem (the wrong dxf file)? |
|
To get the problem just try and open plate2.csg see file above. The way I am debugging the problem is with modified versions of importCSG.py and OpenSCADUtils.py to output debugging information With the moded versions you will then see in report view just before the failure something like processing circle successfully exported /tmp/fc-03737-034770-000001.dxf processing Offset2D successfully exported /tmp/fc-03737-034778-000002.dxf callopenscadstring : minkowski(){import(file = "fc-03737-034770-000001.dxf"); import(file = "fc-03737-034778-000002.dxf");} Traceback (most recent call last): The BREP versions are also dumped to /tmp as debugFC1.brep & debugFC2.brep debugFC1 is the circle and debugFC2 is the wire The dxf files should match corresponding export of the brep files, but the wire does not. I am attaching debugging versions here or you can get them from branch 2D https://github.com/KeithSloan/FreeCAD_sf_master They live in /usr/lib/freecad/mod/OpenSCAD importCSG.py (44,604 bytes)
# -*- coding: utf8 -*- #*************************************************************************** #* * #* Copyright (c) 2012 Keith Sloan <keith@sloan-home.co.uk> * #* * #* 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 <keith@sloan-home.co.uk>" __url__ = ["http://www.sloan-home.co.uk/ImportCSG"] printverbose = False import FreeCAD, os, sys if FreeCAD.GuiUp: import FreeCADGui gui = True else: if printverbose: print("FreeCAD Gui not present.") gui = False import ply.lex as lex import ply.yacc as yacc import Part from OpenSCADFeatures import * from OpenSCADUtils import * params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") printverbose = params.GetBool('printVerbose',False) if open.__module__ == '__builtin__': pythonopen = open # to distinguish python built-in open function from the one declared here # Get the token map from the lexer. This is required. import tokrules from tokrules import tokens try: _encoding = QtGui.QApplication.UnicodeUTF8 def translate(context, text): "convenience function for Qt translator" from PySide import QtGui return QtGui.QApplication.translate(context, text, None, _encoding) except AttributeError: def translate(context, text): "convenience function for Qt translator" from PySide import QtGui return QtGui.QApplication.translate(context, text, None) 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) if workaroundforissue128needed(): pathName = '' #https://github.com/openscad/openscad/issues/128 #pathName = os.getcwd() #https://github.com/openscad/openscad/issues/128 else: 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 = os.path.splitext(os.path.basename(filename))[0] try: doc=FreeCAD.getDocument(docname) except NameError: doc=FreeCAD.newDocument(docname) #importgroup = doc.addObject("App::DocumentObjectGroup",groupname) if filename.lower().endswith('.scad'): tmpfile=callopenscad(filename) if workaroundforissue128needed(): pathName = '' #https://github.com/openscad/openscad/issues/128 #pathName = os.getcwd() #https://github.com/openscad/openscad/issues/128 else: 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') # No debug out otherwise Linux has protection exception parser = yacc.yacc(debug=0) if printverbose: print('Parser Loaded') # Give the lexer some input #f=open('test.scad', 'r') f = pythonopen(filename, 'r') #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) 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 if need for implicit fuse if (len(p[5]) > 1) : p[0] = [fuse(p[5],"Group")] else : 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 inplemented #please note that disabled objects usually are stript of the CSG ouput 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_twist | rotate_extrude_file | import_file1 | 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' if len(p[6]) == 0: newobj = placeholder('group',[],'{}') elif (len(p[6]) == 1 ): #single object subobj = p[6] 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']) #print subobj #print subobj[0].Label pl = subobj[0].PropertiesList #print pl if 'Shapes' in pl : v = subobj[0].Shapes.Volume if 'Base' in pl and 'Tool' in pl : v = subobj[0].Base.Shape.Volume + subobj[0].Tool.Shape.Volume # if Base & Tool present then Shape may not be set properly # e.g. Group() { polygon box } elif 'Shape' in pl : v = subobj[0].Shape.Volume if v == 0 : newobj=doc.addObject("Part::Offset2D",'Offset2D') newobj.Source = subobj[0] newobj.Value = offset if 'r' in p[3] : newobj.Join = 0 else : newobj.Join = 2 else : newobj=doc.addObject("Part::Offset",'offset') # if 3D is Shape always valid ?? newobj.Shape = subobj[0].Shape.makeOffset(offset) newobj.Document.recompute() subobj[0].ViewObject.hide() # 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 p[0] = [newobj] 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_not_supported(p): ''' not_supported : glide LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE | resize 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, unicode(translate('OpenSCAD',"Unsupported Function"))+" : "+p[1],unicode(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]: obj.ViewObject.ShapeColor =color obj.ViewObject.Transparency = transp 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] myfuse.Shape = Part.makeCompound([myfuse.Base.Shape,myfuse.Tool.Shape]) if gui: myfuse.Base.ViewObject.hide() myfuse.Tool.ViewObject.hide() return(myfuse) 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 = 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] 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] 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',[],'{}') p[0] = [mycommon] if printverbose: print("End Intersection") def process_rotate_extrude(obj): 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 = 360.00 myrev.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90)) if gui: newobj.ViewObject.hide() return(myrev) def p_rotate_extrude_action(p): 'rotate_extrude_action : rotate_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Rotate Extrude") print p[6] if (len(p[6]) > 1) : part = fuse(p[6],"Rotate Extrude Union") else : part = p[6][0] p[0] = [process_rotate_extrude(part)] 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") filen,ext =p[3]['file'] .rsplit('.',1) obj = process_import_file(filen,ext,p[3]['layer']) p[0] = [process_rotate_extrude(obj)] 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_twist(base,height,twist) : newobj=doc.addObject("Part::FeaturePython",'twist_extrude') Twist(newobj,base,height,-twist) #base is an FreeCAD Object, heigth and twist are floats 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_twist(p): 'linear_extrude_with_twist : linear_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE' if printverbose: print("Linear Extrude With Twist") h = float(p[3]['height']) if printverbose: print("Twist : ",p[3]) if 'twist' in p[3]: t = float(p[3]['twist']) else: t = 0 # 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] if t: newobj = process_linear_extrude_with_twist(obj,h,t) else: newobj = process_linear_extrude(obj,h) if p[3]['center']=='true' : center(newobj,0,0,h) p[0] = [newobj] if printverbose: print("End Linear Extrude with twist") 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]['center']=='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) else: raise ValueError("Unsupported file extension %s" % ext) return(obj) def process_mesh_file(fname,ext): import Mesh,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): import os 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]) 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") # Need to recompute to stop transformGeometry causing a crash doc.recompute() new_part = doc.addObject("Part::Feature","Matrix Deformation") # new_part.Shape = part.Base.Shape.transformGeometry(transform_matrix) 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 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]['center'] 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') 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 : # 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]['center']=='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',50) # 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 #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]['center']=='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) t = addString(t,'script',p) 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]) print pp w = Part.makePolygon(pp) f = Part.Face(w) #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') if p[3]['cut']=='true' : planedim=1e9 # large but finite #infinite planes look bad in the GUI planename='xy_plane_used_for_project_cut' obj=doc.addObject('Part::MultiCommon','projection_cut') plane = doc.getObject(planename) if not plane: plane=doc.addObject("Part::Plane",planename) plane.Length=planedim*2 plane.Width=planedim*2 plane.Placement = FreeCAD.Placement(FreeCAD.Vector(\ -planedim,-planedim,0),FreeCAD.Rotation()) if gui: plane.ViewObject.hide() 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, unicode(translate('OpenSCAD',"Unsupported Function"))+" : "+p[1],unicode(translate('OpenSCAD',"Press OK"))) else: p[0] = [placeholder(p[1],p[6],p[3])] OpenSCADUtils.py (24,641 bytes)
#*************************************************************************** #* * #* Copyright (c) 2012 Sebastian Hoogen <github@sebastianhoogen.de> * #* * #* 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__="FreeCAD OpenSCAD Workbench - Utility Fuctions" __author__ = "Sebastian Hoogen" __url__ = ["http://www.freecadweb.org"] ''' This Script includes various pyhton helper functions that are shared across the module ''' try: from PySide import QtGui _encoding = QtGui.QApplication.UnicodeUTF8 def translate(context, text): "convenience function for Qt translator" return QtGui.QApplication.translate(context, text, None, _encoding) except AttributeError: def translate(context, text): "convenience function for Qt translator" from PySide import QtGui return QtGui.QApplication.translate(context, text, None) try: import FreeCAD BaseError = FreeCAD.Base.FreeCADError except (ImportError, AttributeError): BaseError = RuntimeError class OpenSCADError(BaseError): def __init__(self,value): self.value= value #def __repr__(self): # return self.msg def __str__(self): return repr(self.value) def searchforopenscadexe(): import os,sys,subprocess if sys.platform == 'win32': testpaths = [os.path.join(os.environ.get('Programfiles(x86)','C:'),\ 'OpenSCAD\\openscad.exe')] if 'ProgramW6432' in os.environ: testpaths.append(os.path.join(os.environ.get('ProgramW6432','C:')\ ,'OpenSCAD\\openscad.exe')) for testpath in testpaths: if os.path.isfile(testpath): return testpath elif sys.platform == 'darwin': ascript = ('tell application "Finder"\n' 'POSIX path of (application file id "org.openscad.OpenSCAD"' 'as alias)\n' 'end tell') p1=subprocess.Popen(['osascript','-'],stdin=subprocess.PIPE,\ stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout,stderr = p1.communicate(ascript) if p1.returncode == 0: opathl=stdout.split('\n') if len(opathl) >=1: return opathl[0]+'Contents/MacOS/OpenSCAD' #test the default path testpath="/Applications/OpenSCAD.app/Contents/MacOS/OpenSCAD" if os.path.isfile(testpath): return testpath else: #unix p1=subprocess.Popen(['which','openscad'],stdout=subprocess.PIPE) if p1.wait() == 0: opath=p1.stdout.read().split('\n')[0] return opath def workaroundforissue128needed(): '''sets the import path depending on the OpenSCAD Verion for versions <= 2012.06.23 to the current working dir for versions above to the inputfile dir see https://github.com/openscad/openscad/issues/128''' vdate=getopenscadversion().split('-')[0] vdate=vdate.split(' ')[2].split('.') year,mon=int(vdate[0]),int(vdate[1]) return (year<2012 or (year==2012 and (mon <6 or (mon == 6 and \ (len(vdate)<3 or int(vdate[2]) <=23))))) #ifdate=int(vdate[0])+(int(vdate[1])-1)/12.0 #if len(vdate)>2: # fdate+=int((vdate[2])-1)/12.0/31.0 #return fdate < 2012.4759 def getopenscadversion(osfilename=None): import os,subprocess,time if not osfilename: import FreeCAD osfilename = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetString('openscadexecutable') if osfilename and os.path.isfile(osfilename): p=subprocess.Popen([osfilename,'-v'],\ stdout=subprocess.PIPE,stderr=subprocess.PIPE,universal_newlines=True) p.wait() stdout=p.stdout.read().strip() stderr=p.stderr.read().strip() return (stdout or stderr) def newtempfilename(): import os,time formatstr='fc-%05d-%06d-%06d' count = 0 while True: count+=1 yield formatstr % (os.getpid(),int(time.time()*100) % 1000000,count) tempfilenamegen=newtempfilename() def callopenscad(inputfilename,outputfilename=None,outputext='csg',keepname=False): '''call the open scad binary returns the filename of the result (or None), please delete the file afterwards''' import FreeCAD,os,subprocess,tempfile,time def check_output2(*args,**kwargs): kwargs.update({'stdout':subprocess.PIPE,'stderr':subprocess.PIPE}) p=subprocess.Popen(*args,**kwargs) stdoutd,stderrd = p.communicate() if p.returncode != 0: raise OpenSCADError('%s %s\n' % (stdoutd.strip(),stderrd.strip())) #raise Exception,'stdout %s\n stderr%s' %(stdoutd,stderrd) if stderrd.strip(): FreeCAD.Console.PrintWarning(stderrd+u'\n') if stdoutd.strip(): FreeCAD.Console.PrintMessage(stdoutd+u'\n') return stdoutd osfilename = FreeCAD.ParamGet(\ "User parameter:BaseApp/Preferences/Mod/OpenSCAD").\ GetString('openscadexecutable') if osfilename and os.path.isfile(osfilename): if not outputfilename: dir1=tempfile.gettempdir() if keepname: outputfilename=os.path.join(dir1,'%s.%s' % (os.path.split(\ inputfilename)[1].rsplit('.',1)[0],outputext)) else: outputfilename=os.path.join(dir1,'%s.%s' % \ (tempfilenamegen.next(),outputext)) check_output2([osfilename,'-o',outputfilename, inputfilename]) return outputfilename else: raise OpenSCADError('OpenSCAD executeable unavailable') def callopenscadstring(scadstr,outputext='csg'): '''create a tempfile and call the open scad binary returns the filename of the result (or None), please delete the file afterwards''' import os,tempfile,time dir1=tempfile.gettempdir() inputfilename=os.path.join(dir1,'%s.scad' % tempfilenamegen.next()) inputfile = open(inputfilename,'w') inputfile.write(scadstr) inputfile.close() outputfilename = callopenscad(inputfilename,outputext=outputext,\ keepname=True) os.unlink(inputfilename) return outputfilename def reverseimporttypes(): '''allows to search for supported filetypes by module''' def getsetfromdict(dict1,index): if index in dict1: return dict1[index] else: set1=set() dict1[index]=set1 return set1 importtypes={} import FreeCAD for key,value in FreeCAD.getImportType().iteritems(): if type(value) is str: getsetfromdict(importtypes,value).add(key) else: for vitem in value: getsetfromdict(importtypes,vitem).add(key) return importtypes def fcsubmatrix(m): """Extracts the 3x3 Submatrix from a freecad Matrix Object as a list of row vectors""" return [[m.A11,m.A12,m.A13],[m.A21,m.A22,m.A23],[m.A31,m.A32,m.A33]] def multiplymat(l,r): """multiply matrices given as lists of row vectors""" rt=zip(*r) #transpose r mat=[] for y in range(len(rt)): mline=[] for x in range(len(l)): mline.append(sum([le*re for le,re in zip(l[y],rt[x])])) mat.append(mline) return mat def isorthogonal(submatrix,precision=4): """checking if 3x3 Matrix is ortogonal (M*Transp(M)==I)""" prod=multiplymat(submatrix,zip(*submatrix)) return [[round(f,precision) for f in line] \ for line in prod]==[[1,0,0],[0,1,0],[0,0,1]] def detsubmatrix(s): """get the determinant of a 3x3 Matrix given as list of row vectors""" 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]-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] def isspecialorthogonalpython(submat,precision=4): return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision)==1 def isrotoinversionpython(submat,precision=4): return isorthogonal(submat,precision) and round(detsubmatrix(submat),precision)==-1 def isspecialorthogonal(mat,precision=4): return abs(mat.submatrix(3).isOrthogonal(10**(-precision))-1.0) < \ 10**(-precision) and \ abs(mat.submatrix(3).determinant()-1.0) < 10**(-precision) def decomposerotoinversion(m,precision=4): import FreeCAD rmat = [[round(f,precision) for f in line] for line in fcsubmatrix(m)] cmat = FreeCAD.Matrix() if rmat ==[[-1,0,0],[0,1,0],[0,0,1]]: cmat.scale(-1,1,1) return m*cmat,FreeCAD.Vector(1) elif rmat ==[[1,0,0],[0,-1,0],[0,0,1]]: cmat.scale(1,-1,1) return m*cmat, FreeCAD.Vector(0,1) elif rmat ==[[1,0,0],[0,1,0],[0,0,-1]]: cmat.scale(1,1,-1) return m*cmat, FreeCAD.Vector(0,0,1) else: cmat.scale(1,1,-1) return m*cmat, FreeCAD.Vector(0,0,1) def mirror2mat(nv,bv): import FreeCAD """calculate the transformation matrix of a mirror feature""" mbef=FreeCAD.Matrix() mbef.move(bv * -1) maft=FreeCAD.Matrix() maft.move(bv) return maft*vec2householder(nv)*mbef def vec2householder(nv): """calculated the householder matrix for a given normal vector""" import FreeCAD lnv=nv.dot(nv) l=2/lnv if lnv > 0 else 0 hh=FreeCAD.Matrix(nv.x*nv.x*l,nv.x*nv.y*l,nv.x*nv.z*l,0,\ nv.y*nv.x*l,nv.y*nv.y*l,nv.y*nv.z*l,0,\ nv.z*nv.x*l,nv.z*nv.y*l,nv.z*nv.z*l,0,0,0,0,0) return FreeCAD.Matrix()-hh def angneg(d): return d if (d <= 180.0) else (d-360) def shorthexfloat(f): s=f.hex() mantisse, exponent = f.hex().split('p',1) return '%sp%s' % (mantisse.rstrip('0'),exponent) def comparerotations(r1,r2): import FreeCAD '''compares two rotations a value of zero means that they are identical''' r2c=FreeCAD.Rotation(r2) r2c.invert() return r1.multiply(r2c).Angle def findbestmatchingrotation(r1): import FreeCAD vangl = \ (1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 11.25, 12.0, 13.0, 14.0, 15.0, 16.0, (180.0/11.0), 17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 22.5, 23.0, 24.0, 25.0, 26.0, 27.0, 28.0, 29.0, 30.0, 31.0, 32.0, (360.0/11.0), 33.0, 33.75, 34.0, 35.0, 36.0, 37.0, 38.0, 39.0, 40.0, 41.0, 42.0, 43.0, 44.0, 45.0, 46.0, 47.0, 48.0, 49.0,(540.0/11.0), 50.0, 51.0, (360.0/7.0), 52.0, 53.0, 54.0, 55.0, 56.0, 56.25, 57.0, 58.0, 59.0, 60.0, 61.0, 62.0, 63.0, 64.0, 65.0,(720.0/11.0), 66.0, 67.0, 67.5, 68.0, 69.0, 70.0, 71.0, 72.0, 73.0, 74.0, 75.0, 76.0, 77.0, 78.0, 78.75, 79.0, 80.0, 81.0,(900.0/11.0), 82.0, 83.0, 84.0, 85.0, 86.0, 87.0, 88.0, 89.0, 90.0, 91.0, 92.0, 93.0, 94.0, 95.0, 96.0, 97.0, 98.0,(1080.0/11.0), 99.0, 100.0, 101.0, 101.25, 102.0, (720.0/7.0), 103.0, 104.0, 105.0, 106.0, 107.0, 108.0, 109.0, 110.0, 111.0, 112.0, 112.5, 113.0, 114.0, (1260.0/11), 115.0, 116.0, 117.0, 118.0, 119.0, 120.0, 121.0, 122.0, 123.0, 123.75, 124.0, 125.0, 126.0, 127.0, 128.0, 129.0, 130.0,(1440.0/11.0), 131.0, 132.0, 133.0, 134.0, 135.0, 136.0, 137.0, 138.0, 139.0, 140.0, 141.0, 142.0, 143.0, 144.0, 145.0, 146.0, 146.25, 147.0, (1620.0/11.0), 148.0, 149.0, 150.0, 151.0, 152.0, 153.0, 154.0, (1080.0/7.0), 155.0, 156.0, 157.0, 157.5, 158.0, 159.0, 160.0, 161.0, 162.0, 163.0, (1800.0/11.0), 164.0, 165.0, 166.0, 167.0, 168.0, 168.75, 169.0, 170.0, 171.0, 172.0, 173.0, 174.0, 175.0, 176.0, 177.0,178.0, 179.0,180.0, -179.0, -178.0, -177.0, -176.0, -175.0, -174.0, -173.0, -172.0, -171.0, -170.0, -169.0, -168.75, -168.0, -167.0, -166.0, -165.0, -164.0, (-1800.0/11.0), -163.0, -162.0, -161.0, -160.0, -159.0, -158.0, -157.5, -157.0, -156.0, -155.0, (-1080.0/7.0), -154.0, -153.0, -152.0, -151.0, -150.0, -149.0, -148.0, (-1620.0/11.0), -147.0, -146.25, -146.0, -145.0, -144.0, -143.0, -142.0, -141.0, -140.0, -139.0,-138.0, -137.0, -136.0, -135.0, -134.0, -133.0, -132.0, -131.0, (-1440/11.0), -130.0, -129.0, -128.0,-127.0, -126.0, -125.0, -124.0, -123.75, -123.0, -122.0, -121.0, -120.0, -119.0, -118.0, -117.0, -116.0, -115.0,(-1260.0/11.0), -114.0, -113.0, -112.5, -112.0, -111.0, -110.0, -109.0, -108.0, -107.0, -106.0, -105.0,-104.0, -103.0,(-720.0/7.0), -102.0, -101.25, -101.0, -100.0, -99.0, (-1080.0/11.0), -98.0, -97.0, -96.0, -95.0, -94.0, -93.0, -92.0, -91.0, -90.0, -89.0, -88.0, -87.0, -86.0, -85.0, -84.0, -83.0, -82.0,(-900.0/11.0), -81.0, -80.0, -79.0, -78.75, -78.0, -77.0, -76.0, -75.0, -74.0, -73.0, -72.0, -71.0, -70.0, -69.0, -68.0, -67.5, -67.0, -66.0, (-720.0/11.0), -65.0, -64.0, -63.0, -62.0, -61.0, -60.0, -59.0, -58.0, -57.0, -56.25, -56.0, -55.0, -54.0, -53.0, -52.0,(-360.0/7.0), -51.0, -50.0, (-540.0/11.0), -49.0, -48.0, -47.0, -46.0, -45.0, -44.0, -43.0, -42.0, -41.0, -40.0, -39.0, -38.0, -37.0, -36.0, -35.0, -34.0, -33.75, -33.0,(-360.0/11.0), -32.0, -31.0, -30.0, -29.0, -28.0, -27.0, -26.0, -25.0, -24.0, -23.0, -22.5, -22.0, -21.0, -20.0, -19.0, -18.0, -17.0,(-180.0/11.0), -16.0, -15.0, -14.0, -13.0, -12.0, -11.25, -11.0, -10.0, -9.0, -8.0, -7.0, -6.0, -5.0, -4.0, -3.0, -2.0, -1.0) def tup2nvect(tup): """convert a tuple to a normalized vector""" v=FreeCAD.Vector(*tup) v.normalize() return v def wkaxes(): """well known axes for rotations""" vtupl=((1,0,0),(0,1,0),(0,0,1), (1,1,0),(1,0,1),(0,1,1),(-1,1,0),(-1,0,1),(0,1,-1), (1,1,1),(1,1,-1),(1,-1,1),(-1,1,1)) return tuple(tup2nvect(tup) for tup in vtupl) bestrot=FreeCAD.Rotation() dangle = comparerotations(r1,bestrot) for axis in wkaxes(): for angle in vangl: for axissign in (1.0,-1.0): r2=FreeCAD.Rotation(axis*axissign,angle) dangletest = comparerotations(r1,r2) if dangletest < dangle: bestrot = r2 dangle = dangletest return (bestrot,dangle) def roundrotation(rot,maxangulardistance=1e-5): '''guess the rotation axis and angle for a rotation recreated from rounded floating point values (from a quaterion or transformation matrix)''' def teststandardrot(r1,maxangulardistance=1e-5): '''test a few common rotations beforehand''' import FreeCAD,itertools eulers = [] for angle in (90,-90,180,45,-45,135,-135): for euler in itertools.permutations((0,0,angle)): eulers.append(euler) for euler in itertools.product((0,45,90,135,180,-45,-90,-135),repeat=3): eulers.append(euler) for euler in eulers: r2 = FreeCAD.Rotation(*euler) if comparerotations(r1,r2) < maxangulardistance: return r2 if rot.isNull(): return rot firstguess = teststandardrot(rot,maxangulardistance) if firstguess is not None: return firstguess #brute force bestguess,angulardistance = findbestmatchingrotation(rot) if angulardistance < maxangulardistance: #use guess return bestguess else: #use original return rot def callopenscadmeshstring(scadstr): """Call OpenSCAD and return the result as a Mesh""" import Mesh,os tmpfilename=callopenscadstring(scadstr,'stl') newmesh=Mesh.Mesh() newmesh.read(tmpfilename) try: os.unlink(tmpfilename) except OSError: pass return newmesh def meshopinline(opname,iterable1): """uses OpenSCAD to combine meshes takes the name of the CGAL operation and an iterable (tuple,list) of FreeCAD Mesh objects includes all the mesh data in the SCAD file """ from exportCSG import mesh2polyhedron return callopenscadmeshstring('%s(){%s}' % (opname,' '.join(\ (mesh2polyhedron(meshobj) for meshobj in iterable1)))) def meshoptempfile(opname,iterable1): """uses OpenSCAD to combine meshes takes the name of the CGAL operation and an iterable (tuple,list) of FreeCAD Mesh objects uses stl files to supply the mesh data """ import os,tempfile dir1=tempfile.gettempdir() filenames = [] for mesh in iterable1: outputfilename=os.path.join(dir1,'%s.stl' % tempfilenamegen.next()) mesh.write(outputfilename) filenames.append(outputfilename) #absolute path causes error. We rely that the scad file will be in the dame tmpdir meshimports = ' '.join("import(file = \"%s\");" % \ #filename \ os.path.split(filename)[1] for filename in filenames) result = callopenscadmeshstring('%s(){%s}' % (opname,meshimports)) for filename in filenames: try: os.unlink(filename) except OSError: pass return result def meshoponobjs(opname,inobjs): """ takes a string (operation name) and a list of Feature Objects returns a mesh and a list of objects that were used Part Objects will be meshed """ objs=[] meshes=[] for obj in inobjs: if obj.isDerivedFrom('Mesh::Feature'): objs.append(obj) meshes.append(obj.Mesh) elif obj.isDerivedFrom('Part::Feature'): #mesh the shape import FreeCAD params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") objs.append(obj) if False: # disabled due to issue 1292 import MeshPart meshes.append(MeshPart.meshFromShape(obj.Shape,params.GetFloat(\ 'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\ params.GetFloat('meshlocallen',0.0),\ params.GetFloat('meshdeflection',0.0))) else: import Mesh meshes.append(Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\ 'meshmaxlength',1.0)))) else: pass #neither a mesh nor a part if len(objs) > 0: return (meshoptempfile(opname,meshes),objs) else: return (None,[]) def process2D_ObjectsViaOpenSCADShape(ObjList,Operation,doc): import FreeCAD,importDXF import os,tempfile dir1=tempfile.gettempdir() filenames = [] for item in ObjList : outputfilename=os.path.join(dir1,'%s.dxf' % tempfilenamegen.next()) importDXF.export([item],outputfilename,True,True) filenames.append(outputfilename) dxfimports = ' '.join("import(file = \"%s\");" % \ #filename \ os.path.split(filename)[1] for filename in filenames) print "callopenscadstring : " + '%s(){%s}' % (Operation,dxfimports) tmpfilename = callopenscadstring('%s(){%s}' % (Operation,dxfimports),'dxf') from OpenSCAD2Dgeom import importDXFface # TBD: assure the given doc is active face = importDXFface(tmpfilename,None,None) #clean up filenames.append(tmpfilename) #delete the ouptut file as well try: os.unlink(tmpfilename) except OSError: pass return face def process2D_ObjectsViaOpenSCAD(ObjList,Operation,doc=None): import FreeCAD doc = doc or FreeCAD.activeDocument() face=process2D_ObjectsViaOpenSCADShape(ObjList,Operation,doc) obj=doc.addObject('Part::Feature',Operation) obj.Shape=face # Hide Children if FreeCAD.GuiUp: for index in ObjList : index.ViewObject.hide() return(obj) def process3D_ObjectsViaOpenSCADShape(ObjList,Operation,maxmeshpoints=None): import FreeCAD,Mesh,Part params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD") if False: # disabled due to issue 1292 import MeshPart meshes = [MeshPart.meshFromShape(obj.Shape,params.GetFloat(\ 'meshmaxlength',1.0), params.GetFloat('meshmaxarea',0.0),\ params.GetFloat('meshlocallen',0.0),\ params.GetFloat('meshdeflection',0.0)) for obj in ObjList] else: meshes = [Mesh.Mesh(obj.Shape.tessellate(params.GetFloat(\ 'meshmaxlength',1.0))) for obj in ObjList] if max(mesh.CountPoints for mesh in meshes) < \ (maxmeshpoints or params.GetInt('tempmeshmaxpoints',5000)): stlmesh = meshoptempfile(Operation,meshes) sh=Part.Shape() sh.makeShapeFromMesh(stlmesh.Topology,0.1) solid = Part.Solid(sh) solid=solid.removeSplitter() if solid.Volume < 0: solid.complement() return solid def process3D_ObjectsViaOpenSCAD(doc,ObjList,Operation): solid = process3D_ObjectsViaOpenSCADShape(ObjList,Operation) if solid is not None: obj=doc.addObject('Part::Feature',Operation) #non parametric objec obj.Shape=solid#.removeSplitter() if FreeCAD.GuiUp: for index in ObjList : index.ViewObject.hide() return(obj) def process_ObjectsViaOpenSCADShape(doc,children,name,maxmeshpoints=None): if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \ for obj in children): #KS temp debug export x = 1 for obj in children : obj.Shape.exportBrep("/tmp/debugFC"+str(x)+".brep") x = x + 1 return process2D_ObjectsViaOpenSCADShape(children,name,doc) elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \ for obj in children): return process3D_ObjectsViaOpenSCADShape(children,name,maxmeshpoints) else: import FreeCAD FreeCAD.Console.PrintError( unicode(translate('OpenSCAD',\ "Error all shapes must be either 2D or both must be 3D"))+u'\n') def process_ObjectsViaOpenSCAD(doc,children,name): if all((not obj.Shape.isNull() and obj.Shape.Volume == 0) \ for obj in children): return process2D_ObjectsViaOpenSCAD(children,name,doc) elif all((not obj.Shape.isNull() and obj.Shape.Volume > 0) \ for obj in children): return process3D_ObjectsViaOpenSCAD(doc,children,name) else: import FreeCAD FreeCAD.Console.PrintError( unicode(translate('OpenSCAD',\ "Error all shapes must be either 2D or both must be 3D"))+u'\n') def removesubtree(objs): def addsubobjs(obj,toremoveset): toremove.add(obj) for subobj in obj.OutList: addsubobjs(subobj,toremoveset) import FreeCAD toremove=set() for obj in objs: addsubobjs(obj,toremove) checkinlistcomplete =False while not checkinlistcomplete: for obj in toremove: if (obj not in objs) and (frozenset(obj.InList) - toremove): toremove.remove(obj) break else: checkinlistcomplete = True for obj in toremove: obj.Document.removeObject(obj.Name) def applyPlacement(shape): if shape.Placement.isNull(): return shape else: import Part if shape.ShapeType == 'Solid': return Part.Solid(shape.childShapes()[0]) elif shape.ShapeType == 'Face': return Part.Face(shape.childShapes()) elif shape.ShapeType == 'Compound': return Part.Compound(shape.childShapes()) elif shape.ShapeType == 'Wire': return Part.Wire(shape.childShapes()) elif shape.ShapeType == 'Shell': return Part.Shell(shape.childShapes()) else: return Part.Compound([shape]) |
|
The code to output the Brep's is around line 549 in OpenSCADUtils.py and the output of the DXF is at line 483 |
|
OpenSCAD does not support POLYLINE so have to use LWPOLYLINE. The Draft Library importDXF.export has option for lwPoly but this does not appear to be working i.e. for a three sided polygon ( triangle ) it produces just a line def export(objectslist,filename,nospline=False,lwPoly=False): "called when freecad exports a file. If nospline=True, bsplines are exported as straight segs lwPoly=True for OpenSCAD DXF" But does explain why one is able to load brep versions and correctly export as DXF as the default is to call export with lwPoly=False @yorik any ideas what has changed with LWPOLYLINE and why this is no longer working. |
|
Not sure if it helps but loaded the debugFC2.brep file into FreeCAD and exported as a DXF file ( with Polyline ) Polyline.dxf. The bad file if one uses the FreeCAD importDXF.export with lwpoly flag is Bad-LWpolyline.dxf. Polyline.dxf converted to v13 using QCAD is QCAD-Polygon.dxf and has the three sided polygon as LWPOLYLINE @yorik |
|
Okay I changed Draft importDXF.py around line 1730 to
And getWire to output vertex's and the stdout then has which looks good, but the output dxf file only contains the first vertex
So I conclude that the problem is in dxflibrary.LwPolyline which is only outputting the first vertex in the passed list. SO I altered dxfLibrary around line 450 to
And the print of point only outputs one value
Should I try posting in the forum? @Kunda1 |
|
Okay spotted the problem in dxfLibrary.py Code looks like
The return result is indented to far so that it returns on the first value. It should look like |
|
Okay Pull request submitted for Yorik's dxflibrary @Yorik I note that the readme says no longer used by FreeCAD, but it is still used by OpenSCAD workbench importCSG |
|
@yorik can you weigh in? Seems Keith has sent a PR o your DXF branch on your personal repo. |
|
The PR is merged |
|
Marking as fixed. Users need to set the preference for FreeCAD to load the dxf library to pick up latest version. Thanks Keith! |
|
Closing |
Date Modified | Username | Field | Change |
---|---|---|---|
2015-09-16 15:46 | l3iggs | New Issue | |
2015-09-16 15:46 | l3iggs | File Added: plate1.csg | |
2017-01-16 09:04 | Kunda1 | Project | FreeCAD => File formats |
2017-01-16 09:04 | Kunda1 | Category | Bug => General |
2017-02-02 17:30 | Kunda1 | Note Added: 0008175 | |
2017-02-02 17:31 | Kunda1 | Note Edited: 0008175 | |
2017-02-02 17:31 | Kunda1 | Status | new => confirmed |
2017-02-02 17:32 | Kunda1 | Tag Attached: CSG | |
2017-05-11 03:28 | Kunda1 | Note Added: 0008949 | |
2017-05-11 21:21 | Kunda1 | Note Added: 0008964 | |
2017-05-12 14:58 | keithsloan52 | File Added: plate2.csg | |
2017-05-12 14:58 | keithsloan52 | Note Added: 0008975 | |
2017-05-12 15:25 | keithsloan52 | Note Added: 0008976 | |
2017-05-12 16:20 | keithsloan52 | File Added: plate3.csg | |
2017-05-12 16:20 | keithsloan52 | File Added: plate3.fcstd | |
2017-05-12 16:20 | keithsloan52 | File Added: plate3.dxf | |
2017-05-12 16:20 | keithsloan52 | File Added: fc-12862-441813-000002.dxf | |
2017-05-12 16:20 | keithsloan52 | Note Added: 0008977 | |
2017-06-07 11:50 | keithsloan52 | File Added: debugFC2.brep | |
2017-06-07 11:50 | keithsloan52 | File Added: fc-03100-455637-000017.dxf | |
2017-06-07 11:50 | keithsloan52 | Note Added: 0009304 | |
2017-06-07 12:09 | Kunda1 | Note Edited: 0009304 | |
2017-06-07 12:09 | Kunda1 | Relationship added | related to 0003072 |
2017-06-07 12:10 | Kunda1 | Tag Attached: DXF | |
2017-06-07 12:10 | Kunda1 | Priority | normal => high |
2017-06-08 19:12 | yorik | Note Added: 0009313 | |
2017-06-08 19:49 | keithsloan52 | File Added: plate2-2.csg | |
2017-06-08 19:49 | keithsloan52 | File Added: importCSG.py | |
2017-06-08 19:49 | keithsloan52 | File Added: OpenSCADUtils.py | |
2017-06-08 19:49 | keithsloan52 | Note Added: 0009314 | |
2017-06-08 20:06 | keithsloan52 | Note Added: 0009315 | |
2017-06-13 12:49 | keithsloan52 | Note Added: 0009354 | |
2017-06-13 13:08 | keithsloan52 | Note Edited: 0009354 | |
2017-06-15 16:26 | Kunda1 | Relationship added | related to 0003035 |
2017-06-16 09:24 | keithsloan52 | File Added: Bad-LWpolyline.dxf | |
2017-06-16 09:24 | keithsloan52 | File Added: Polyline.dxf | |
2017-06-16 09:24 | keithsloan52 | File Added: QCAD_Polygon.dxf | |
2017-06-16 09:24 | keithsloan52 | Note Added: 0009405 | |
2017-06-16 11:33 | Kunda1 | Note Edited: 0009405 | |
2017-06-20 03:39 | keithsloan52 | Note Added: 0009462 | |
2017-06-20 04:00 | keithsloan52 | Note Added: 0009463 | |
2017-06-20 04:22 | keithsloan52 | Note Added: 0009464 | |
2017-06-25 15:47 | Kunda1 | Note Added: 0009526 | |
2017-06-26 14:06 | yorik | Note Added: 0009543 | |
2017-06-26 21:41 | Kunda1 | Assigned To | => keithsloan52 |
2017-06-26 21:41 | Kunda1 | Status | confirmed => resolved |
2017-06-26 21:41 | Kunda1 | Resolution | open => fixed |
2017-06-26 21:41 | Kunda1 | Note Added: 0009553 | |
2017-07-05 17:04 | Kunda1 | Note Added: 0009670 | |
2017-07-05 17:04 | Kunda1 | Status | resolved => closed |