Issue: A pass statement is only necessary when it is the only statement in a block (the list of statements forming part of a compound statement). This is because the purpose of the pass statement is to allow empty blocks where they would otherwise be syntactically invalid. If the block already contains other statements then the pass statement is unnecessary. Recommendation: Remove the pass statement. Relevant links:1a82a5e136/files/src/Mod/Import/stepZ.py (x78f479c31a062553):11a82a5e136/files/src/Mod/OpenSCAD/OpenSCADCommands.py (xad4868ffcdf82498):11a82a5e136/files/src/App/FreeCADInit.py (x9034ec6815c48e38):1
433 lines
20 KiB
Python
433 lines
20 KiB
Python
#***************************************************************************
|
|
#* *
|
|
#* 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 - GUI Commands"
|
|
__author__ = "Sebastian Hoogen"
|
|
__url__ = ["https://www.freecadweb.org"]
|
|
|
|
'''
|
|
This Script includes the GUI Commands of the OpenSCAD module
|
|
'''
|
|
|
|
import FreeCAD,FreeCADGui
|
|
from PySide import QtCore, QtGui
|
|
|
|
try:
|
|
_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"
|
|
return QtGui.QApplication.translate(context, text, None)
|
|
|
|
class ExplodeGroup:
|
|
"Ungroup Objects"
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
|
|
def Activated(self):
|
|
def isdefault(shapecolor):
|
|
def comparefloat(f1,f2):
|
|
if f1 == 0.0:
|
|
return f1 == f2
|
|
else:
|
|
return abs((f1-f2)/f1) < 2**-24
|
|
scol=FreeCAD.ParamGet("User parameter:BaseApp/Preferences/View").GetUnsigned('DefaultShapeColor',0xccccccff)
|
|
defaultcolor = (((scol >> 24) & 0xff) / 255.0,
|
|
((scol >> 16) & 0xff) / 255.0,
|
|
((scol >> 8) & 0xff) / 255.0, 0.0)
|
|
return all(all(comparefloat(fcc,dcc) for fcc,dcc in \
|
|
zip(facecolor,defaultcolor)) for facecolor in shapecolor)
|
|
|
|
def isgrey(shapecolor):
|
|
defaultcolor=(float.fromhex('0x1.99999ap-1'),float.fromhex(\
|
|
'0x1.99999ap-1'),float.fromhex('0x1.99999ap-1'),0.0)
|
|
return all(facecolor == defaultcolor for facecolor in shapecolor)
|
|
|
|
def randomcolor(transp=0.0):
|
|
import random
|
|
return (random.random(),random.random(),random.random(),transp)
|
|
|
|
def explode(obj,color=True):
|
|
if obj.isDerivedFrom('Part::Fuse') or \
|
|
obj.isDerivedFrom('Part::MultiFuse') or \
|
|
obj.isDerivedFrom('Part::Compound'):
|
|
plm = obj.Placement
|
|
outlist = obj.OutList[:]
|
|
if plm.isNull() or all((len(oo.InList)==1 and \
|
|
not oo.isDerivedFrom('PartDesign::Feature')) \
|
|
for oo in obj.OutList):
|
|
obj.Document.removeObject(obj.Name)
|
|
for oo in outlist:
|
|
if not plm.isNull():
|
|
oo.Placement=plm.multiply(oo.Placement)
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
oo.ViewObject.show()
|
|
if color and isdefault(oo.ViewObject.DiffuseColor):
|
|
if color == True:
|
|
oo.ViewObject.DiffuseColor=randomcolor()
|
|
else:
|
|
oo.ViewObject.DiffuseColor=color
|
|
else:
|
|
FreeCAD.Console.PrintError(translate('OpenSCAD', 'Unable to explode %s') % obj.Name +u'\n')
|
|
|
|
for obj in FreeCADGui.Selection.getSelection():
|
|
if len(obj.InList) == 0: # allowed only for for top level objects
|
|
explode(obj)
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_Explode_Group',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ExplodeGroup', 'Explode Group'),
|
|
'ToolTip': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ExplodeGroup', 'Remove fusion, apply placement to children, and color randomly')}
|
|
|
|
class ColorCodeShape:
|
|
"Change the Color of selected or all Shapes based on their validity"
|
|
def Activated(self):
|
|
import colorcodeshapes
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
if len(selection) > 0:
|
|
objs=[selobj.Object for selobj in selection]
|
|
|
|
else:
|
|
objs=FreeCAD.ActiveDocument.Objects
|
|
colorcodeshapes.colorcodeshapes(objs)
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_ColorCodeShape',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ColorCodeShape', 'Color Shapes'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ColorCodeShape', 'Color Shapes by validity and type')}
|
|
|
|
class Edgestofaces:
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
|
|
def Activated(self):
|
|
from OpenSCAD2Dgeom import edgestofaces,Overlappingfaces
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
edges=[]
|
|
for selobj in selection:
|
|
edges.extend(selobj.Object.Shape.Edges)
|
|
Overlappingfaces(edgestofaces(edges,None)).makefeatures(FreeCAD.ActiveDocument)
|
|
for selobj in selection:
|
|
selobj.Object.ViewObject.hide()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'Python',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_Edgestofaces', 'Convert Edges To Faces'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD', 'Convert Edges to Faces')}
|
|
|
|
class RefineShapeFeature:
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
|
|
def Activated(self):
|
|
import Part,OpenSCADFeatures
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
for selobj in selection:
|
|
newobj=selobj.Document.addObject("Part::FeaturePython",'refine')
|
|
OpenSCADFeatures.RefineShape(newobj,selobj.Object)
|
|
OpenSCADFeatures.ViewProviderTree(newobj.ViewObject)
|
|
newobj.Label='refine_%s' % selobj.Object.Label
|
|
selobj.Object.ViewObject.hide()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_RefineShapeFeature',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_RefineShapeFeature', 'Refine Shape Feature'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_RefineShapeFeature', 'Create Refine Shape Feature')}
|
|
|
|
class IncreaseToleranceFeature:
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
|
|
def Activated(self):
|
|
import Part,OpenSCADFeatures
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
for selobj in selection:
|
|
newobj=selobj.Document.addObject("Part::FeaturePython",'tolerance')
|
|
OpenSCADFeatures.IncreaseTolerance(newobj,selobj.Object)
|
|
OpenSCADFeatures.ViewProviderTree(newobj.ViewObject)
|
|
newobj.Label='tolerance_%s' % selobj.Object.Label
|
|
selobj.Object.ViewObject.hide()
|
|
FreeCAD.ActiveDocument.recompute()
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_IncreaseToleranceFeature',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_IncreaseToleranceFeature', 'Increase Tolerance Feature'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_IncreaseToleranceFeature', 'Create Feature that allows to increase the tolerance')}
|
|
|
|
class ExpandPlacements:
|
|
'''This should aid interactive repair in the future
|
|
but currently it breaks extrusions, as axis, base and so on have to be
|
|
recalculated'''
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
|
|
def Activated(self):
|
|
import expandplacements
|
|
for selobj in FreeCADGui.Selection.getSelectionEx():
|
|
expandplacements.expandplacements(selobj.Object,FreeCAD.Placement())
|
|
FreeCAD.ActiveDocument.recompute()
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'Python',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ExpandPlacements', 'Expand Placements'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ExpandPlacements', 'Expand all placements downwards the FeatureTree')}
|
|
|
|
class ReplaceObject:
|
|
def IsActive(self):
|
|
nobj = FreeCADGui.Selection.countObjectsOfType('Part::Feature')
|
|
if nobj == 3: return True
|
|
elif nobj == 2: return tuple((len(obj.InList)) for obj in \
|
|
FreeCADGui.Selection.getSelection()) in ((0,1),(1,0))
|
|
#else: return False
|
|
|
|
def Activated(self):
|
|
import replaceobj
|
|
objs=FreeCADGui.Selection.getSelection()
|
|
if len(objs)==3 or \
|
|
tuple((len(obj.InList)) for obj in objs) in ((0,1),(1,0)):
|
|
replaceobj.replaceobjfromselection(objs)
|
|
else:
|
|
FreeCAD.Console.PrintError(translate('OpenSCAD', 'Please select 3 objects first')+u'\n')
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_ReplaceObject',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ReplaceObject', 'Replace Object'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_ReplaceObject', 'Replace an object in the Feature Tree. Please select old, new, and parent object')}
|
|
|
|
class RemoveSubtree:
|
|
def IsActive(self):
|
|
return FreeCADGui.Selection.countObjectsOfType('Part::Feature') > 0
|
|
def Activated(self):
|
|
import OpenSCADUtils,FreeCADGui
|
|
OpenSCADUtils.removesubtree(FreeCADGui.Selection.getSelection())
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_RemoveSubtree',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_RemoveSubtree', 'Remove Objects and their Children'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_RemoveSubtree', 'Removes the selected objects and all children that are not referenced from other objects')}
|
|
|
|
class AddSCADWidget(QtGui.QWidget):
|
|
def __init__(self,*args):
|
|
QtGui.QWidget.__init__(self,*args)
|
|
self.textEdit=QtGui.QTextEdit()
|
|
self.buttonadd = QtGui.QPushButton(translate('OpenSCAD','Add'))
|
|
self.buttonclear = QtGui.QPushButton(translate('OpenSCAD','Clear'))
|
|
self.checkboxmesh = QtGui.QCheckBox(translate('OpenSCAD','as Mesh'))
|
|
layouth=QtGui.QHBoxLayout()
|
|
layouth.addWidget(self.buttonadd)
|
|
layouth.addWidget(self.buttonclear)
|
|
layout= QtGui.QVBoxLayout()
|
|
layout.addLayout(layouth)
|
|
layout.addWidget(self.checkboxmesh)
|
|
layout.addWidget(self.textEdit)
|
|
self.setLayout(layout)
|
|
self.setWindowTitle(translate('OpenSCAD','Add OpenSCAD Element'))
|
|
self.textEdit.setText(u'cube();')
|
|
self.buttonclear.clicked.connect(self.textEdit.clear)
|
|
|
|
def retranslateUi(self, widget=None):
|
|
self.buttonadd.setText(translate('OpenSCAD','Add'))
|
|
self.buttonclear.setText(translate('OpenSCAD','Clear'))
|
|
self.checkboxmesh.setText(translate('OpenSCAD','as Mesh'))
|
|
self.setWindowTitle(translate('OpenSCAD','Add OpenSCAD Element'))
|
|
|
|
class AddSCADTask:
|
|
def __init__(self):
|
|
self.form = AddSCADWidget()
|
|
self.form.buttonadd.clicked.connect(self.addelement)
|
|
def getStandardButtons(self):
|
|
return int(QtGui.QDialogButtonBox.Close)
|
|
|
|
def isAllowedAlterSelection(self):
|
|
return True
|
|
|
|
def isAllowedAlterView(self):
|
|
return True
|
|
|
|
def isAllowedAlterDocument(self):
|
|
return True
|
|
|
|
def addelement(self):
|
|
scadstr=self.form.textEdit.toPlainText()
|
|
asmesh=self.form.checkboxmesh.checkState()
|
|
import OpenSCADUtils, os
|
|
extension= 'stl' if asmesh else 'csg'
|
|
try:
|
|
tmpfilename=OpenSCADUtils.callopenscadstring(scadstr,extension)
|
|
doc=FreeCAD.activeDocument() or FreeCAD.newDocument()
|
|
if asmesh:
|
|
import Mesh
|
|
Mesh.insert(tmpfilename,doc.Name)
|
|
else:
|
|
import importCSG
|
|
importCSG.insert(tmpfilename,doc.Name)
|
|
try:
|
|
os.unlink(tmpfilename)
|
|
except OSError:
|
|
pass
|
|
|
|
except OpenSCADUtils.OpenSCADError as e:
|
|
FreeCAD.Console.PrintError(e.value)
|
|
|
|
class OpenSCADMeshBooleanWidget(QtGui.QWidget):
|
|
def __init__(self,*args):
|
|
QtGui.QWidget.__init__(self,*args)
|
|
#self.textEdit=QtGui.QTextEdit()
|
|
self.buttonadd = QtGui.QPushButton(translate('OpenSCAD','Perform'))
|
|
self.rb_group = QtGui.QButtonGroup()
|
|
self.rb_group_box = QtGui.QGroupBox()
|
|
self.rb_group_box_layout = QtGui.QVBoxLayout()
|
|
self.rb_group_box.setLayout(self.rb_group_box_layout)
|
|
self.rb_union = QtGui.QRadioButton("Union")
|
|
self.rb_group.addButton(self.rb_union)
|
|
self.rb_group_box_layout.addWidget(self.rb_union)
|
|
self.rb_intersection = QtGui.QRadioButton("Intersection")
|
|
self.rb_group.addButton(self.rb_intersection)
|
|
self.rb_group_box_layout.addWidget(self.rb_intersection)
|
|
self.rb_difference = QtGui.QRadioButton("Difference")
|
|
self.rb_group.addButton(self.rb_difference)
|
|
self.rb_group_box_layout.addWidget(self.rb_difference)
|
|
self.rb_hull = QtGui.QRadioButton("Hull")
|
|
self.rb_group.addButton(self.rb_hull)
|
|
self.rb_group_box_layout.addWidget(self.rb_hull)
|
|
self.rb_minkowski = QtGui.QRadioButton("Minkowski")
|
|
self.rb_group.addButton(self.rb_minkowski)
|
|
self.rb_group_box_layout.addWidget(self.rb_minkowski)
|
|
layouth=QtGui.QHBoxLayout()
|
|
layouth.addWidget(self.buttonadd)
|
|
layout= QtGui.QVBoxLayout()
|
|
layout.addLayout(layouth)
|
|
layout.addWidget(self.rb_group_box)
|
|
self.setLayout(layout)
|
|
self.setWindowTitle(translate('OpenSCAD','Mesh Boolean'))
|
|
|
|
def retranslateUi(self, widget=None):
|
|
self.buttonadd.setText(translate('OpenSCAD','Perform'))
|
|
self.setWindowTitle(translate('OpenSCAD','Mesh Boolean'))
|
|
|
|
class OpenSCADMeshBooleanTask:
|
|
def __init__(self):
|
|
self.form = OpenSCADMeshBooleanWidget()
|
|
self.form.buttonadd.clicked.connect(self.doboolean)
|
|
def getStandardButtons(self):
|
|
return int(QtGui.QDialogButtonBox.Close)
|
|
|
|
def isAllowedAlterSelection(self):
|
|
return False
|
|
|
|
def isAllowedAlterView(self):
|
|
return False
|
|
|
|
def isAllowedAlterDocument(self):
|
|
return True
|
|
|
|
def doboolean(self):
|
|
from OpenSCADUtils import meshoponobjs
|
|
if self.form.rb_intersection.isChecked(): opname = 'intersection'
|
|
elif self.form.rb_difference.isChecked(): opname = 'difference'
|
|
elif self.form.rb_hull.isChecked(): opname = 'hull'
|
|
elif self.form.rb_minkowski.isChecked(): opname = 'minkowski'
|
|
else: opname = 'union'
|
|
newmesh,objsused = meshoponobjs(opname,FreeCADGui.Selection.getSelection())
|
|
if len(objsused) > 0:
|
|
newmeshobj = FreeCAD.activeDocument().addObject('Mesh::Feature',opname) #create a Feature for the result
|
|
newmeshobj.Mesh = newmesh #assign the result to the new Feature
|
|
for obj in objsused:
|
|
obj.ViewObject.hide() #hide the selected Features
|
|
|
|
class AddOpenSCADElement:
|
|
def IsActive(self):
|
|
return not FreeCADGui.Control.activeDialog()
|
|
def Activated(self):
|
|
panel = AddSCADTask()
|
|
FreeCADGui.Control.showDialog(panel)
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_AddOpenSCADElement',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_AddOpenSCADElement', 'Add OpenSCAD Element...'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_AddOpenSCADElement',
|
|
'Add an OpenSCAD element by entering OpenSCAD code and executing the OpenSCAD binary')}
|
|
|
|
class OpenSCADMeshBoolean:
|
|
def IsActive(self):
|
|
return not FreeCADGui.Control.activeDialog() and \
|
|
len(FreeCADGui.Selection.getSelection()) >= 1
|
|
def Activated(self):
|
|
panel = OpenSCADMeshBooleanTask()
|
|
FreeCADGui.Control.showDialog(panel)
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_MeshBooleans',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_MeshBoolean','Mesh Boolean...'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_MeshBoolean',
|
|
'Export objects as meshes and use OpenSCAD to perform a boolean operation')}
|
|
|
|
class Hull:
|
|
def IsActive(self):
|
|
return len(FreeCADGui.Selection.getSelection()) >= 2
|
|
|
|
def Activated(self):
|
|
import Part,OpenSCADFeatures
|
|
import importCSG
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
objList = []
|
|
for selobj in selection:
|
|
objList.append(selobj.Object)
|
|
selobj.Object.ViewObject.hide()
|
|
importCSG.process_ObjectsViaOpenSCAD(FreeCAD.activeDocument(),objList,"hull")
|
|
FreeCAD.ActiveDocument.recompute()
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_Hull',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_Hull', 'Hull'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_Hull', 'Perform Hull')}
|
|
|
|
class Minkowski:
|
|
def IsActive(self):
|
|
return len(FreeCADGui.Selection.getSelection()) >= 2
|
|
|
|
def Activated(self):
|
|
import Part,OpenSCADFeatures
|
|
import importCSG
|
|
selection=FreeCADGui.Selection.getSelectionEx()
|
|
objList = []
|
|
for selobj in selection:
|
|
objList.append(selobj.Object)
|
|
selobj.Object.ViewObject.hide()
|
|
importCSG.process_ObjectsViaOpenSCAD(FreeCAD.activeDocument(),objList,"minkowski")
|
|
FreeCAD.ActiveDocument.recompute()
|
|
def GetResources(self):
|
|
return {'Pixmap' : 'OpenSCAD_Minkowski',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP('OpenSCAD_Minkowski', 'Minkowski'),
|
|
'ToolTip' : QtCore.QT_TRANSLATE_NOOP('OpenSCAD_Minkowski', 'Perform Minkowski')}
|
|
|
|
FreeCADGui.addCommand('OpenSCAD_ColorCodeShape',ColorCodeShape())
|
|
FreeCADGui.addCommand('OpenSCAD_ExplodeGroup',ExplodeGroup())
|
|
FreeCADGui.addCommand('OpenSCAD_Edgestofaces',Edgestofaces())
|
|
FreeCADGui.addCommand('OpenSCAD_RefineShapeFeature',RefineShapeFeature())
|
|
FreeCADGui.addCommand('OpenSCAD_IncreaseToleranceFeature',IncreaseToleranceFeature())
|
|
FreeCADGui.addCommand('OpenSCAD_ExpandPlacements',ExpandPlacements())
|
|
FreeCADGui.addCommand('OpenSCAD_ReplaceObject',ReplaceObject())
|
|
FreeCADGui.addCommand('OpenSCAD_RemoveSubtree',RemoveSubtree())
|
|
FreeCADGui.addCommand('OpenSCAD_AddOpenSCADElement',AddOpenSCADElement())
|
|
FreeCADGui.addCommand('OpenSCAD_MeshBoolean',OpenSCADMeshBoolean())
|
|
FreeCADGui.addCommand('OpenSCAD_Hull',Hull())
|
|
FreeCADGui.addCommand('OpenSCAD_Minkowski',Minkowski())
|