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