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):
|
||||
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):
|
||||
"""Test if this is a hollow cylinder"""
|
||||
from Part import Circle
|
||||
|
@ -77,6 +88,10 @@ def z_cylinder(cyl):
|
|||
return False
|
||||
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):
|
||||
for otheredge in face.Edges:
|
||||
|
@ -84,6 +99,50 @@ def connected(edge, face):
|
|||
return True
|
||||
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):
|
||||
"""
|
||||
|
@ -182,7 +241,7 @@ def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vf
|
|||
else:
|
||||
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
elif r_out < dr:
|
||||
elif r_out <= 2 * dr:
|
||||
out += "(single helix mode)\n"
|
||||
radii = [r_out - tool_diameter/2]
|
||||
assert(radii[0] > 0)
|
||||
|
@ -208,7 +267,6 @@ class ObjectPathHelix(object):
|
|||
|
||||
def __init__(self,obj):
|
||||
# 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::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"))
|
||||
|
@ -264,9 +322,13 @@ class ObjectPathHelix(object):
|
|||
return None
|
||||
|
||||
def execute(self,obj):
|
||||
import cProfile, pstats, StringIO
|
||||
pr = cProfile.Profile()
|
||||
pr.enable()
|
||||
|
||||
from Part import Circle, Cylinder, Plane
|
||||
from math import sqrt
|
||||
if obj.Base:
|
||||
if obj.Features:
|
||||
if not obj.Active:
|
||||
obj.Path = Path.Path("(helix cut operation inactive)")
|
||||
obj.ViewObject.Visibility = False
|
||||
|
@ -287,43 +349,19 @@ class ObjectPathHelix(object):
|
|||
obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)")
|
||||
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'
|
||||
if obj.Comment:
|
||||
output += ', '+ str(obj.Comment)+')\n'
|
||||
else:
|
||||
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 = []
|
||||
|
||||
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
|
||||
xc, yc, zc = cylinder.Surface.Center
|
||||
|
||||
|
@ -390,7 +428,8 @@ class ObjectPathHelix(object):
|
|||
face, = faces
|
||||
for edge in face.Edges:
|
||||
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
|
||||
yo = other_cylinder.Surface.Center.y
|
||||
center_dist = sqrt((xo - xc)**2 + (yo - yc)**2)
|
||||
|
@ -433,6 +472,15 @@ class ObjectPathHelix(object):
|
|||
if obj.ViewObject:
|
||||
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):
|
||||
def __init__(self,vobj):
|
||||
|
@ -449,6 +497,7 @@ class ViewProviderPathHelix(object):
|
|||
FreeCADGui.Control.closeDialog()
|
||||
taskpanel = TaskPanel(vobj.Object)
|
||||
FreeCADGui.Control.showDialog(taskpanel)
|
||||
taskpanels[0] = taskpanel
|
||||
return True
|
||||
|
||||
def __getstate__(self):
|
||||
|
@ -471,15 +520,6 @@ class CommandPathHelix(object):
|
|||
import Path
|
||||
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
|
||||
try:
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathHelix","Create a helix cut"))
|
||||
|
@ -489,8 +529,7 @@ class CommandPathHelix(object):
|
|||
ObjectPathHelix(obj)
|
||||
ViewProviderPathHelix(obj.ViewObject)
|
||||
|
||||
obj.Base = selection.Object
|
||||
obj.Features = [(selection.Object, subobj) for subobj in selection.SubElementNames]
|
||||
obj.Features = cylinders_in_selection()
|
||||
obj.DeltaR = 1.0
|
||||
|
||||
project = PathUtils.addToProject(obj)
|
||||
|
@ -520,6 +559,8 @@ class CommandPathHelix(object):
|
|||
obj.VertFeed = 0.0
|
||||
obj.HorizFeed = 0.0
|
||||
|
||||
obj.ViewObject.startEditing()
|
||||
|
||||
# commit
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
|
@ -530,6 +571,7 @@ class CommandPathHelix(object):
|
|||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
class TaskPanel(object):
|
||||
|
||||
def __init__(self, obj):
|
||||
from Units import Quantity
|
||||
self.obj = obj
|
||||
|
@ -544,7 +586,7 @@ class TaskPanel(object):
|
|||
|
||||
def addWidget(widget):
|
||||
row = layout.rowCount()
|
||||
layout.addWidget(widget, row, 0, columnSpan=2)
|
||||
layout.addWidget(widget, row, 0, 1, 2)
|
||||
|
||||
def addWidgets(widget1, widget2):
|
||||
row = layout.rowCount()
|
||||
|
@ -630,6 +672,24 @@ class TaskPanel(object):
|
|||
widget.currentIndexChanged.connect(change)
|
||||
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")
|
||||
addCheckBox("Active", "Operation is active")
|
||||
addCheckBox("Recursive", "Also mill subsequent holes")
|
||||
|
@ -648,7 +708,7 @@ class TaskPanel(object):
|
|||
addQuantity("StartDepth", "Absolute start height", "UseStartDepth")
|
||||
|
||||
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")
|
||||
addQuantity("HorizFeed", "Horizontal Feed")
|
||||
|
@ -674,6 +734,65 @@ class TaskPanel(object):
|
|||
widget.setLayout(layout)
|
||||
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):
|
||||
return True
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user