diff --git a/CompoundFilter.py b/CompoundFilter.py new file mode 100644 index 0000000..f7995bf --- /dev/null +++ b/CompoundFilter.py @@ -0,0 +1,187 @@ +from latticeCommon import * + + +__title__="CompoundFilter module for FreeCAD" +__author__ = "DeepSOIC" +__url__ = "" + + + +# -------------------------- common stuff -------------------------------------------------- + +def makeCompoundFilter(name): + '''makeCompoundFilter(name): makes a CompoundFilter object.''' + obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython",name) + _CompoundFilter(obj) + _ViewProviderCompoundFilter(obj.ViewObject) + return obj + +class _CompoundFilter: + "The CompoundFilter object" + def __init__(self,obj): + self.Type = "CompoundFilter" + obj.addProperty("App::PropertyLink","Base","CompoundFilter","Compound to be filtered") + + obj.addProperty("App::PropertyEnumeration","FilterType","CompoundFilter","") + obj.FilterType = ['bypass','specific items','collision-pass','window-volume','window-area'] + obj.FilterType = 'bypass' + + # properties controlling "specific items" mode + obj.addProperty("App::PropertyString","items","CompoundFilter","list of indexes of childs to be returned (like this: 1,4,8-10)") + + obj.addProperty("App::PropertyLink","Stencil","CompoundFilter","Object that defines filtering") + + obj.addProperty("App::PropertyFloat","WindowFrom","CompoundFilter","Value of threshold, expressed as a percentage of maximum value.") + obj.WindowFrom = 80.0 + obj.addProperty("App::PropertyFloat","WindowTo","CompoundFilter","Value of threshold, expressed as a percentage of maximum value.") + obj.WindowTo = 100.0 + + obj.addProperty("App::PropertyFloat","OverrideMaxVal","CompoundFilter","Volume threshold, expressed as percentage of the volume of largest child") + obj.OverrideMaxVal = 0 + + obj.addProperty("App::PropertyBool","Invert","CompoundFilter","Output shapes that are rejected by filter, instead") + obj.Invert = False + + obj.Proxy = self + + + def execute(self,obj): + rst = [] #variable to receive the final list of shapes + shps = obj.Base.Shape.childShapes() + if obj.FilterType == 'bypass': + rst = shps + elif obj.FilterType == 'specific items': + rst = [] + flags = [False] * len(shps) + ranges = obj.items.split(';') + for r in ranges: + r_v = r.split('-') + if len(r_v) == 1: + i = int(r_v[0]) + rst.append(shps[i]) + flags[i] = True + elif len(r_v) == 2: + ifrom = int(r_v[0]) + ito = int(r_v[1])+1 #python treats range's 'to' value as not-inclusive. I want the string to list in inclusive manner. + rst=rst+shps[ifrom:ito] + for b in flags[ifrom:ito]: + b = True + else: + raise ValueError('index range cannot be parsed:'+r) + if obj.Invert : + rst = [] + for i in xrange(0,len(shps)): + if not flags[i]: + rst.append(shps[i]) + elif obj.FilterType == 'collision-pass': + stencil = obj.Stencil.Shape + for s in shps: + d = s.distToShape(stencil) + if bool(d[0] < DistConfusion) ^ bool(obj.Invert): + rst.append(s) + elif obj.FilterType == 'window-volume' or obj.FilterType == 'window-area': + vals = [0.0] * len(shps) + for i in xrange(0,len(shps)): + if obj.FilterType == 'window-volume': + vals[i] = shps[i].Volume + elif obj.FilterType == 'window-area': + vals[i] = shps[i].Area + + maxval = max(vals) + if obj.Stencil: + if obj.FilterType == 'window-volume': + vals[i] = obj.Stencil.Shape.Volume + elif obj.FilterType == 'window-area': + vals[i] = obj.Stencil.Shape.Area + if obj.OverrideMaxVal: + maxval = obj.OverrideMaxVal + + valFrom = obj.WindowFrom / 100.0 * maxval + valTo = obj.WindowTo / 100.0 * maxval + + for i in xrange(0,len(shps)): + if bool(vals[i] >= valFrom and vals[i] <= valTo) ^ obj.Invert: + rst.append(shps[i]) + else: + raise ValueError('Filter mode not implemented:'+obj.FilterType) + + obj.Shape = Part.makeCompound(rst) + return + + +class _ViewProviderCompoundFilter: + "A View Provider for the CompoundFilter object" + + def __init__(self,vobj): + vobj.Proxy = self + + def getIcon(self): + return getIconPath("Lattice_CompoundFilter.svg") + + def attach(self, vobj): + self.ViewObject = vobj + self.Object = vobj.Object + + + def setEdit(self,vobj,mode): + return False + + def unsetEdit(self,vobj,mode): + return + + def __getstate__(self): + return None + + def __setstate__(self,state): + return None + +def CreateCompoundFilter(name): + FreeCAD.ActiveDocument.openTransaction("Create CompoundFilter") + FreeCADGui.addModule("CompoundFilter") + FreeCADGui.doCommand("f = CompoundFilter.makeCompoundFilter(name = '"+name+"')") + FreeCADGui.doCommand("f.Base = FreeCADGui.Selection.getSelection()[0]") + if len(FreeCADGui.Selection.getSelection()) == 2: + FreeCADGui.doCommand("f.Stencil = FreeCADGui.Selection.getSelection()[1]") + FreeCADGui.doCommand("f.FilterType = 'collision-pass'") + else: + FreeCADGui.doCommand("f.FilterType = 'window-volume'") + FreeCADGui.doCommand("f.Proxy.execute(f)") + FreeCADGui.doCommand("f.purgeTouched()") + FreeCADGui.doCommand("f.Base.ViewObject.hide()") + FreeCADGui.doCommand("f = None") + FreeCAD.ActiveDocument.commitTransaction() + + +# -------------------------- /common stuff -------------------------------------------------- + +# -------------------------- Gui command -------------------------------------------------- + +class _CommandCompoundFilter: + "Command to create CompoundFilter feature" + def GetResources(self): + return {'Pixmap' : getIconPath("Lattice_CompoundFilter.svg"), + 'MenuText': QtCore.QT_TRANSLATE_NOOP("Lattice_CompoundFilter","Fuse compound"), + 'Accel': "", + 'ToolTip': QtCore.QT_TRANSLATE_NOOP("Lattice_CompoundFilter","Fuse objects contained in a compound")} + + def Activated(self): + if len(FreeCADGui.Selection.getSelection()) == 1 or len(FreeCADGui.Selection.getSelection()) == 2 : + CreateCompoundFilter(name = "CompoundFilter") + else: + mb = QtGui.QMessageBox() + mb.setIcon(mb.Icon.Warning) + mb.setText(_translate("Lattice_CompoundFilter", "Select a shape that is a compound, first! Second selected item (optional) will be treated as a stencil.", None)) + mb.setWindowTitle(_translate("Lattice_CompoundFilter","Bad selection", None)) + mb.exec_() + + def IsActive(self): + if FreeCAD.ActiveDocument: + return True + else: + return False + +FreeCADGui.addCommand('Lattice_CompoundFilter', _CommandCompoundFilter()) + +exportedCommands = ['Lattice_CompoundFilter'] + +# -------------------------- /Gui command -------------------------------------------------- diff --git a/InitGui.py b/InitGui.py index 338413b..24e0919 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,8 +1,8 @@ class LatticeWorkbench (Workbench): MenuText = 'Lattice' def Initialize(self): - import FuseCompound - commandslist = FuseCompound.exportedCommands + import FuseCompound, CompoundFilter + commandslist = FuseCompound.exportedCommands + CompoundFilter.exportedCommands self.appendToolbar('Lattice', commandslist) self.treecmdList = ['importPart', 'updateImportedPartsCommand'] #FreeCADGui.addIconPath( '' ) diff --git a/latticeCommon.py b/latticeCommon.py index 86a2c69..7e532fb 100644 --- a/latticeCommon.py +++ b/latticeCommon.py @@ -27,3 +27,7 @@ def getParamRefine(): def getIconPath(icon_dot_svg): return ":/icons/" + icon_dot_svg + +# OCC's Precision::Confusion; should have taken this from FreeCAD but haven't found; unlikely to ever change. +DistConfusion = 1e-7 +