3
#***************************************************************************
4
#* Copyright (c) 2012 Keith Sloan <keith@sloan-home.co.uk> *
6
#* This program is free software; you can redistribute it and/or modify *
7
#* it under the terms of the GNU Lesser General Public License (LGPL) *
8
#* as published by the Free Software Foundation; either version 2 of *
9
#* the License, or (at your option) any later version. *
10
#* for detail see the LICENCE text file. *
12
#* This program is distributed in the hope that it will be useful, *
13
#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
14
#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
15
#* GNU Library General Public License for more details. *
17
#* You should have received a copy of the GNU Library General Public *
18
#* License along with this program; if not, write to the Free Software *
19
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
24
#* Thanks to shoogen on the FreeCAD forum and Peter Li *
25
#* for programming advice and some code. *
28
#***************************************************************************
29
__title__ = "FreeCAD OpenSCAD Workbench - CSG importer"
30
__author__ = "Keith Sloan <keith@sloan-home.co.uk>"
31
__url__ = ["http://www.sloan-home.co.uk/ImportCSG"]
44
from OpenSCADFeatures import *
45
from OpenSCADUtils import *
49
import ply.yacc as yacc
51
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD")
52
printverbose = params.GetBool('printVerbose', False)
57
if printverbose: print("FreeCAD Gui not present.")
62
original_root_objects = []
64
# Get the token map from the lexer. This is required.
66
from tokrules import tokens
67
from builtins import open as pyopen
69
translate = FreeCAD.Qt.translate
72
def shallHide(subject):
73
for obj in subject.OutListRecursive:
74
if "Matrix_Union" in str(obj.FullName):
76
if "Extrude" in str(obj.FullName):
81
def setColorRecursively(obj, color, transp):
83
For some reason a part made by cutting or fusing other parts do not have a color
84
unless its constituents are also colored. This code sets colors for those
85
constituents unless already set elsewhere.
87
obj.ViewObject.ShapeColor = color
88
obj.ViewObject.Transparency = transp
89
# Add any other relevant features to this list
90
boolean_features = ["Part::Fuse", "Part::MultiFuse", "Part::Cut",
91
"Part::Common", "Part::MultiCommon"]
92
if obj.TypeId in boolean_features:
93
for currentObject in obj.OutList:
94
if printverbose: print(f"Fixing up colors for: {currentObject.FullName}")
95
if currentObject not in hassetcolor:
96
setColorRecursively(currentObject, color, transp)
100
# After an import, only the remaining root objects that we created should be visible, not any
101
# of their individual component objects. But make sure to only handle the ones we just imported,
102
# not anything that already existed. And objects that exist at the toplevel without any
103
# children are ignored.
104
for root_object in FreeCAD.ActiveDocument.RootObjects:
105
if root_object not in original_root_objects:
106
root_object.ViewObject.Visibility = True
107
for obj in root_object.OutListRecursive:
108
obj.ViewObject.Visibility = False
112
"called when freecad opens a file."
115
docname = os.path.splitext(os.path.basename(filename))[0]
116
doc = FreeCAD.newDocument(docname)
117
if filename.lower().endswith('.scad'):
118
tmpfile = callopenscad(filename)
119
pathName = os.path.dirname(os.path.normpath(filename))
126
pathName = os.path.dirname(os.path.normpath(filename))
131
def insert(filename, docname):
132
"called when freecad imports a file"
135
groupname_unused = os.path.splitext(os.path.basename(filename))[0]
137
doc = FreeCAD.getDocument(docname)
138
for obj in doc.RootObjects:
139
original_root_objects.append(obj)
141
doc = FreeCAD.newDocument(docname)
142
#importgroup = doc.addObject("App::DocumentObjectGroup",groupname)
143
if filename.lower().endswith('.scad'):
144
tmpfile = callopenscad(filename)
145
pathName = os.path.dirname(os.path.normpath(filename))
152
pathName = os.path.dirname(os.path.normpath(filename))
155
def processcsg(filename):
158
if printverbose: print('ImportCSG Version 0.6a')
160
if printverbose: print('Start Lex')
161
lex.lex(module=tokrules)
162
if printverbose: print('End Lex')
165
if printverbose: print('Load Parser')
166
# Disable generation of debug ('parser.out') and table cache ('parsetab.py'),
167
# as it requires a writable location
168
parser = yacc.yacc(debug=False, write_tables=False)
169
if printverbose: print('Parser Loaded')
170
# Give the lexer some input
171
#f=open('test.scad', 'r')
172
f = io.open(filename, 'r', encoding="utf8")
173
#lexer.input(f.read())
175
if printverbose: print('Start Parser')
176
# Swap statements to enable Parser debugging
177
#result = parser.parse(f.read(),debug=1)
178
result = parser.parse(f.read())
186
alreadyhidden.clear()
187
FreeCAD.Console.PrintMessage('End processing CSG file\n')
193
block_list : statement
194
| block_list statement
196
| block_list statementwithmod
198
#if printverbose: print("Block List")
199
#if printverbose: print(p[1])
201
if printverbose: print(p[2])
205
#if printverbose: print("End Block List")
208
def p_render_action(p):
209
'render_action : render LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
210
if printverbose: print("Render (ignored)")
214
def p_group_action1(p):
215
'group_action1 : group LPAREN RPAREN OBRACE block_list EBRACE'
216
if printverbose: print("Group")
217
# Test need for implicit fuse
222
if printverbose: print('Fuse Group')
225
p[0] = [fuse(p[5], "Group")]
227
if printverbose: print(f"Group {p[5]} type {type(p[5])}")
232
def p_group_action2(p):
233
'group_action2 : group LPAREN RPAREN SEMICOL'
234
if printverbose: print("Group2")
246
# 'string : QUOTE ID QUOTE'
250
def p_stripped_string(p):
251
'stripped_string : STRING'
252
p[0] = p[1].strip('"')
269
'''anymodifier : MODIFIERBACK
274
# just return the plain modifier for now
275
# has to be changed when the modifiers are implemented
276
# please note that disabled objects usually are stripped of the CSG output during compilation
280
def p_statementwithmod(p):
281
'''statementwithmod : anymodifier statement'''
282
# ignore the modifiers but add them to the label
285
if hasattr(obj, 'Label'):
286
obj.Label = modifier + obj.Label
298
| polygon_action_nopath
299
| polygon_action_plus_path
306
'2d_point : OSQUARE NUMBER COMMA NUMBER ESQUARE'
308
if printverbose: print("2d Point")
309
p[0] = [float(p[2]), float(p[4])]
312
def p_points_list_2d(p):
314
points_list_2d : 2d_point COMMA
315
| points_list_2d 2d_point COMMA
316
| points_list_2d 2d_point
320
# print("Start List")
329
#if printverbose: print(p[0])
333
'3d_point : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
335
if printverbose: print("3d point")
336
p[0] = [p[2], p[4], p[6]]
339
def p_points_list_3d(p):
341
points_list_3d : 3d_point COMMA
342
| points_list_3d 3d_point COMMA
343
| points_list_3d 3d_point
346
if printverbose: print("Start List")
347
if printverbose: print(p[1])
350
if printverbose: print(p[1])
351
if printverbose: print(p[2])
354
if printverbose: print(p[0])
358
path_points : NUMBER COMMA
359
| path_points NUMBER COMMA
362
#if printverbose: print("Path point")
364
#if printverbose: print('Start list')
365
#if printverbose: print(p[1])
368
#if printverbose: print(p[1])
369
#if printverbose: print(len(p[1]))
370
#if printverbose: print(p[2])
371
p[1].append(int(p[2]))
373
#if printverbose: print(p[0])
377
'path_list : OSQUARE path_points ESQUARE'
378
#if printverbose: print('Path List ')
379
#if printverbose: print(p[2])
386
| path_set COMMA path_list
388
#if printverbose: print('Path Set')
389
#if printverbose: print(len(p))
395
#if printverbose: print(p[0])
399
operation : difference_action
400
| intersection_action
402
| rotate_extrude_action
403
| linear_extrude_with_transform
404
| rotate_extrude_file
415
def placeholder(name, children, arguments):
416
from OpenSCADFeatures import OpenSCADPlaceholder
417
newobj=doc.addObject("Part::FeaturePython",name)
418
OpenSCADPlaceholder(newobj, children, str(arguments))
420
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
421
GetBool('useViewProviderTree'):
422
from OpenSCADFeatures import ViewProviderTree
423
ViewProviderTree(newobj.ViewObject)
425
newobj.ViewObject.Proxy = 0
426
#don't hide the children
429
def CGALFeatureObj(name, children,arguments=[]):
430
myobj=doc.addObject("Part::FeaturePython", name)
431
CGALFeature(myobj, name, children, str(arguments))
433
for subobj in children:
434
subobj.ViewObject.hide()
435
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
436
GetBool('useViewProviderTree'):
437
from OpenSCADFeatures import ViewProviderTree
438
ViewProviderTree(myobj.ViewObject)
440
myobj.ViewObject.Proxy = 0
443
def p_offset_action(p):
444
'offset_action : offset LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
447
newobj = placeholder('group',[],'{}')
448
elif (len(p[6]) == 1 ): #single object
451
subobj = fuse(p[6],"Offset Union")
453
offset = float(p[3]['r'])
455
offset = float(p[3]['delta'])
456
checkObjShape(subobj)
457
if subobj.Shape.Volume == 0 :
458
newobj = doc.addObject("Part::Offset2D",'Offset2D')
459
newobj.Source = subobj
460
newobj.Value = offset
466
newobj = doc.addObject("Part::Offset",'offset')
467
newobj.Shape = subobj[0].Shape.makeOffset(offset)
468
newobj.Document.recompute()
470
subobj.ViewObject.hide()
471
# if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
472
# GetBool('useViewProviderTree'):
473
# from OpenSCADFeatures import ViewProviderTree
474
# ViewProviderTree(newobj.ViewObject)
476
# newobj.ViewObject.Proxy = 0
479
def checkObjShape(obj):
480
if printverbose: print('Check Object Shape')
481
if hasattr(obj, 'Shape'):
482
if obj.Shape.isNull():
483
if printverbose: print('Shape is Null - recompute')
485
if obj.Shape.isNull():
486
print(f'Recompute failed : {obj.Name}')
488
if hasattr(obj, 'Name'):
489
print(f"obj {obj.Name} has no Shape")
491
print(f"obj {obj} has no Name & Shape")
494
'hull_action : hull LPAREN RPAREN OBRACE block_list EBRACE'
495
p[0] = [ CGALFeatureObj(p[1],p[5]) ]
497
def p_minkowski_action(p):
499
minkowski_action : minkowski LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'''
500
p[0] = [ CGALFeatureObj(p[1],p[6],p[3]) ]
502
def p_resize_action(p):
504
resize_action : resize LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE '''
505
new_size = p[3]['newsize']
508
if p[6][0].Shape.isNull():
510
p[6][0].Shape.tessellate(0.05)
511
old_bbox = p[6][0].Shape.BoundBox
512
old_size = [old_bbox.XLength, old_bbox.YLength, old_bbox.ZLength]
515
new_size[r] = new_size[0]
516
if new_size[r] == '0':
517
new_size[r] = str(old_size[r])
519
# Calculate a transform matrix from the current bounding box to the new one:
520
transform_matrix = FreeCAD.Matrix()
522
scale = FreeCAD.Vector(float(new_size[0])/old_size[0],
523
float(new_size[1])/old_size[1],
524
float(new_size[2])/old_size[2])
526
transform_matrix.scale(scale)
528
new_part=doc.addObject("Part::FeaturePython",'Matrix Deformation')
529
new_part.Shape = p[6][0].Shape.transformGeometry(transform_matrix)
531
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
532
GetBool('useViewProviderTree'):
533
from OpenSCADFeatures import ViewProviderTree
534
ViewProviderTree(new_part.ViewObject)
536
new_part.ViewObject.Proxy = 0
537
p[6][0].ViewObject.hide()
541
def p_not_supported(p):
543
not_supported : glide LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE
544
| subdiv LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE
546
if gui and not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
547
GetBool('usePlaceholderForUnsupported'):
548
from PySide import QtGui
549
QtGui.QMessageBox.critical(None, translate('OpenSCAD',"Unsupported Function")+" : "+p[1],translate('OpenSCAD',"Press OK"))
551
p[0] = [placeholder(p[1],p[6],p[3])]
554
'size_vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
555
if printverbose: print("size vector")
556
p[0] = [p[2],p[4],p[6]]
558
def p_keywordargument(p):
559
'''keywordargument : ID EQ boolean
564
| text EQ stripped_string
565
| ID EQ stripped_string
568
if printverbose: print(p[0])
570
def p_keywordargument_list(p):
572
keywordargument_list : keywordargument
573
| keywordargument_list COMMA keywordargument
576
p[0] = {p[1][0] : p[1][1]}
578
p[1][p[3][0]] = p[3][1]
581
def p_color_action(p):
582
'color_action : color LPAREN vector RPAREN OBRACE block_list EBRACE'
584
if printverbose: print("Color")
585
color = tuple([float(f) for f in p[3][:3]]) #RGB
586
transp = 100 - int(math.floor(100*float(p[3][3]))) #Alpha
590
if "Group" in obj.FullName:
591
obj.ViewObject.Visibility=False
592
alreadyhidden.append(obj)
593
setColorRecursively(obj, color, transp)
594
hassetcolor.append(obj)
597
# Error rule for syntax errors
599
if printverbose: print("Syntax error in input!")
600
if printverbose: print(p)
604
if printverbose: print("Fuse")
605
if printverbose: print(lst)
607
myfuse = placeholder('group',[],'{}')
612
if printverbose: print("Multi Fuse")
613
myfuse = doc.addObject('Part::MultiFuse',name)
616
for subobj in myfuse.Shapes:
617
subobj.ViewObject.hide()
619
if printverbose: print("Single Fuse")
620
myfuse = doc.addObject('Part::Fuse',name)
623
checkObjShape(myfuse.Base)
624
checkObjShape(myfuse.Tool)
625
myfuse.Shape = myfuse.Base.Shape.fuse(myfuse.Tool.Shape)
627
myfuse.Base.ViewObject.hide()
628
myfuse.Tool.ViewObject.hide()
629
myfuse.Placement = FreeCAD.Placement()
632
def p_empty_union_action(p):
633
'union_action : union LPAREN RPAREN SEMICOL'
634
if printverbose: print("empty union")
635
newpart = fuse([],p[1])
636
if printverbose: print("Push Union Result")
638
if printverbose: print("End Union")
640
def p_union_action(p):
641
'union_action : union LPAREN RPAREN OBRACE block_list EBRACE'
642
if printverbose: print("union")
643
newpart = fuse(p[5],p[1])
644
if printverbose: print("Push Union Result")
646
if printverbose: print("End Union")
648
def p_difference_action(p):
649
'difference_action : difference LPAREN RPAREN OBRACE block_list EBRACE'
651
if printverbose: print("difference")
652
if printverbose: print(len(p[5]))
653
if printverbose: print(p[5])
654
if (len(p[5]) == 0 ): #nochild
655
mycut_unused = placeholder('group',[],'{}')
656
elif (len(p[5]) == 1 ): #single object
660
mycut = doc.addObject('Part::Cut',p[1])
662
# Can only Cut two objects do we need to fuse extras
664
if printverbose: print("Need to Fuse Extra First")
665
mycut.Tool = fuse(p[5][1:],'union')
668
checkObjShape(mycut.Tool)
670
mycut.Base.ViewObject.hide()
671
mycut.Tool.ViewObject.hide()
672
if printverbose: print("Push Resulting Cut")
674
if printverbose: print("End Cut")
676
def p_intersection_action(p):
677
'intersection_action : intersection LPAREN RPAREN OBRACE block_list EBRACE'
679
if printverbose: print("intersection")
680
# Is this Multi Common
682
if printverbose: print("Multi Common")
683
mycommon = doc.addObject('Part::MultiCommon',p[1])
684
mycommon.Shapes = p[5]
686
for subobj in mycommon.Shapes:
687
subobj.ViewObject.hide()
688
elif (len(p[5]) == 2):
689
if printverbose: print("Single Common")
690
mycommon = doc.addObject('Part::Common',p[1])
691
mycommon.Base = p[5][0]
692
mycommon.Tool = p[5][1]
693
checkObjShape(mycommon.Base)
694
checkObjShape(mycommon.Tool)
696
mycommon.Base.ViewObject.hide()
697
mycommon.Tool.ViewObject.hide()
698
elif (len(p[5]) == 1):
701
mycommon = placeholder('group',[],'{}')
702
mycommon.Shape = mycommon.Base.Shape.common(mycommon.Tool.Shape)
704
if printverbose: print("End Intersection")
706
def process_rotate_extrude(obj, angle):
707
newobj=doc.addObject("Part::FeaturePython",'RefineRotateExtrude')
708
RefineShape(newobj,obj)
710
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
711
GetBool('useViewProviderTree'):
712
from OpenSCADFeatures import ViewProviderTree
713
ViewProviderTree(newobj.ViewObject)
715
newobj.ViewObject.Proxy = 0
716
obj.ViewObject.hide()
717
myrev = doc.addObject("Part::Revolution","RotateExtrude")
718
myrev.Source = newobj
719
myrev.Axis = (0.00,1.00,0.00)
720
myrev.Base = (0.00,0.00,0.00)
722
myrev.Placement = FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90))
724
newobj.ViewObject.hide()
727
def process_rotate_extrude_prism(obj, angle, n):
728
newobj=doc.addObject("Part::FeaturePython",'PrismaticToroid')
729
PrismaticToroid(newobj, obj, angle, n)
730
newobj.Placement=FreeCAD.Placement(FreeCAD.Vector(),FreeCAD.Rotation(0,0,90))
732
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
733
GetBool('useViewProviderTree'):
734
from OpenSCADFeatures import ViewProviderTree
735
ViewProviderTree(newobj.ViewObject)
737
newobj.ViewObject.Proxy = 0
738
obj.ViewObject.hide()
741
def p_rotate_extrude_action(p):
742
'rotate_extrude_action : rotate_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
743
if printverbose: print("Rotate Extrude")
746
angle = float(p[3]['angle'])
747
n = int(round(float(p[3]['$fn'])))
748
fnmax = FreeCAD.ParamGet(\
749
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
750
GetInt('useMaxFN', 16)
752
part = fuse(p[6],"Rotate Extrude Union")
756
if n < 3 or fnmax != 0 and n > fnmax:
757
p[0] = [process_rotate_extrude(part,angle)]
759
p[0] = [process_rotate_extrude_prism(part,angle,n)]
760
if printverbose: print("End Rotate Extrude")
762
def p_rotate_extrude_file(p):
763
'rotate_extrude_file : rotate_extrude LPAREN keywordargument_list RPAREN SEMICOL'
764
if printverbose: print("Rotate Extrude File")
767
angle = float(p[3]['angle'])
768
filen,ext = p[3]['file'] .rsplit('.',1)
769
obj = process_import_file(filen,ext,p[3]['layer'])
770
n = int(round(float(p[3]['$fn'])))
771
fnmax = FreeCAD.ParamGet(\
772
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
773
GetInt('useMaxFN', 16)
775
if n < 3 or fnmax != 0 and n > fnmax:
776
p[0] = [process_rotate_extrude(obj,angle)]
778
p[0] = [process_rotate_extrude_prism(obj,angle,n)]
779
if printverbose: print("End Rotate Extrude File")
781
def process_linear_extrude(obj,h) :
783
newobj = doc.addObject("Part::FeaturePython",'RefineLinearExtrude')
784
RefineShape(newobj,obj)#mylinear)
786
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
787
GetBool('useViewProviderTree'):
788
from OpenSCADFeatures import ViewProviderTree
789
ViewProviderTree(newobj.ViewObject)
791
newobj.ViewObject.Proxy = 0
792
obj.ViewObject.hide()
793
#mylinear.ViewObject.hide()
794
mylinear = doc.addObject("Part::Extrusion","LinearExtrude")
795
mylinear.Base = newobj #obj
796
mylinear.Dir = (0,0,h)
797
mylinear.Placement=FreeCAD.Placement()
798
# V17 change to False mylinear.Solid = True
799
mylinear.Solid = False
801
newobj.ViewObject.hide()
804
def process_linear_extrude_with_transform(base,height,twist,scale) :
805
newobj = doc.addObject("Part::FeaturePython",'transform_extrude')
806
# base is a FreeCAD Object, height & twist are floats, scale is a two-component vector of floats
807
Twist(newobj,base,height,-twist,scale)
809
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
810
GetBool('useViewProviderTree'):
811
from OpenSCADFeatures import ViewProviderTree
812
ViewProviderTree(newobj.ViewObject)
814
newobj.ViewObject.Proxy = 0
815
#import ViewProviderTree from OpenSCADFeatures
816
#ViewProviderTree(obj.ViewObject)
819
def p_linear_extrude_with_transform(p):
820
'linear_extrude_with_transform : linear_extrude LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
821
if printverbose: print("Linear Extrude With Transform")
822
h = float(p[3]['height'])
823
if printverbose: print("Height : ",h)
827
if isinstance(p[3]['scale'], str):
828
s = [float(p[3]['scale']), float(p[3]['scale'])]
830
s = [float(p[3]['scale'][0]), float(p[3]['scale'][1])]
831
if printverbose: print ("Scale: " + str(s))
833
t = float(p[3]['twist'])
834
if printverbose: print("Twist : ",t)
835
# Test if null object like from null text
836
if (len(p[6]) == 0) :
840
obj = fuse(p[6],"Linear Extrude Union")
844
if t != 0.0 or s[0] != 1.0 or s[1] != 1.0:
845
newobj = process_linear_extrude_with_transform(obj,h,t,s)
847
newobj = process_linear_extrude(obj,h)
848
if p[3].get('center','false')=='true' :
852
obj.ViewObject.hide()
853
if printverbose: print("End Linear Extrude with Transform")
855
def p_import_file1(p):
856
'import_file1 : import LPAREN keywordargument_list RPAREN SEMICOL'
857
if printverbose: print("Import File")
858
filen,ext = p[3]['file'].rsplit('.',1)
859
p[0] = [process_import_file(filen,ext,p[3]['layer'])]
860
if printverbose: print("End Import File")
862
def p_surface_action(p):
863
'surface_action : surface LPAREN keywordargument_list RPAREN SEMICOL'
864
if printverbose: print("Surface")
865
obj = doc.addObject("Part::Feature",'surface')
866
obj.Shape,xoff,yoff=makeSurfaceVolume(p[3]['file'])
867
if p[3].get('center','false') == 'true' :
868
center(obj,xoff,yoff,0.0)
870
if printverbose: print("End surface")
872
def process_import_file(fname,ext,layer):
873
if printverbose: print("Importing : "+fname+"."+ext+" Layer : "+layer)
874
if ext.lower() in reverseimporttypes()['Mesh']:
875
obj = process_mesh_file(fname,ext)
876
elif ext.lower() == 'dxf' :
877
obj = processDXF(fname,layer)
878
elif ext.lower() == 'svg':
879
obj = processSVG(fname, ext)
881
raise ValueError("Unsupported file extension %s" % ext)
884
def processSVG(fname, ext):
885
from importSVG import svgHandler
886
if printverbose: print("SVG Handler")
887
doc = FreeCAD.ActiveDocument
888
docSVG = FreeCAD.newDocument(fname+'_tmp')
889
FreeCAD.ActiveDocument = docSVG
892
parser = xml.sax.make_parser()
893
parser.setFeature(xml.sax.handler.feature_external_ges, False)
894
parser.setContentHandler(svgHandler())
895
parser._cont_handler.doc = docSVG
897
# pathName is a Global
898
filename = os.path.join(pathName,fname+'.'+ext)
899
# Use the native Python open which was saved as `pyopen`
900
parser.parse(pyopen(filename))
902
#combine SVG objects into one
904
for obj in FreeCAD.ActiveDocument.Objects:
905
if printverbose: print(obj.Name)
906
if printverbose: print(obj.Shape)
907
shapes.append(obj.Shape)
908
#compoundSVG = Part.makeCompound(shapes)
909
#compoundSVG = Draft.join(objects)
910
FreeCAD.closeDocument(docSVG.Name)
911
FreeCAD.ActiveDocument=doc
912
obj=doc.addObject('Part::Feature',fname)
913
obj.Shape=Part.Compound(shapes)
916
def process_mesh_file(fname,ext):
919
fullname = fname+'.'+ext
920
filename = os.path.join(pathName,fullname)
921
objname = os.path.split(fname)[1]
922
mesh1 = doc.getObject(objname) #reuse imported object
924
Mesh.insert(filename)
925
mesh1 = doc.getObject(objname)
926
if mesh1 is not None:
928
mesh1.ViewObject.hide()
930
sh.makeShapeFromMesh(mesh1.Mesh.Topology,0.1)
931
solid = Part.Solid(sh)
932
obj = doc.addObject('Part::Feature',"Mesh")
933
#ImportObject(obj,mesh1) #This object is not mutable from the GUI
934
#ViewProviderTree(obj.ViewObject)
935
solid = solid.removeSplitter()
940
obj.Shape = solid#.removeSplitter()
942
FreeCAD.Console.PrintError('Mesh not imported %s.%s %s\n' % \
943
(objname,ext,filename))
945
obj = doc.addObject('Part::Feature',"FailedMeshImport")
946
obj.Shape = Part.Compound([])
950
def processTextCmd(t):
951
from OpenSCADUtils import callopenscadstring
952
tmpfilename = callopenscadstring(t,'dxf')
953
from OpenSCAD2Dgeom import importDXFface
954
face = importDXFface(tmpfilename,None,None)
955
obj = doc.addObject('Part::Feature','text')
958
os.unlink(tmpfilename)
963
def processDXF(fname,layer):
966
from OpenSCAD2Dgeom import importDXFface
967
if printverbose: print("Process DXF file")
968
if printverbose: print("File Name : "+fname)
969
if printverbose: print("Layer : "+layer)
970
if printverbose: print("PathName : "+pathName)
971
dxfname = fname+'.dxf'
972
filename = os.path.join(pathName,dxfname)
973
shortname = os.path.split(fname)[1]
974
if printverbose: print("DXF Full path : "+filename)
975
face = importDXFface(filename,layer,doc)
976
obj=doc.addObject('Part::Feature','dxf_%s_%s' % (shortname,layer or "all"))
978
if printverbose: print("DXF Diagnostics")
979
if printverbose: print(obj.Shape.ShapeType)
980
if printverbose: print("Closed : "+str(obj.Shape.isClosed()))
981
if printverbose: print(obj.Shape.check())
982
if printverbose: print([w.isClosed() for w in obj.Shape.Wires])
985
def processSTL(fname):
986
if printverbose: print("Process STL file")
988
def p_multmatrix_action(p):
989
'multmatrix_action : multmatrix LPAREN matrix RPAREN OBRACE block_list EBRACE'
990
if printverbose: print("MultMatrix")
991
transform_matrix = FreeCAD.Matrix()
992
if printverbose: print("Multmatrix")
993
if printverbose: print(p[3])
995
parentcolor=p[6][0].ViewObject.ShapeColor
996
parenttransparency=p[6][0].ViewObject.Transparency
999
if any('x' in me for me in m1l): #hexfloats
1000
m1l=[float.fromhex(me) for me in m1l]
1001
matrixisrounded=False
1002
elif max((len(me) for me in m1l)) >= 14: #might have double precision
1003
m1l=[float(me) for me in m1l] # assume precise output
1004
m1l=[(0 if (abs(me) < 1e-15) else me) for me in m1l]
1005
matrixisrounded=False
1006
else: #trucanted numbers
1007
m1l=[round(float(me),12) for me in m1l] #round
1008
matrixisrounded=True
1009
transform_matrix = FreeCAD.Matrix(*tuple(m1l))
1010
if printverbose: print(transform_matrix)
1011
if printverbose: print("Apply Multmatrix")
1012
# If more than one object on the stack for multmatrix fuse first
1013
if (len(p[6]) == 0) :
1014
part = placeholder('group',[],'{}')
1015
elif (len(p[6]) > 1) :
1016
part = fuse(p[6],"Matrix Union")
1019
if (isspecialorthogonalpython(fcsubmatrix(transform_matrix))) :
1020
if printverbose: print("special orthogonal")
1022
if printverbose: print("rotation rounded")
1023
plm=FreeCAD.Placement(transform_matrix)
1024
plm=FreeCAD.Placement(plm.Base,roundrotation(plm.Rotation))
1025
part.Placement=plm.multiply(part.Placement)
1027
part.Placement=FreeCAD.Placement(transform_matrix).multiply(\
1030
elif isrotoinversionpython(fcsubmatrix(transform_matrix)):
1031
if printverbose: print("orthogonal and inversion")
1032
cmat,axisvec = decomposerotoinversion(transform_matrix)
1033
new_part=doc.addObject("Part::Mirroring",'mirr_%s'%part.Name)
1034
new_part.Source=part
1035
new_part.Normal=axisvec
1037
if printverbose: print("rotation rounded")
1038
plm=FreeCAD.Placement(cmat)
1039
new_part.Placement=FreeCAD.Placement(plm.Base,roundrotation(plm.Rotation))
1041
new_part.Placement=FreeCAD.Placement(cmat)
1042
new_part.Label="mirrored %s" % part.Label
1044
part.ViewObject.hide()
1045
elif FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1046
GetBool('useMultmatrixFeature'):
1047
from OpenSCADFeatures import MatrixTransform
1048
new_part=doc.addObject("Part::FeaturePython",'Matrix Deformation')
1049
MatrixTransform(new_part,transform_matrix,part)
1051
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1052
GetBool('useViewProviderTree'):
1053
from OpenSCADFeatures import ViewProviderTree
1054
ViewProviderTree(new_part.ViewObject)
1056
new_part.ViewObject.Proxy = 0
1057
part.ViewObject.hide()
1059
if printverbose: print("Transform Geometry")
1061
if part.Shape.isNull():
1063
new_part = doc.addObject("Part::Feature","Matrix Deformation")
1064
new_part.Shape = part.Shape.transformGeometry(transform_matrix)
1066
part.ViewObject.hide()
1068
# Does not fix problemfile or beltTighener although later is closer
1069
newobj=doc.addObject("Part::FeaturePython",'RefineMultMatrix')
1070
RefineShape(newobj,new_part)
1072
newobj.ViewObject.Proxy = 0
1073
new_part.ViewObject.hide()
1078
new_part.ViewObject.ShapeColor=parentcolor
1079
new_part.ViewObject.Transparency = parenttransparency
1080
if printverbose: print("Multmatrix applied")
1083
'matrix : OSQUARE vector COMMA vector COMMA vector COMMA vector ESQUARE'
1084
if printverbose: print("Matrix")
1085
p[0] = [p[2],p[4],p[6],p[8]]
1088
'vector : OSQUARE NUMBER COMMA NUMBER COMMA NUMBER COMMA NUMBER ESQUARE'
1089
if printverbose: print("Vector")
1090
p[0] = [p[2],p[4],p[6],p[8]]
1092
def center(obj,x,y,z):
1093
obj.Placement = FreeCAD.Placement(\
1094
FreeCAD.Vector(-x/2.0,-y/2.0,-z/2.0),\
1095
FreeCAD.Rotation(0,0,0,1))
1097
def p_sphere_action(p):
1098
'sphere_action : sphere LPAREN keywordargument_list RPAREN SEMICOL'
1099
if printverbose: print("Sphere : ",p[3])
1100
r = float(p[3]['r'])
1101
mysphere = doc.addObject("Part::Sphere",p[1])
1103
if printverbose: print("Push Sphere")
1105
if printverbose: print("End Sphere")
1108
# Adapted from Draft::_Polygon
1110
if printverbose: print("My Polygon")
1112
nodes = [FreeCAD.Vector(r1,0,0)]
1113
for i in range(n-1) :
1115
nodes.append(FreeCAD.Vector(r1*math.cos(th),r1*math.sin(th),0))
1116
nodes.append(nodes[0])
1117
polygonwire = Part.makePolygon(nodes)
1119
polygon = doc.addObject("Part::Feature","Polygon")
1120
polygon.Shape = Part.Face(polygonwire)
1123
def p_cylinder_action(p):
1124
'cylinder_action : cylinder LPAREN keywordargument_list RPAREN SEMICOL'
1125
if printverbose: print("Cylinder")
1126
tocenter = p[3].get('center','false')
1127
h = float(p[3]['h'])
1128
r1 = float(p[3]['r1'])
1129
r2 = float(p[3]['r2'])
1130
#n = int(p[3]['$fn'])
1131
n = int(round(float(p[3]['$fn'])))
1132
fnmax = FreeCAD.ParamGet(\
1133
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1134
GetInt('useMaxFN', 16)
1135
if printverbose: print(p[3])
1137
if ( r1 == r2 and r1 > 0):
1138
if printverbose: print("Make Cylinder")
1139
if n < 3 or fnmax != 0 and n > fnmax:
1140
mycyl=doc.addObject("Part::Cylinder",p[1])
1144
if printverbose: print("Make Prism")
1145
if False: #user Draft Polygon
1146
mycyl=doc.addObject("Part::Extrusion","prism")
1150
mycyl.Base = Draft.makePolygon(n,r1,face=True)
1152
# If Draft can't import (probably due to lack of Pivy on Mac and
1153
# Linux builds of FreeCAD), this is a fallback.
1154
# or old level of FreeCAD
1156
print("Draft makePolygon Failed, falling back on manual polygon")
1157
mycyl.Base = myPolygon(n,r1)
1158
# mycyl.Solid = True
1163
mycyl.Base.ViewObject.hide()
1164
else: #Use Part::Prism primitive
1165
mycyl=doc.addObject("Part::Prism","prism")
1167
mycyl.Circumradius = r1
1171
if n < 3 or fnmax != 0 and n > fnmax:
1172
if printverbose: print("Make Cone")
1173
mycyl=doc.addObject("Part::Cone",p[1])
1178
if printverbose: print("Make Frustum")
1179
mycyl=doc.addObject("Part::FeaturePython",'frustum')
1180
Frustum(mycyl,r1,r2,n,h)
1182
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1183
GetBool('useViewProviderTree'):
1184
from OpenSCADFeatures import ViewProviderTree
1185
ViewProviderTree(mycyl.ViewObject)
1187
mycyl.ViewObject.Proxy = 0
1188
else: # r1 == r2 == 0
1189
FreeCAD.Console.PrintWarning('cylinder with radius zero\n')
1190
mycyl=doc.addObject("Part::Feature","emptycyl")
1191
mycyl.Shape = Part.Compound([])
1193
FreeCAD.Console.PrintWarning('cylinder with height <= zero\n')
1194
mycyl=doc.addObject("Part::Feature","emptycyl")
1195
mycyl.Shape = Part.Compound([])
1196
if printverbose: print("Center = ",tocenter)
1197
if tocenter=='true' :
1200
# Does not fix problemfile or beltTighener although later is closer
1201
newobj=doc.addObject("Part::FeaturePython",'RefineCylinder')
1202
RefineShape(newobj,mycyl)
1204
if FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1205
GetBool('useViewProviderTree'):
1206
from OpenSCADFeatures import ViewProviderTree
1207
ViewProviderTree(newobj.ViewObject)
1209
newobj.ViewObject.Proxy = 0
1210
mycyl.ViewObject.hide()
1214
if printverbose: print("End Cylinder")
1216
def p_cube_action(p):
1217
'cube_action : cube LPAREN keywordargument_list RPAREN SEMICOL'
1219
l,w,h = [float(str1) for str1 in p[3]['size']]
1220
if (l > 0 and w > 0 and h >0):
1221
if printverbose: print("cube : ",p[3])
1222
mycube=doc.addObject('Part::Box',p[1])
1227
FreeCAD.Console.PrintWarning('cube with radius zero\n')
1228
mycube=doc.addObject("Part::Feature","emptycube")
1229
mycube.Shape = Part.Compound([])
1230
if p[3].get('center','false')=='true' :
1231
center(mycube,l,w,h)
1233
if printverbose: print("End Cube")
1235
def p_circle_action(p) :
1236
'circle_action : circle LPAREN keywordargument_list RPAREN SEMICOL'
1237
if printverbose: print("Circle : "+str(p[3]))
1238
r = float(p[3]['r'])
1240
if r == 0 : r = 0.00001
1241
n = int(p[3]['$fn'])
1242
fnmax = FreeCAD.ParamGet(\
1243
"User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1244
GetInt('useMaxFN',16)
1245
# Alter Max polygon to control if polygons are circles or polygons
1246
# in the modules preferences
1248
if n == 0 or fnmax != 0 and n >= fnmax:
1249
mycircle = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",'circle')
1250
Draft._Circle(mycircle)
1252
mycircle.MakeFace = True
1253
mycircle = Draft.makeCircle(r,face=True) # would call doc.recompute
1254
FreeCAD.ActiveDocument.recompute()
1255
#mycircle = doc.addObject('Part::Circle',p[1]) #would not create a face
1256
#mycircle.Radius = r
1258
#mycircle = Draft.makePolygon(n,r) # would call doc.recompute
1259
mycircle = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",'polygon')
1260
Draft._Polygon(mycircle)
1261
mycircle.FacesNumber = n
1263
mycircle.DrawMode = "inscribed"
1264
mycircle.MakeFace = True
1266
Draft._ViewProviderDraft(mycircle.ViewObject)
1267
if printverbose: print("Push Circle")
1270
def p_square_action(p) :
1271
'square_action : square LPAREN keywordargument_list RPAREN SEMICOL'
1272
if printverbose: print("Square")
1276
mysquare = doc.addObject('Part::Plane',p[1])
1279
if p[3].get('center','false')=='true' :
1280
center(mysquare,x,y,0)
1283
def addString(t,s,p):
1284
return(t + ', ' +s+' = "'+p[3][s]+'"')
1287
return(t + ', ' +v+' = '+p[3][v])
1289
def p_text_action(p) :
1290
'text_action : text LPAREN keywordargument_list RPAREN SEMICOL'
1291
# If text string is null ignore
1292
if p[3]['text'] == "" or p[3]['text'] == " " :
1295
t = 'text ( text="'+p[3]['text']+'"'
1296
t = addValue(t,'size',p)
1297
t = addString(t,'spacing',p)
1298
t = addString(t,'font',p)
1299
t = addString(t,'direction',p)
1300
t = addString(t,'language',p)
1301
if "script" in p[3]:
1302
t = addString(t,'script',p)
1304
t += ', script="latin"'
1305
t = addString(t,'halign',p)
1306
t = addString(t,'valign',p)
1307
t = addValue(t,'$fn',p)
1308
t = addValue(t,'$fa',p)
1309
t = addValue(t,'$fs',p)
1312
FreeCAD.Console.PrintMessage("textmsg : "+t+"\n")
1313
p[0] = [processTextCmd(t)]
1315
def convert_points_list_to_vector(l):
1318
if printverbose: print(i)
1319
v.append(FreeCAD.Vector(i[0],i[1]))
1320
if printverbose: print(v)
1324
def p_polygon_action_nopath(p) :
1325
'polygon_action_nopath : polygon LPAREN points EQ OSQUARE points_list_2d ESQUARE COMMA paths EQ undef COMMA keywordargument_list RPAREN SEMICOL'
1326
if printverbose: print("Polygon")
1327
if printverbose: print(p[6])
1328
v = convert_points_list_to_vector(p[6])
1329
mypolygon = doc.addObject('Part::Feature',p[1])
1330
if printverbose: print("Make Parts")
1333
parts = Part.makePolygon(v)
1334
if printverbose: print("update object")
1335
mypolygon.Shape = Part.Face(parts)
1338
def p_polygon_action_plus_path(p) :
1339
'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'
1340
if printverbose: print("Polygon with Path")
1341
if printverbose: print(p[6])
1342
v = convert_points_list_to_vector(p[6])
1343
if printverbose: print("Path Set List")
1344
if printverbose: print(p[12])
1346
if printverbose: print(i)
1347
mypolygon = doc.addObject('Part::Feature','wire')
1351
if printverbose: print(j)
1352
path_list.append(v[j])
1354
path_list.append(v[int(i[0])])
1355
if printverbose: print('Path List')
1356
if printverbose: print(path_list)
1357
wire = Part.makePolygon(path_list)
1358
mypolygon.Shape = Part.Face(wire)
1360
# This only pushes last polygon
1362
def make_face(v1,v2,v3):
1363
wire = Part.makePolygon([v1,v2,v3,v1])
1364
face = Part.Face(wire)
1367
def p_polyhedron_action(p) :
1368
'''polyhedron_action : polyhedron LPAREN points EQ OSQUARE points_list_3d ESQUARE COMMA faces EQ OSQUARE path_set ESQUARE COMMA keywordargument_list RPAREN SEMICOL
1369
| polyhedron LPAREN points EQ OSQUARE points_list_3d ESQUARE COMMA triangles EQ OSQUARE points_list_3d ESQUARE COMMA keywordargument_list RPAREN SEMICOL'''
1370
if printverbose: print("Polyhedron Points")
1373
if printverbose: print(i)
1374
v.append(FreeCAD.Vector(float(i[0]),float(i[1]),float(i[2])))
1377
print ("Polyhedron "+p[9])
1380
mypolyhed = doc.addObject('Part::Feature',p[1])
1382
if printverbose: print(i)
1384
pp =[v2(v[k]) for k in i]
1385
# Add first point to end of list to close polygon
1388
w = Part.makePolygon(pp)
1391
secWireList = w.Edges[:]
1392
f = Part.makeFilledFace(Part.__sortEdges__(secWireList))
1393
#f = make_face(v[int(i[0])],v[int(i[1])],v[int(i[2])])
1394
faces_list.append(f)
1395
shell=Part.makeShell(faces_list)
1396
solid=Part.Solid(shell).removeSplitter()
1397
if solid.Volume < 0:
1399
mypolyhed.Shape=solid
1402
def p_projection_action(p) :
1403
'projection_action : projection LPAREN keywordargument_list RPAREN OBRACE block_list EBRACE'
1404
if printverbose: print('Projection')
1407
p[6][0].Shape.tessellate(0.05) # Ensure the bounding box calculation is not done with the splines, which can give a bad result
1408
bbox = p[6][0].Shape.BoundBox
1410
shape.Shape.tessellate(0.05)
1411
bbox.add(shape.Shape.BoundBox)
1412
plane = doc.addObject("Part::Plane","xy_plane_used_for_projection")
1413
plane.Length = bbox.XLength
1414
plane.Width = bbox.YLength
1415
plane.Placement = FreeCAD.Placement(FreeCAD.Vector(\
1416
bbox.XMin,bbox.YMin,0),FreeCAD.Rotation())
1418
plane.ViewObject.hide()
1420
if p[3]['cut'] == 'true' :
1421
obj = doc.addObject('Part::MultiCommon','projection_cut')
1423
subobj = [fuse(p[6],"projection_cut_implicit_group")]
1426
obj.Shapes = [plane] + subobj
1428
subobj[0].ViewObject.hide()
1430
else: # cut == 'false' => true projection
1431
if gui and not FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/OpenSCAD").\
1432
GetBool('usePlaceholderForUnsupported'):
1433
from PySide import QtGui
1434
QtGui.QMessageBox.critical(None, translate('OpenSCAD',"Unsupported Function") + " : " + p[1],translate('OpenSCAD',"Press OK"))
1436
p[0] = [placeholder(p[1],p[6],p[3])]