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 |
FreeCAD