PathHelix: Add GUI list with selected features
This commit is contained in:
parent
6ad0a1f64b
commit
2d6ea99035
|
@ -41,6 +41,17 @@ except AttributeError:
|
||||||
def translate(context, text, disambig=None):
|
def translate(context, text, disambig=None):
|
||||||
return QtGui.QApplication.translate(context, text, disambig)
|
return QtGui.QApplication.translate(context, text, disambig)
|
||||||
|
|
||||||
|
def print_exceptions(func):
|
||||||
|
from functools import wraps
|
||||||
|
@wraps(func)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
try:
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
except:
|
||||||
|
import traceback
|
||||||
|
raise
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def hollow_cylinder(cyl):
|
def hollow_cylinder(cyl):
|
||||||
"""Test if this is a hollow cylinder"""
|
"""Test if this is a hollow cylinder"""
|
||||||
from Part import Circle
|
from Part import Circle
|
||||||
|
@ -77,6 +88,10 @@ def z_cylinder(cyl):
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def full_cylinder(cyl):
|
||||||
|
p1 = cyl.valueAt(cyl.ParameterRange[0], cyl.ParameterRange[2])
|
||||||
|
p2 = cyl.valueAt(cyl.ParameterRange[1], cyl.ParameterRange[2])
|
||||||
|
return fmt(p1.x) == fmt(p2.x) and fmt(p1.y) == fmt(p2.y) and p1.z == p2.z
|
||||||
|
|
||||||
def connected(edge, face):
|
def connected(edge, face):
|
||||||
for otheredge in face.Edges:
|
for otheredge in face.Edges:
|
||||||
|
@ -84,6 +99,50 @@ def connected(edge, face):
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def connected_cylinders(base, edge):
|
||||||
|
from Part import Cylinder
|
||||||
|
cylinders = []
|
||||||
|
for n in range(len(base.Shape.Faces)):
|
||||||
|
face = "Face{0}".format(n+1)
|
||||||
|
subobj = base.Shape.Faces[n]
|
||||||
|
if isinstance(subobj.Surface, Cylinder):
|
||||||
|
if not connected(edge, subobj):
|
||||||
|
continue
|
||||||
|
if not z_cylinder(subobj):
|
||||||
|
continue
|
||||||
|
if not full_cylinder(subobj):
|
||||||
|
continue
|
||||||
|
cylinders.append(face)
|
||||||
|
return cylinders
|
||||||
|
|
||||||
|
def cylinders_in_selection():
|
||||||
|
from Part import Cylinder
|
||||||
|
selections = FreeCADGui.Selection.getSelectionEx()
|
||||||
|
|
||||||
|
cylinders = []
|
||||||
|
|
||||||
|
for selection in selections:
|
||||||
|
base = selection.Object
|
||||||
|
cylinders.append((base, []))
|
||||||
|
for feature in selection.SubElementNames:
|
||||||
|
subobj = getattr(base.Shape, feature)
|
||||||
|
if subobj.ShapeType =='Face':
|
||||||
|
if isinstance(subobj.Surface, Cylinder):
|
||||||
|
if z_cylinder(subobj):
|
||||||
|
cylinders[-1][1].append(feature)
|
||||||
|
else:
|
||||||
|
# brute force triple-loop as FreeCAD does not expose
|
||||||
|
# any topology information...
|
||||||
|
for edge in subobj.Edges:
|
||||||
|
for face in connected_cylinders(base, edge):
|
||||||
|
if hollow_cylinder(getattr(base.Shape, face)):
|
||||||
|
cylinders[-1][1].append(face)
|
||||||
|
|
||||||
|
if subobj.ShapeType == 'Edge':
|
||||||
|
cylinders.extend(connected_cylinders(base, (feature,)))
|
||||||
|
|
||||||
|
return cylinders
|
||||||
|
|
||||||
|
|
||||||
def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside):
|
def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside):
|
||||||
"""
|
"""
|
||||||
|
@ -182,7 +241,7 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf
|
||||||
else:
|
else:
|
||||||
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
||||||
radii = linspace(r_out, r_in, nr)
|
radii = linspace(r_out, r_in, nr)
|
||||||
elif r_out < dr:
|
elif r_out <= 2 * dr:
|
||||||
out += "(single helix mode)\n"
|
out += "(single helix mode)\n"
|
||||||
radii = [r_out - tool_diameter/2]
|
radii = [r_out - tool_diameter/2]
|
||||||
assert(radii[0] > 0)
|
assert(radii[0] > 0)
|
||||||
|
@ -208,7 +267,6 @@ class ObjectPathHelix(object):
|
||||||
|
|
||||||
def __init__(self,obj):
|
def __init__(self,obj):
|
||||||
# Basic
|
# Basic
|
||||||
obj.addProperty("App::PropertyLinkSub","Base","Path",translate("Parent Object","The base geometry of this toolpath"))
|
|
||||||
obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation"))
|
obj.addProperty("App::PropertyLinkSubList","Features","Path",translate("Features","Selected features for the drill operation"))
|
||||||
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Set to False to disable code generation"))
|
obj.addProperty("App::PropertyBool","Active","Path",translate("Active","Set to False to disable code generation"))
|
||||||
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile, will appear in G-Code"))
|
obj.addProperty("App::PropertyString","Comment","Path",translate("Comment","An optional comment for this profile, will appear in G-Code"))
|
||||||
|
@ -264,9 +322,13 @@ class ObjectPathHelix(object):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def execute(self,obj):
|
def execute(self,obj):
|
||||||
|
import cProfile, pstats, StringIO
|
||||||
|
pr = cProfile.Profile()
|
||||||
|
pr.enable()
|
||||||
|
|
||||||
from Part import Circle, Cylinder, Plane
|
from Part import Circle, Cylinder, Plane
|
||||||
from math import sqrt
|
from math import sqrt
|
||||||
if obj.Base:
|
if obj.Features:
|
||||||
if not obj.Active:
|
if not obj.Active:
|
||||||
obj.Path = Path.Path("(helix cut operation inactive)")
|
obj.Path = Path.Path("(helix cut operation inactive)")
|
||||||
obj.ViewObject.Visibility = False
|
obj.ViewObject.Visibility = False
|
||||||
|
@ -287,43 +349,19 @@ class ObjectPathHelix(object):
|
||||||
obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)")
|
obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)")
|
||||||
return
|
return
|
||||||
|
|
||||||
def connected_cylinders(base, edge):
|
|
||||||
cylinders = []
|
|
||||||
for face in base.Shape.Faces:
|
|
||||||
if isinstance(face.Surface, Cylinder):
|
|
||||||
if connected(edge, face):
|
|
||||||
if z_cylinder(face):
|
|
||||||
cylinders.append((base, face))
|
|
||||||
return cylinders
|
|
||||||
|
|
||||||
cylinders = []
|
|
||||||
|
|
||||||
for base, feature in obj.Features:
|
|
||||||
subobj = getattr(base.Shape, feature)
|
|
||||||
if subobj.ShapeType =='Face':
|
|
||||||
if isinstance(subobj.Surface, Cylinder):
|
|
||||||
if z_cylinder(subobj):
|
|
||||||
cylinders.append((base, subobj))
|
|
||||||
else:
|
|
||||||
# brute force triple-loop as FreeCAD does not expose
|
|
||||||
# any topology information...
|
|
||||||
for edge in subobj.Edges:
|
|
||||||
cylinders.extend(filter(lambda b_c: hollow_cylinder(b_c[1]), (connected_cylinders(base, edge))))
|
|
||||||
|
|
||||||
if subobj.ShapeType == 'Edge':
|
|
||||||
cylinders.extend(connected_cylinders(base, subobj))
|
|
||||||
|
|
||||||
output = '(helix cut operation'
|
output = '(helix cut operation'
|
||||||
if obj.Comment:
|
if obj.Comment:
|
||||||
output += ', '+ str(obj.Comment)+')\n'
|
output += ', '+ str(obj.Comment)+')\n'
|
||||||
else:
|
else:
|
||||||
output += ')\n'
|
output += ')\n'
|
||||||
|
|
||||||
output += "G0 Z" + fmt(obj.Base[0].Shape.BoundBox.ZMax + float(obj.Clearance))
|
zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Features) + obj.Clearance.Value
|
||||||
|
output += "G0 Z" + fmt(zsafe)
|
||||||
|
|
||||||
drill_jobs = []
|
drill_jobs = []
|
||||||
|
|
||||||
for base, cylinder in cylinders:
|
for base, feature in sum((list((obj, feature) for feature in features) for obj, features in obj.Features), []):
|
||||||
|
cylinder = getattr(base.Shape, feature)
|
||||||
zsafe = cylinder.BoundBox.ZMax + obj.Clearance.Value
|
zsafe = cylinder.BoundBox.ZMax + obj.Clearance.Value
|
||||||
xc, yc, zc = cylinder.Surface.Center
|
xc, yc, zc = cylinder.Surface.Center
|
||||||
|
|
||||||
|
@ -390,7 +428,8 @@ class ObjectPathHelix(object):
|
||||||
face, = faces
|
face, = faces
|
||||||
for edge in face.Edges:
|
for edge in face.Edges:
|
||||||
if not edge.isSame(other_edge):
|
if not edge.isSame(other_edge):
|
||||||
for base, other_cylinder in connected_cylinders(base, edge):
|
for other_face in connected_cylinders(base, edge):
|
||||||
|
other_cylinder = getattr(base.Shape, other_face)
|
||||||
xo = other_cylinder.Surface.Center.x
|
xo = other_cylinder.Surface.Center.x
|
||||||
yo = other_cylinder.Surface.Center.y
|
yo = other_cylinder.Surface.Center.y
|
||||||
center_dist = sqrt((xo - xc)**2 + (yo - yc)**2)
|
center_dist = sqrt((xo - xc)**2 + (yo - yc)**2)
|
||||||
|
@ -433,6 +472,15 @@ class ObjectPathHelix(object):
|
||||||
if obj.ViewObject:
|
if obj.ViewObject:
|
||||||
obj.ViewObject.Visibility = True
|
obj.ViewObject.Visibility = True
|
||||||
|
|
||||||
|
pr.disable()
|
||||||
|
s = StringIO.StringIO()
|
||||||
|
sortby = 'time' #cumulative'
|
||||||
|
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
|
||||||
|
ps.print_stats(10)
|
||||||
|
FreeCAD.Console.PrintError(s.getvalue() + "\n\n")
|
||||||
|
|
||||||
|
|
||||||
|
taskpanels = {}
|
||||||
|
|
||||||
class ViewProviderPathHelix(object):
|
class ViewProviderPathHelix(object):
|
||||||
def __init__(self,vobj):
|
def __init__(self,vobj):
|
||||||
|
@ -449,6 +497,7 @@ class ViewProviderPathHelix(object):
|
||||||
FreeCADGui.Control.closeDialog()
|
FreeCADGui.Control.closeDialog()
|
||||||
taskpanel = TaskPanel(vobj.Object)
|
taskpanel = TaskPanel(vobj.Object)
|
||||||
FreeCADGui.Control.showDialog(taskpanel)
|
FreeCADGui.Control.showDialog(taskpanel)
|
||||||
|
taskpanels[0] = taskpanel
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __getstate__(self):
|
def __getstate__(self):
|
||||||
|
@ -471,15 +520,6 @@ class CommandPathHelix(object):
|
||||||
import Path
|
import Path
|
||||||
from PathScripts import PathUtils
|
from PathScripts import PathUtils
|
||||||
|
|
||||||
selection = FreeCADGui.Selection.getSelectionEx()
|
|
||||||
|
|
||||||
if not len(selection) == 1:
|
|
||||||
FreeCAD.Console.PrintError("Only considering first object for PathHelix!\n")
|
|
||||||
selection = selection[0]
|
|
||||||
|
|
||||||
if not len(selection.SubElementNames) > 0:
|
|
||||||
FreeCAD.Console.PrintError("Select a face or circles to create helix cuts\n")
|
|
||||||
|
|
||||||
# register the transaction for the undo stack
|
# register the transaction for the undo stack
|
||||||
try:
|
try:
|
||||||
FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut"))
|
FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut"))
|
||||||
|
@ -489,8 +529,7 @@ class CommandPathHelix(object):
|
||||||
ObjectPathHelix(obj)
|
ObjectPathHelix(obj)
|
||||||
ViewProviderPathHelix(obj.ViewObject)
|
ViewProviderPathHelix(obj.ViewObject)
|
||||||
|
|
||||||
obj.Base = selection.Object
|
obj.Features = cylinders_in_selection()
|
||||||
obj.Features = [(selection.Object, subobj) for subobj in selection.SubElementNames]
|
|
||||||
obj.DeltaR = 1.0
|
obj.DeltaR = 1.0
|
||||||
|
|
||||||
project = PathUtils.addToProject(obj)
|
project = PathUtils.addToProject(obj)
|
||||||
|
@ -520,6 +559,8 @@ class CommandPathHelix(object):
|
||||||
obj.VertFeed = 0.0
|
obj.VertFeed = 0.0
|
||||||
obj.HorizFeed = 0.0
|
obj.HorizFeed = 0.0
|
||||||
|
|
||||||
|
obj.ViewObject.startEditing()
|
||||||
|
|
||||||
# commit
|
# commit
|
||||||
FreeCAD.ActiveDocument.commitTransaction()
|
FreeCAD.ActiveDocument.commitTransaction()
|
||||||
|
|
||||||
|
@ -530,6 +571,7 @@ class CommandPathHelix(object):
|
||||||
FreeCAD.ActiveDocument.recompute()
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
class TaskPanel(object):
|
class TaskPanel(object):
|
||||||
|
|
||||||
def __init__(self, obj):
|
def __init__(self, obj):
|
||||||
from Units import Quantity
|
from Units import Quantity
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
|
@ -544,7 +586,7 @@ class TaskPanel(object):
|
||||||
|
|
||||||
def addWidget(widget):
|
def addWidget(widget):
|
||||||
row = layout.rowCount()
|
row = layout.rowCount()
|
||||||
layout.addWidget(widget, row, 0, columnSpan=2)
|
layout.addWidget(widget, row, 0, 1, 2)
|
||||||
|
|
||||||
def addWidgets(widget1, widget2):
|
def addWidgets(widget1, widget2):
|
||||||
row = layout.rowCount()
|
row = layout.rowCount()
|
||||||
|
@ -630,6 +672,24 @@ class TaskPanel(object):
|
||||||
widget.currentIndexChanged.connect(change)
|
widget.currentIndexChanged.connect(change)
|
||||||
addWidgets(label, widget)
|
addWidgets(label, widget)
|
||||||
|
|
||||||
|
self.featureList = QtGui.QListWidget()
|
||||||
|
self.featureList.setMinimumHeight(200)
|
||||||
|
self.featureList.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||||
|
self.featureList.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
|
||||||
|
self.featureList.setDefaultDropAction(QtCore.Qt.MoveAction)
|
||||||
|
self.fillFeatureList()
|
||||||
|
sm = self.featureList.selectionModel()
|
||||||
|
sm.selectionChanged.connect(self.selectFeatures)
|
||||||
|
addWidget(self.featureList)
|
||||||
|
|
||||||
|
self.addButton = QtGui.QPushButton("Add holes")
|
||||||
|
self.addButton.clicked.connect(self.addCylinders)
|
||||||
|
|
||||||
|
self.delButton = QtGui.QPushButton("Delete")
|
||||||
|
self.delButton.clicked.connect(self.delCylinders)
|
||||||
|
|
||||||
|
addWidgets(self.addButton, self.delButton)
|
||||||
|
|
||||||
heading("Drill parameters")
|
heading("Drill parameters")
|
||||||
addCheckBox("Active", "Operation is active")
|
addCheckBox("Active", "Operation is active")
|
||||||
addCheckBox("Recursive", "Also mill subsequent holes")
|
addCheckBox("Recursive", "Also mill subsequent holes")
|
||||||
|
@ -648,7 +708,7 @@ class TaskPanel(object):
|
||||||
addQuantity("StartDepth", "Absolute start height", "UseStartDepth")
|
addQuantity("StartDepth", "Absolute start height", "UseStartDepth")
|
||||||
|
|
||||||
fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth")
|
fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth")
|
||||||
tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth for open holes")
|
tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth\nfor open holes")
|
||||||
|
|
||||||
heading("Feeds")
|
heading("Feeds")
|
||||||
addQuantity("HorizFeed", "Horizontal Feed")
|
addQuantity("HorizFeed", "Horizontal Feed")
|
||||||
|
@ -674,6 +734,65 @@ class TaskPanel(object):
|
||||||
widget.setLayout(layout)
|
widget.setLayout(layout)
|
||||||
self.form = widget
|
self.form = widget
|
||||||
|
|
||||||
|
@print_exceptions
|
||||||
|
def addCylinders(self):
|
||||||
|
features_per_base = {}
|
||||||
|
for base, features in self.obj.Features:
|
||||||
|
features_per_base[base] = list(set(features))
|
||||||
|
|
||||||
|
for base, features in cylinders_in_selection():
|
||||||
|
for feature in features:
|
||||||
|
if base in features_per_base:
|
||||||
|
if not feature in features_per_base[base]:
|
||||||
|
features_per_base[base].append(feature)
|
||||||
|
else:
|
||||||
|
features_per_base[base] = [feature]
|
||||||
|
|
||||||
|
self.obj.Features = list(features_per_base.items())
|
||||||
|
self.featureList.clear()
|
||||||
|
self.fillFeatureList()
|
||||||
|
self.obj.Proxy.execute(self.obj)
|
||||||
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
@print_exceptions
|
||||||
|
def delCylinders(self):
|
||||||
|
del_features = []
|
||||||
|
for item in self.featureList.selectedItems():
|
||||||
|
obj, feature = item.data(QtCore.Qt.UserRole)
|
||||||
|
del_features.append((obj, feature))
|
||||||
|
self.featureList.takeItem(self.featureList.row(item))
|
||||||
|
|
||||||
|
new_features = []
|
||||||
|
for obj, features in self.obj.Features:
|
||||||
|
for feature in features:
|
||||||
|
if (obj, feature) not in del_features:
|
||||||
|
new_features.append((obj, feature))
|
||||||
|
|
||||||
|
FreeCAD.Console.PrintError(del_features)
|
||||||
|
FreeCAD.Console.PrintError("\n")
|
||||||
|
FreeCAD.Console.PrintError(new_features)
|
||||||
|
FreeCAD.Console.PrintError("\n")
|
||||||
|
self.obj.Features = new_features
|
||||||
|
self.obj.Proxy.execute(self.obj)
|
||||||
|
FreeCAD.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
@print_exceptions
|
||||||
|
def fillFeatureList(self):
|
||||||
|
for obj, features in self.obj.Features:
|
||||||
|
for feature in features:
|
||||||
|
radius = getattr(obj.Shape, feature).Surface.Radius
|
||||||
|
item = QtGui.QListWidgetItem()
|
||||||
|
item.setText(obj.Name + "." + feature + " ({0:.2f})".format(radius))
|
||||||
|
item.setData(QtCore.Qt.UserRole, (obj, feature))
|
||||||
|
self.featureList.addItem(item)
|
||||||
|
|
||||||
|
@print_exceptions
|
||||||
|
def selectFeatures(self, selected, deselected):
|
||||||
|
FreeCADGui.Selection.clearSelection()
|
||||||
|
for item in self.featureList.selectedItems():
|
||||||
|
obj, feature = item.data(QtCore.Qt.UserRole)
|
||||||
|
FreeCADGui.Selection.addSelection(obj, feature)
|
||||||
|
|
||||||
def needsFullSpace(self):
|
def needsFullSpace(self):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user