Rebased Helix op on PathOp framework, also it's UI.
This commit is contained in:
committed by
Yorik van Havre
parent
355075f7c3
commit
b6c31bedab
224
src/Mod/Path/PathScripts/PathCircularHoleBase.py
Normal file
224
src/Mod/Path/PathScripts/PathCircularHoleBase.py
Normal file
@@ -0,0 +1,224 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * 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 *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import DraftGeomUtils
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import string
|
||||
import sys
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
# Qt tanslation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
if True:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
class ObjectOp(PathOp.ObjectOp):
|
||||
|
||||
def opFeatures(self, obj):
|
||||
return PathOp.FeatureTool | PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureBaseFaces | self.circularHoleFeatures(obj)
|
||||
|
||||
def initOperation(self, obj):
|
||||
obj.addProperty("App::PropertyStringList", "Disabled", "Base", QtCore.QT_TRANSLATE_NOOP("Path", "List of disabled features"))
|
||||
self.initCircularHoleOperation(obj)
|
||||
|
||||
def baseIsArchPanel(self, obj, base):
|
||||
return hasattr(base, "Proxy") and isinstance(base.Proxy, ArchPanel.PanelSheet)
|
||||
|
||||
def getArchPanelEdge(self, obj, base, sub):
|
||||
ids = string.split(sub, '.')
|
||||
holeId = int(ids[0])
|
||||
wireId = int(ids[1])
|
||||
edgeId = int(ids[2])
|
||||
|
||||
for holeNr, hole in enumerate(base.Proxy.getHoles(base, transform=True)):
|
||||
if holeNr == holeId:
|
||||
for wireNr, wire in enumerate(hole.Wires):
|
||||
if wireNr == wireId:
|
||||
for edgeNr, edge in enumerate(wire.Edges):
|
||||
if edgeNr == edgeId:
|
||||
return edge
|
||||
|
||||
def holeDiameter(self, obj, base, sub):
|
||||
if self.baseIsArchPanel(obj, base):
|
||||
edge = self.getArchPanelEdge(obj, base, sub)
|
||||
return edge.BoundBox.XLength
|
||||
|
||||
shape = base.Shape.getElement(sub)
|
||||
if shape.ShapeType == 'Vertex':
|
||||
return 0
|
||||
|
||||
# for all other shapes the diameter is just the dimension in X
|
||||
return shape.BoundBox.XLength
|
||||
|
||||
def holePosition(self, obj, base, sub):
|
||||
if self.baseIsArchPanel(obj, base):
|
||||
edge = self.getArchPanelEdge(obj, base, sub)
|
||||
center = edge.Curve.Center
|
||||
return FreeCAD.Vector(center.x, center.y, 0)
|
||||
|
||||
shape = base.Shape.getElement(sub)
|
||||
if shape.ShapeType == 'Vertex':
|
||||
return FreeCAD.Vector(shape.X, shape.Y, 0)
|
||||
|
||||
if shape.ShapeType == 'Edge':
|
||||
return FreeCAD.Vector(shape.Curve.Center.x, shape.Curve.Center.y, 0)
|
||||
|
||||
if shape.ShapeType == 'Face':
|
||||
return FreeCAD.Vector(shape.Surface.Center.x, shape.Surface.Center.y, 0)
|
||||
|
||||
PathLog.error('This is bad')
|
||||
|
||||
def isHoleEnabled(self, obj, base, sub):
|
||||
name = "%s.%s" % (base.Name, sub)
|
||||
return not name in obj.Disabled
|
||||
|
||||
def opExecute(self, obj):
|
||||
PathLog.track()
|
||||
|
||||
if len(obj.Base) == 0:
|
||||
job = PathUtils.findParentJob(obj)
|
||||
if not job or not job.Base:
|
||||
return
|
||||
baseobject = job.Base
|
||||
|
||||
# Arch PanelSheet
|
||||
features = []
|
||||
if self.baseIsArchPanel(obj, baseobject):
|
||||
holeshapes = baseobject.Proxy.getHoles(baseobject, transform=True)
|
||||
tooldiameter = obj.ToolController.Proxy.getTool(obj.ToolController).Diameter
|
||||
for holeNr, hole in enumerate(holeshapes):
|
||||
PathLog.debug('Entering new HoleShape')
|
||||
for wireNr, wire in enumerate(hole.Wires):
|
||||
PathLog.debug('Entering new Wire')
|
||||
for edgeNr, edge in enumerate(wire.Edges):
|
||||
if PathUtils.isDrillable(baseobject, edge, tooldiameter):
|
||||
PathLog.debug('Found drillable hole edges: {}'.format(edge))
|
||||
features.append((baseobject, "%d.%d.%d" % (holeNr, wireNr, edgeNr)))
|
||||
|
||||
self.setDepths(obj, None, None, baseobject.Shape.BoundBox)
|
||||
else:
|
||||
features = self.findHoles(obj, baseobject)
|
||||
self.setupDepthsFrom(obj, features, baseobject)
|
||||
obj.Base = features
|
||||
obj.Disabled = []
|
||||
|
||||
holes = []
|
||||
|
||||
for base, subs in obj.Base:
|
||||
for sub in subs:
|
||||
if self.isHoleEnabled(obj, base, sub):
|
||||
pos = self.holePosition(obj, base, sub)
|
||||
holes.append({'x': pos.x, 'y': pos.y, 'r': self.holeDiameter(obj, base, sub)})
|
||||
|
||||
if len(holes) > 0:
|
||||
self.circularHoleExecute(obj, holes)
|
||||
|
||||
def opOnChanged(self, obj, prop):
|
||||
if 'Base' == prop and not 'Restore' in obj.State and obj.Base:
|
||||
features = []
|
||||
for base, subs in obj.Base:
|
||||
for sub in subs:
|
||||
features.append((base, sub))
|
||||
|
||||
job = PathUtils.findParentJob(obj)
|
||||
if not job or not job.Base:
|
||||
return
|
||||
|
||||
self.setupDepthsFrom(obj, features, job.Base)
|
||||
|
||||
def setupDepthsFrom(self, obj, features, baseobject):
|
||||
zmax = None
|
||||
zmin = None
|
||||
for base,sub in features:
|
||||
shape = base.Shape.getElement(sub)
|
||||
bb = shape.BoundBox
|
||||
# find the highes zmax and the highes zmin levels, those provide
|
||||
# the safest values for StartDepth and FinalDepth
|
||||
if zmax is None or zmax < bb.ZMax:
|
||||
zmax = bb.ZMax
|
||||
if zmin is None or zmin < bb.ZMin:
|
||||
zmin = bb.ZMin
|
||||
self.setDepths(obj, zmax, zmin, baseobject.Shape.BoundBox)
|
||||
|
||||
def setDepths(self, obj, zmax, zmin, bb):
|
||||
PathLog.track(obj.Label, zmax, zmin, bb)
|
||||
if zmax is None:
|
||||
zmax = 5
|
||||
if zmin is None:
|
||||
zmin = 0
|
||||
|
||||
if zmin > zmax:
|
||||
zmax = zmin
|
||||
|
||||
PathLog.debug("setDepths(%s): z=%.2f -> %.2f bb.z=%.2f -> %.2f" % (obj.Label, zmin, zmax, bb.ZMin, bb.ZMax))
|
||||
|
||||
obj.StartDepth = zmax
|
||||
obj.ClearanceHeight = bb.ZMax + 5.0
|
||||
obj.SafeHeight = bb.ZMax + 3.0
|
||||
obj.FinalDepth = zmin
|
||||
|
||||
def findHoles(self, obj, baseobject):
|
||||
shape = baseobject.Shape
|
||||
PathLog.track('obj: {} shape: {}'.format(obj, shape))
|
||||
holelist = []
|
||||
features = []
|
||||
# tooldiameter = obj.ToolController.Proxy.getTool(obj.ToolController).Diameter
|
||||
tooldiameter = None
|
||||
PathLog.debug('search for holes larger than tooldiameter: {}: '.format(tooldiameter))
|
||||
if DraftGeomUtils.isPlanar(shape):
|
||||
PathLog.debug("shape is planar")
|
||||
for i in range(len(shape.Edges)):
|
||||
candidateEdgeName = "Edge" + str(i + 1)
|
||||
e = shape.getElement(candidateEdgeName)
|
||||
if PathUtils.isDrillable(shape, e, tooldiameter):
|
||||
PathLog.debug('edge candidate: {} (hash {})is drillable '.format(e, e.hashCode()))
|
||||
x = e.Curve.Center.x
|
||||
y = e.Curve.Center.y
|
||||
diameter = e.BoundBox.XLength
|
||||
holelist.append({'featureName': candidateEdgeName, 'feature': e, 'x': x, 'y': y, 'd': diameter, 'enabled': True})
|
||||
features.append((baseobject, candidateEdgeName))
|
||||
PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateEdgeName))
|
||||
else:
|
||||
PathLog.debug("shape is not planar")
|
||||
for i in range(len(shape.Faces)):
|
||||
candidateFaceName = "Face" + str(i + 1)
|
||||
f = shape.getElement(candidateFaceName)
|
||||
if PathUtils.isDrillable(shape, f, tooldiameter):
|
||||
PathLog.debug('face candidate: {} is drillable '.format(f))
|
||||
x = f.Surface.Center.x
|
||||
y = f.Surface.Center.y
|
||||
diameter = f.BoundBox.XLength
|
||||
holelist.append({'featureName': candidateFaceName, 'feature': f, 'x': x, 'y': y, 'd': diameter, 'enabled': True})
|
||||
features.append((baseobject, candidateFaceName))
|
||||
PathLog.debug("Found hole feature %s.%s" % (baseobject.Label, candidateFaceName))
|
||||
|
||||
PathLog.debug("holes found: {}".format(holelist))
|
||||
return features
|
||||
152
src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py
Normal file
152
src/Mod/Path/PathScripts/PathCircularHoleBaseGui.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * 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 *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
if True:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule())
|
||||
|
||||
class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
DataFeatureName = QtCore.Qt.ItemDataRole.UserRole
|
||||
DataObject = QtCore.Qt.ItemDataRole.UserRole + 1
|
||||
DataObjectSub = QtCore.Qt.ItemDataRole.UserRole + 2
|
||||
|
||||
def getForm(self):
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageBaseHoleGeometryEdit.ui")
|
||||
|
||||
def setFields(self, obj):
|
||||
PathLog.track()
|
||||
self.form.baseList.blockSignals(True)
|
||||
self.form.baseList.clearContents()
|
||||
self.form.baseList.setRowCount(0)
|
||||
for i, (base, subs) in enumerate(self.obj.Base):
|
||||
for sub in subs:
|
||||
self.form.baseList.insertRow(self.form.baseList.rowCount())
|
||||
|
||||
item = QtGui.QTableWidgetItem("%s.%s" % (base.Label, sub))
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
if self.obj.Proxy.isHoleEnabled(self.obj, base, sub):
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
name = "%s.%s" % (base.Name, sub)
|
||||
item.setData(self.DataFeatureName, name)
|
||||
item.setData(self.DataObject, base)
|
||||
item.setData(self.DataObjectSub, sub)
|
||||
self.form.baseList.setItem(self.form.baseList.rowCount()-1, 0, item)
|
||||
|
||||
item = QtGui.QTableWidgetItem("{:.3f}".format(self.obj.Proxy.holeDiameter(self.obj, base, sub)))
|
||||
item.setData(self.DataFeatureName, name)
|
||||
item.setData(self.DataObject, base)
|
||||
item.setData(self.DataObjectSub, sub)
|
||||
item.setTextAlignment(QtCore.Qt.AlignHCenter)
|
||||
self.form.baseList.setItem(self.form.baseList.rowCount()-1, 1, item)
|
||||
|
||||
self.form.baseList.resizeColumnToContents(0)
|
||||
self.form.baseList.blockSignals(False)
|
||||
|
||||
def itemActivated(self):
|
||||
PathLog.track()
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
activatedRows = []
|
||||
for item in self.form.baseList.selectedItems():
|
||||
row = item.row()
|
||||
if not row in activatedRows:
|
||||
activatedRows.append(row)
|
||||
obj = item.data(self.DataObject)
|
||||
sub = str(item.data(self.DataObjectSub))
|
||||
PathLog.debug("itemActivated() -> %s.%s" % (obj.Label, sub))
|
||||
if sub:
|
||||
FreeCADGui.Selection.addSelection(obj, sub)
|
||||
else:
|
||||
FreeCADGui.Selection.addSelection(obj)
|
||||
#FreeCADGui.updateGui()
|
||||
|
||||
def deleteBase(self):
|
||||
PathLog.track()
|
||||
deletedRows = []
|
||||
selected = self.form.baseList.selectedItems()
|
||||
for item in selected:
|
||||
row = self.form.baseList.row(item)
|
||||
if not row in deletedRows:
|
||||
deletedRows.append(row)
|
||||
self.form.baseList.removeRow(row)
|
||||
self.updateBase()
|
||||
#self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def updateBase(self):
|
||||
PathLog.track()
|
||||
newlist = []
|
||||
for i in range(self.form.baseList.rowCount()):
|
||||
item = self.form.baseList.item(i, 0)
|
||||
obj = item.data(self.DataObject)
|
||||
sub = str(item.data(self.DataObjectSub))
|
||||
base = (obj, sub)
|
||||
PathLog.debug("keeping (%s.%s)" % (obj.Label, sub))
|
||||
newlist.append(base)
|
||||
PathLog.debug("obj.Base=%s newlist=%s" % (self.obj.Base, newlist))
|
||||
self.obj.Base = newlist
|
||||
|
||||
def checkedChanged(self):
|
||||
PathLog.track()
|
||||
disabled = []
|
||||
for i in xrange(0, self.form.baseList.rowCount()):
|
||||
item = self.form.baseList.item(i, 0)
|
||||
if item.checkState() != QtCore.Qt.Checked:
|
||||
disabled.append(item.data(self.DataFeatureName))
|
||||
self.obj.Disabled = disabled
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
self.form.baseList.itemSelectionChanged.connect(self.itemActivated)
|
||||
self.form.addBase.clicked.connect(self.addBase)
|
||||
self.form.deleteBase.clicked.connect(self.deleteBase)
|
||||
self.form.resetBase.clicked.connect(self.resetBase)
|
||||
self.form.baseList.itemChanged.connect(self.checkedChanged)
|
||||
|
||||
def resetBase(self):
|
||||
self.obj.Base = []
|
||||
self.obj.Disabled = []
|
||||
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop in ['Base', 'Disabled']:
|
||||
self.setFields(self.obj)
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
def taskPanelBaseGeometryPage(self, obj, features):
|
||||
return TaskPanelHoleGeometryPage(obj, features)
|
||||
|
||||
@@ -54,18 +54,17 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
# drilling works on anything
|
||||
return PathOp.FeatureBaseGeometry
|
||||
|
||||
def initOperation(self, obj):
|
||||
def initCircularHoleOperation(self, obj):
|
||||
|
||||
obj.addProperty("App::PropertyStringList", "Disabled", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "List of disabled features"))
|
||||
obj.addProperty("App::PropertyLength", "PeckDepth", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Drill depth before retracting to clear chips"))
|
||||
obj.addProperty("App::PropertyBool", "PeckEnabled", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable pecking"))
|
||||
obj.addProperty("App::PropertyFloat", "DwellTime", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The time to dwell between peck cycles"))
|
||||
obj.addProperty("App::PropertyBool", "DwellEnabled", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable dwell"))
|
||||
obj.addProperty("App::PropertyBool", "AddTipLength", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Calculate the tip length and subtract from final depth"))
|
||||
obj.addProperty("App::PropertyEnumeration", "ReturnLevel", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "Controls how tool retracts Default=G98"))
|
||||
obj.addProperty("App::PropertyLength", "PeckDepth", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Incremental Drill depth before retracting to clear chips"))
|
||||
obj.addProperty("App::PropertyBool", "PeckEnabled", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable pecking"))
|
||||
obj.addProperty("App::PropertyFloat", "DwellTime", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "The time to dwell between peck cycles"))
|
||||
obj.addProperty("App::PropertyBool", "DwellEnabled", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable dwell"))
|
||||
obj.addProperty("App::PropertyBool", "AddTipLength", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Calculate the tip length and subtract from final depth"))
|
||||
obj.addProperty("App::PropertyEnumeration", "ReturnLevel", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "Controls how tool retracts Default=G98"))
|
||||
obj.ReturnLevel = ['G98', 'G99'] # this is the direction that the Contour runs
|
||||
|
||||
obj.addProperty("App::PropertyDistance", "RetractHeight", "Depth", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height where feed starts and height during retract tool when path is finished"))
|
||||
obj.addProperty("App::PropertyDistance", "RetractHeight", "Drill", QtCore.QT_TRANSLATE_NOOP("App::Property", "The height where feed starts and height during retract tool when path is finished"))
|
||||
|
||||
def circularHoleExecute(self, obj, holes):
|
||||
PathLog.track()
|
||||
@@ -109,6 +108,13 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
|
||||
self.commandlist.append(Path.Command('G80'))
|
||||
|
||||
def setDepths(self, obj, zmax, zmin, bb):
|
||||
super(self.__class__, self).setDepths(obj, zmax, zmin, bb)
|
||||
if zmax is not None:
|
||||
obj.RetractHeight = zmax + 1.0
|
||||
else:
|
||||
obj.RetractHeight = 6.0
|
||||
|
||||
def opSetDefaultValues(self, obj):
|
||||
obj.RetractHeight = 10
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathDrilling as PathDrilling
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathPocketBaseGui as PathPocketBaseGui
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
@@ -37,117 +37,8 @@ if True:
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule())
|
||||
|
||||
class TaskPanelHoleGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
DataFeatureName = QtCore.Qt.ItemDataRole.UserRole
|
||||
DataObject = QtCore.Qt.ItemDataRole.UserRole + 1
|
||||
DataObjectSub = QtCore.Qt.ItemDataRole.UserRole + 2
|
||||
|
||||
def getForm(self):
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageBaseHoleGeometryEdit.ui")
|
||||
|
||||
def setFields(self, obj):
|
||||
PathLog.track()
|
||||
self.form.baseList.blockSignals(True)
|
||||
self.form.baseList.clearContents()
|
||||
self.form.baseList.setRowCount(0)
|
||||
for i, (base, subs) in enumerate(self.obj.Base):
|
||||
for sub in subs:
|
||||
self.form.baseList.insertRow(self.form.baseList.rowCount())
|
||||
|
||||
item = QtGui.QTableWidgetItem("%s.%s" % (base.Label, sub))
|
||||
item.setFlags(item.flags() | QtCore.Qt.ItemIsUserCheckable)
|
||||
if self.obj.Proxy.isHoleEnabled(self.obj, base, sub):
|
||||
item.setCheckState(QtCore.Qt.Checked)
|
||||
else:
|
||||
item.setCheckState(QtCore.Qt.Unchecked)
|
||||
name = "%s.%s" % (base.Name, sub)
|
||||
item.setData(self.DataFeatureName, name)
|
||||
item.setData(self.DataObject, base)
|
||||
item.setData(self.DataObjectSub, sub)
|
||||
self.form.baseList.setItem(self.form.baseList.rowCount()-1, 0, item)
|
||||
|
||||
item = QtGui.QTableWidgetItem("{:.3f}".format(self.obj.Proxy.holeDiameter(self.obj, base, sub)))
|
||||
item.setData(self.DataFeatureName, name)
|
||||
item.setData(self.DataObject, base)
|
||||
item.setData(self.DataObjectSub, sub)
|
||||
item.setTextAlignment(QtCore.Qt.AlignHCenter)
|
||||
self.form.baseList.setItem(self.form.baseList.rowCount()-1, 1, item)
|
||||
|
||||
self.form.baseList.resizeColumnToContents(0)
|
||||
self.form.baseList.blockSignals(False)
|
||||
|
||||
def itemActivated(self):
|
||||
PathLog.track()
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
activatedRows = []
|
||||
for item in self.form.baseList.selectedItems():
|
||||
row = item.row()
|
||||
if not row in activatedRows:
|
||||
activatedRows.append(row)
|
||||
obj = item.data(self.DataObject)
|
||||
sub = str(item.data(self.DataObjectSub))
|
||||
PathLog.debug("itemActivated() -> %s.%s" % (obj.Label, sub))
|
||||
if sub:
|
||||
FreeCADGui.Selection.addSelection(obj, sub)
|
||||
else:
|
||||
FreeCADGui.Selection.addSelection(obj)
|
||||
#FreeCADGui.updateGui()
|
||||
|
||||
def deleteBase(self):
|
||||
PathLog.track()
|
||||
deletedRows = []
|
||||
selected = self.form.baseList.selectedItems()
|
||||
for item in selected:
|
||||
row = self.form.baseList.row(item)
|
||||
if not row in deletedRows:
|
||||
deletedRows.append(row)
|
||||
self.form.baseList.removeRow(row)
|
||||
self.updateBase()
|
||||
#self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def updateBase(self):
|
||||
PathLog.track()
|
||||
newlist = []
|
||||
for i in range(self.form.baseList.rowCount()):
|
||||
item = self.form.baseList.item(i, 0)
|
||||
obj = item.data(self.DataObject)
|
||||
sub = str(item.data(self.DataObjectSub))
|
||||
base = (obj, sub)
|
||||
PathLog.debug("keeping (%s.%s)" % (obj.Label, sub))
|
||||
newlist.append(base)
|
||||
PathLog.debug("obj.Base=%s newlist=%s" % (self.obj.Base, newlist))
|
||||
self.obj.Base = newlist
|
||||
|
||||
def checkedChanged(self):
|
||||
PathLog.track()
|
||||
disabled = []
|
||||
for i in xrange(0, self.form.baseList.rowCount()):
|
||||
item = self.form.baseList.item(i, 0)
|
||||
if item.checkState() != QtCore.Qt.Checked:
|
||||
disabled.append(item.data(self.DataFeatureName))
|
||||
self.obj.Disabled = disabled
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
self.form.baseList.itemSelectionChanged.connect(self.itemActivated)
|
||||
self.form.addBase.clicked.connect(self.addBase)
|
||||
self.form.deleteBase.clicked.connect(self.deleteBase)
|
||||
self.form.resetBase.clicked.connect(self.resetBase)
|
||||
self.form.baseList.itemChanged.connect(self.checkedChanged)
|
||||
|
||||
def resetBase(self):
|
||||
self.obj.Base = []
|
||||
self.obj.Disabled = []
|
||||
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop in ['Base', 'Disabled']:
|
||||
self.setFields(self.obj)
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
|
||||
def getForm(self):
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDrillingEdit.ui")
|
||||
@@ -190,16 +81,16 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
signals = []
|
||||
|
||||
signals.append(self.form.retractHeight.editingFinished)
|
||||
signals.append(self.form.peckDepth.editingFinished)
|
||||
signals.append(self.form.dwellTime.editingFinished)
|
||||
signals.append(self.form.dwellEnabled.stateChanged)
|
||||
signals.append(self.form.peckEnabled.stateChanged)
|
||||
signals.append(self.form.useTipLength.stateChanged)
|
||||
return signals
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
|
||||
def taskPanelBaseGeometryPage(self, obj, features):
|
||||
return TaskPanelHoleGeometryPage(obj, features)
|
||||
return signals
|
||||
|
||||
PathOpGui.SetupOperation('Drilling',
|
||||
PathDrilling.Create,
|
||||
|
||||
@@ -22,11 +22,17 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
from . import PathUtils
|
||||
from .PathUtils import fmt
|
||||
import Part
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
|
||||
import PathScripts.PathCircularHoleBase as PathCircularHoleBase
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PathUtils import fmt
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore, QtGui
|
||||
@@ -55,7 +61,6 @@ def connected(edge, face):
|
||||
|
||||
|
||||
def cylinders_in_selection():
|
||||
from Part import Cylinder
|
||||
selections = FreeCADGui.Selection.getSelectionEx()
|
||||
|
||||
cylinders = []
|
||||
@@ -66,132 +71,13 @@ def cylinders_in_selection():
|
||||
for feature in selection.SubElementNames:
|
||||
subobj = getattr(base.Shape, feature)
|
||||
if subobj.ShapeType == 'Face':
|
||||
if isinstance(subobj.Surface, Cylinder):
|
||||
if isinstance(subobj.Surface, Part.Cylinder):
|
||||
if z_cylinder(subobj):
|
||||
cylinders[-1][1].append(feature)
|
||||
|
||||
return cylinders
|
||||
|
||||
|
||||
def helix_cut(center, r_out, r_in, dr, zmax, zmin, dz, safe_z, tool_diameter, vfeed, hfeed, direction, startside):
|
||||
"""
|
||||
center: 2-tuple
|
||||
(x0, y0) coordinates of center
|
||||
r_out, r_in: floats
|
||||
radial range, cut from outer radius r_out in layers of dr to inner radius r_in
|
||||
zmax, zmin: floats
|
||||
z-range, cut from zmax in layers of dz down to zmin
|
||||
safe_z: float
|
||||
safety layer height
|
||||
tool_diameter: float
|
||||
Width of tool
|
||||
"""
|
||||
from numpy import ceil, linspace
|
||||
|
||||
if (zmax <= zmin):
|
||||
return
|
||||
|
||||
out = "(helix_cut <{0}, {1}>, {2})".format(center[0], center[1],
|
||||
", ".join(map(str, (r_out, r_in, dr, zmax, zmin, dz, safe_z,
|
||||
tool_diameter, vfeed, hfeed, direction, startside))))
|
||||
|
||||
x0, y0 = center
|
||||
nz = max(int(ceil((zmax - zmin)/dz)), 2)
|
||||
zi = linspace(zmax, zmin, 2 * nz + 1)
|
||||
|
||||
if dr > tool_diameter:
|
||||
FreeCAD.Console.PrintWarning("PathHelix: Warning, shortening dr to tool diameter!\n")
|
||||
dr = tool_diameter
|
||||
|
||||
def xyz(x=None, y=None, z=None):
|
||||
out = ""
|
||||
if x is not None:
|
||||
out += " X" + fmt(x)
|
||||
if y is not None:
|
||||
out += " Y" + fmt(y)
|
||||
if z is not None:
|
||||
out += " Z" + fmt(z)
|
||||
return out
|
||||
|
||||
def rapid(x=None, y=None, z=None):
|
||||
return "G0" + xyz(x, y, z) + "\n"
|
||||
|
||||
def F(f=None):
|
||||
return (" F" + fmt(f) if f else "")
|
||||
|
||||
def feed(x=None, y=None, z=None, f=None):
|
||||
return "G1" + xyz(x, y, z) + F(f) + "\n"
|
||||
|
||||
def arc(x, y, i, j, z, f):
|
||||
if direction == "CW":
|
||||
code = "G2"
|
||||
elif direction == "CCW":
|
||||
code = "G3"
|
||||
return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n"
|
||||
|
||||
def helix_cut_r(r):
|
||||
out = ""
|
||||
out += rapid(x=x0+r, y=y0)
|
||||
out += rapid(z=zmax + tool_diameter)
|
||||
out += feed(z=zmax, f=vfeed)
|
||||
# z = zmin
|
||||
for i in range(1, nz+1):
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z=zi[2*i-1], f=hfeed)
|
||||
out += arc(x0+r, y0, i= r, j=0.0, z=zi[2*i], f=hfeed)
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z=zmin, f=hfeed)
|
||||
out += arc(x0+r, y0, i=r, j=0.0, z=zmin, f=hfeed)
|
||||
out += feed(z=zmax + tool_diameter, f=vfeed)
|
||||
out += rapid(z=safe_z)
|
||||
return out
|
||||
|
||||
assert(r_out > 0.0)
|
||||
assert(r_in >= 0.0)
|
||||
|
||||
msg = None
|
||||
if r_out < 0.0:
|
||||
msg = "r_out < 0"
|
||||
elif r_in > 0 and r_out - r_in < tool_diameter:
|
||||
msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, tool_diameter)
|
||||
elif r_in == 0.0 and not r_out > tool_diameter/2.:
|
||||
msg = "Cannot drill a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, tool_diameter)
|
||||
elif startside not in ["inside", "outside"]:
|
||||
msg = "Invalid value for parameter 'startside'"
|
||||
|
||||
if msg:
|
||||
out += "(ERROR: Hole at {0}:".format((x0, y0, zmax)) + msg + ")\n"
|
||||
FreeCAD.Console.PrintError("PathHelix: Hole at {0}:".format((x0, y0, zmax)) + msg + "\n")
|
||||
return out
|
||||
|
||||
if r_in > 0:
|
||||
out += "(annulus mode)\n"
|
||||
r_out = r_out - tool_diameter/2
|
||||
r_in = r_in + tool_diameter/2
|
||||
if abs((r_out - r_in) / dr) < 1e-5:
|
||||
radii = [(r_out + r_in)/2]
|
||||
else:
|
||||
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
elif r_out <= 2 * dr:
|
||||
out += "(single helix mode)\n"
|
||||
radii = [r_out - tool_diameter/2]
|
||||
assert(radii[0] > 0)
|
||||
else:
|
||||
out += "(full hole mode)\n"
|
||||
r_out = r_out - tool_diameter/2
|
||||
r_in = dr/2
|
||||
|
||||
nr = max(1 + int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
assert(all(radii > 0))
|
||||
|
||||
if startside == "inside":
|
||||
radii = radii[::-1]
|
||||
|
||||
for r in radii:
|
||||
out += "(radius {0})\n".format(r)
|
||||
out += helix_cut_r(r)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def features_by_centers(base, features):
|
||||
@@ -228,644 +114,165 @@ def features_by_centers(base, features):
|
||||
return by_centers
|
||||
|
||||
|
||||
class ObjectPathHelix(object):
|
||||
class ObjectHelix(PathCircularHoleBase.ObjectOp):
|
||||
|
||||
def __init__(self, obj):
|
||||
def circularHoleFeatures(self, obj):
|
||||
return PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureBasePanels
|
||||
|
||||
def initCircularHoleOperation(self, obj):
|
||||
# Basic
|
||||
obj.addProperty("App::PropertyLink", "ToolController", "Path",
|
||||
translate("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
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"))
|
||||
|
||||
# Helix specific
|
||||
obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill",
|
||||
translate("Direction", "The direction of the circular cuts, clockwise (CW), or counter clockwise (CCW)"))
|
||||
obj.addProperty("App::PropertyEnumeration", "Direction", "Helix Drill", translate("PathHelix", "The direction of the circular cuts, clockwise (CW), or counter clockwise (CCW)"))
|
||||
obj.Direction = ['CW', 'CCW']
|
||||
|
||||
obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill",
|
||||
translate("Direction", "Start cutting from the inside or outside"))
|
||||
obj.StartSide = ['inside', 'outside']
|
||||
obj.addProperty("App::PropertyEnumeration", "StartSide", "Helix Drill", translate("PathHelix", "Start cutting from the inside or outside"))
|
||||
obj.StartSide = ['Inside', 'Outside']
|
||||
|
||||
obj.addProperty("App::PropertyLength", "DeltaR", "Helix Drill",
|
||||
translate("DeltaR", "Radius increment (must be smaller than tool diameter)"))
|
||||
obj.addProperty("App::PropertyLength", "StepOver", "Helix Drill", translate("PathHelix", "Radius increment (must be smaller than tool diameter)"))
|
||||
|
||||
# Depth Properties
|
||||
obj.addProperty("App::PropertyDistance", "Clearance", "Depths",
|
||||
translate("Clearance", "Safe distance above the top of the hole to which to retract the tool"))
|
||||
obj.addProperty("App::PropertyLength", "StepDown", "Depths",
|
||||
translate("StepDown", "Incremental Step Down of Tool"))
|
||||
obj.addProperty("App::PropertyBool", "UseStartDepth", "Depths",
|
||||
translate("Use Start Depth", "Set to True to manually specify a start depth"))
|
||||
obj.addProperty("App::PropertyDistance", "StartDepth", "Depths",
|
||||
translate("Start Depth", "Starting Depth of Tool - first cut depth in Z"))
|
||||
obj.addProperty("App::PropertyBool", "UseFinalDepth", "Depths",
|
||||
translate("Use Final Depth", "Set to True to manually specify a final depth"))
|
||||
obj.addProperty("App::PropertyDistance", "FinalDepth", "Depths",
|
||||
translate("Final Depth", "Final Depth of Tool - lowest value in Z"))
|
||||
obj.addProperty("App::PropertyDistance", "ThroughDepth", "Depths",
|
||||
translate("Through Depth", "Add this amount of additional cutting depth "
|
||||
"to open-ended holes. Only used if UseFinalDepth is False"))
|
||||
def circularHoleExecute(self, obj, holes):
|
||||
PathLog.track()
|
||||
self.commandlist.append(Path.Command('(helix cut operation)'))
|
||||
|
||||
# The current tool number, read-only
|
||||
# this is apparently used internally, to keep track of tool chagnes
|
||||
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool",
|
||||
translate("PathProfile", "The current tool in use"))
|
||||
obj.ToolNumber = (0, 0, 1000, 1)
|
||||
obj.setEditorMode('ToolNumber', 1) # make this read only
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
|
||||
|
||||
obj.Proxy = self
|
||||
zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Base) + obj.ClearanceHeight.Value
|
||||
output = ''
|
||||
output += "G0 Z" + fmt(zsafe)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
for hole in holes:
|
||||
output += self.helix_cut(obj, hole['x'], hole['y'], hole['r'] / 2, 0.0, (float(obj.StepOver.Value)/50.0) * self.radius)
|
||||
PathLog.debug(output)
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
def helix_cut(self, obj, x0, y0, r_out, r_in, dr):
|
||||
"""
|
||||
x0, y0:
|
||||
coordinates of center
|
||||
r_out, r_in: floats
|
||||
radial range, cut from outer radius r_out in layers of dr to inner radius r_in
|
||||
zmax, zmin: floats
|
||||
z-range, cut from zmax in layers of dz down to zmin
|
||||
safe_z: float
|
||||
safety layer height
|
||||
tool_diameter: float
|
||||
Width of tool
|
||||
"""
|
||||
from numpy import ceil, linspace
|
||||
|
||||
def execute(self, obj):
|
||||
# from Part import Circle, Cylinder, Plane
|
||||
# from PathScripts import PathUtils
|
||||
# from math import sqrt
|
||||
if (obj.StartDepth.Value <= obj.FinalDepth.Value):
|
||||
return
|
||||
|
||||
output = '(helix cut operation'
|
||||
if obj.Comment:
|
||||
output += ', ' + str(obj.Comment) + ')\n'
|
||||
out = "(helix_cut <{0}, {1}>, {2})".format(x0, y0,
|
||||
", ".join(map(str, (r_out, r_in, dr, obj.StartDepth.Value, obj.FinalDepth.Value, obj.StepDown.Value, obj.SafeHeight.Value,
|
||||
self.radius, self.vertFeed, self.horizFeed, obj.Direction, obj.StartSide))))
|
||||
|
||||
nz = max(int(ceil((obj.StartDepth.Value - obj.FinalDepth.Value)/obj.StepDown.Value)), 2)
|
||||
zi = linspace(obj.StartDepth.Value, obj.FinalDepth.Value, 2 * nz + 1)
|
||||
|
||||
def xyz(x=None, y=None, z=None):
|
||||
out = ""
|
||||
if x is not None:
|
||||
out += " X" + fmt(x)
|
||||
if y is not None:
|
||||
out += " Y" + fmt(y)
|
||||
if z is not None:
|
||||
out += " Z" + fmt(z)
|
||||
return out
|
||||
|
||||
def rapid(x=None, y=None, z=None):
|
||||
return "G0" + xyz(x, y, z) + "\n"
|
||||
|
||||
def F(f=None):
|
||||
return (" F" + fmt(f) if f else "")
|
||||
|
||||
def feed(x=None, y=None, z=None, f=None):
|
||||
return "G1" + xyz(x, y, z) + F(f) + "\n"
|
||||
|
||||
def arc(x, y, i, j, z, f):
|
||||
if obj.Direction == "CW":
|
||||
code = "G2"
|
||||
elif obj.Direction == "CCW":
|
||||
code = "G3"
|
||||
return code + " I" + fmt(i) + " J" + fmt(j) + " X" + fmt(x) + " Y" + fmt(y) + " Z" + fmt(z) + F(f) + "\n"
|
||||
|
||||
def helix_cut_r(r):
|
||||
arc_cmd = 'G2' if obj.Direction == 'CW' else 'G3'
|
||||
out = ""
|
||||
out += rapid(x=x0+r, y=y0)
|
||||
self.commandlist.append(Path.Command('G0', {'X': x0 + r, 'Y': y0, 'F': self.horizRapid}))
|
||||
out += rapid(z=obj.StartDepth.Value + 2*self.radius)
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
out += feed(z=obj.StartDepth.Value, f=self.vertFeed)
|
||||
self.commandlist.append(Path.Command('G1', {'Z': obj.StartDepth.Value, 'F': self.vertFeed}))
|
||||
# z = obj.FinalDepth.Value
|
||||
for i in range(1, nz+1):
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z=zi[2*i-1], f=self.horizFeed)
|
||||
self.commandlist.append(Path.Command(arc_cmd, {'X': x0-r, 'Y': y0, 'Z': zi[2*i-1], 'I': -r, 'J': 0.0, 'F': self.horizFeed}))
|
||||
out += arc(x0+r, y0, i= r, j=0.0, z=zi[2*i], f=self.horizFeed)
|
||||
self.commandlist.append(Path.Command(arc_cmd, {'X': x0+r, 'Y': y0, 'Z': zi[2*i], 'I': r, 'J': 0.0, 'F': self.horizFeed}))
|
||||
out += arc(x0-r, y0, i=-r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed)
|
||||
self.commandlist.append(Path.Command(arc_cmd, {'X': x0-r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': -r, 'J': 0.0, 'F': self.horizFeed}))
|
||||
out += arc(x0+r, y0, i=r, j=0.0, z=obj.FinalDepth.Value, f=self.horizFeed)
|
||||
self.commandlist.append(Path.Command(arc_cmd, {'X': x0+r, 'Y': y0, 'Z': obj.FinalDepth.Value, 'I': r, 'J': 0.0, 'F': self.horizFeed}))
|
||||
out += feed(z=obj.StartDepth.Value + 2*self.radius, f=self.vertFeed)
|
||||
out += rapid(z=obj.SafeHeight.Value)
|
||||
self.commandlist.append(Path.Command('G0', {'Z': obj.SafeHeight.Value, 'F': self.vertRapid}))
|
||||
return out
|
||||
|
||||
assert(r_out > 0.0)
|
||||
assert(r_in >= 0.0)
|
||||
|
||||
msg = None
|
||||
if r_out < 0.0:
|
||||
msg = "r_out < 0"
|
||||
elif r_in > 0 and r_out - r_in < 2*self.radius:
|
||||
msg = "r_out - r_in = {0} is < tool diameter of {1}".format(r_out - r_in, 2*self.radius)
|
||||
elif r_in == 0.0 and not r_out > self.radius/2.:
|
||||
msg = "Cannot drill a hole of diameter {0} with a tool of diameter {1}".format(2 * r_out, 2*self.radius)
|
||||
elif obj.StartSide not in ["Inside", "Outside"]:
|
||||
msg = "Invalid value for parameter 'obj.StartSide'"
|
||||
|
||||
if msg:
|
||||
out += "(ERROR: Hole at {0}:".format((x0, y0, obj.StartDepth.Value)) + msg + ")\n"
|
||||
PathLog.error("PathHelix: Hole at {0}:".format((x0, y0, obj.StartDepth.Value)) + msg + "\n")
|
||||
return out
|
||||
|
||||
if r_in > 0:
|
||||
out += "(annulus mode)\n"
|
||||
r_out = r_out - self.radius
|
||||
r_in = r_in + self.radius
|
||||
if abs((r_out - r_in) / dr) < 1e-5:
|
||||
radii = [(r_out + r_in)/2]
|
||||
else:
|
||||
nr = max(int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
elif r_out <= 2 * dr:
|
||||
out += "(single helix mode)\n"
|
||||
radii = [r_out - self.radius]
|
||||
assert(radii[0] > 0)
|
||||
else:
|
||||
output += ')\n'
|
||||
out += "(full hole mode)\n"
|
||||
r_out = r_out - self.radius
|
||||
r_in = dr/2
|
||||
|
||||
if obj.Features:
|
||||
if not obj.Active:
|
||||
obj.Path = Path.Path("(helix cut operation inactive)")
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.Visibility = False
|
||||
return
|
||||
nr = max(1 + int(ceil((r_out - r_in)/dr)), 2)
|
||||
radii = linspace(r_out, r_in, nr)
|
||||
assert(all(radii > 0))
|
||||
|
||||
if not obj.ToolController:
|
||||
obj.ToolController = PathUtils.findToolController(obj)
|
||||
if obj.StartSide == "Inside":
|
||||
radii = radii[::-1]
|
||||
|
||||
toolLoad = obj.ToolController
|
||||
for r in radii:
|
||||
out += "(radius {0})\n".format(r)
|
||||
out += helix_cut_r(r)
|
||||
|
||||
if toolLoad is None or toolLoad.ToolNumber == 0:
|
||||
FreeCAD.Console.PrintError("PathHelix: No tool selected for helix cut operation, insert a tool change operation first\n")
|
||||
obj.Path = Path.Path("(ERROR: no tool selected for helix cut operation)")
|
||||
return
|
||||
|
||||
tool = toolLoad.Proxy.getTool(toolLoad)
|
||||
|
||||
zsafe = max(baseobj.Shape.BoundBox.ZMax for baseobj, features in obj.Features) + obj.Clearance.Value
|
||||
output += "G0 Z" + fmt(zsafe)
|
||||
|
||||
drill_jobs = []
|
||||
|
||||
for base, features in obj.Features:
|
||||
for center, by_radius in features_by_centers(base, features).items():
|
||||
radii = sorted(by_radius.keys(), reverse=True)
|
||||
cylinders = map(lambda radius: getattr(base.Shape, by_radius[radius]), radii)
|
||||
zsafe = max(cyl.BoundBox.ZMax for cyl in cylinders) + obj.Clearance.Value
|
||||
cur_z = cylinders[0].BoundBox.ZMax
|
||||
jobs = []
|
||||
|
||||
for cylinder in cylinders:
|
||||
# Find other edge of current cylinder
|
||||
other_edge = None
|
||||
for edge in cylinder.Edges:
|
||||
if isinstance(edge.Curve, Part.Circle) and edge.Curve.Center.z != cur_z:
|
||||
other_edge = edge
|
||||
break
|
||||
|
||||
next_z = other_edge.Curve.Center.z
|
||||
dz = next_z - cur_z
|
||||
r = cylinder.Surface.Radius
|
||||
|
||||
if dz < 0:
|
||||
# This is a closed hole if the face connected to
|
||||
# the current cylinder at next_z has the cylinder's
|
||||
# edge as its OuterWire
|
||||
closed = None
|
||||
for face in base.Shape.Faces:
|
||||
if connected(other_edge, face) and not face.isSame(cylinder.Faces[0]):
|
||||
wire = face.OuterWire
|
||||
if len(wire.Edges) == 1 and wire.Edges[0].isSame(other_edge):
|
||||
closed = True
|
||||
else:
|
||||
closed = False
|
||||
|
||||
if closed is None:
|
||||
raise Exception("Cannot determine if this cylinder is closed on the z = {0} side".format(next_z))
|
||||
|
||||
xc, yc, _ = cylinder.Surface.Center
|
||||
jobs.append(dict(xc=xc, yc=yc,
|
||||
zmin=next_z, zmax=cur_z, zsafe=zsafe,
|
||||
r_out=r, r_in=0.0, closed=closed))
|
||||
|
||||
elif dz > 0:
|
||||
new_jobs = []
|
||||
for job in jobs:
|
||||
if job["zmin"] < next_z < job["zmax"]:
|
||||
# split this job
|
||||
job1 = dict(job)
|
||||
job2 = dict(job)
|
||||
job1["zmin"] = next_z
|
||||
job2["zmax"] = next_z
|
||||
job2["r_in"] = r
|
||||
new_jobs.append(job1)
|
||||
new_jobs.append(job2)
|
||||
else:
|
||||
new_jobs.append(job)
|
||||
jobs = new_jobs
|
||||
else:
|
||||
FreeCAD.Console.PrintError("PathHelix: Encountered cylinder with zero height\n")
|
||||
break
|
||||
|
||||
cur_z = next_z
|
||||
|
||||
if obj.UseStartDepth:
|
||||
jobs = [job for job in jobs if job["zmin"] < obj.StartDepth.Value]
|
||||
if jobs:
|
||||
jobs[0]["zmax"] = obj.StartDepth.Value
|
||||
if obj.UseFinalDepth:
|
||||
jobs = [job for job in jobs if job["zmax"] > obj.FinalDepth.Value]
|
||||
if jobs:
|
||||
jobs[-1]["zmin"] = obj.FinalDepth.Value
|
||||
else:
|
||||
if not jobs[-1]["closed"]:
|
||||
jobs[-1]["zmin"] -= obj.ThroughDepth.Value
|
||||
|
||||
drill_jobs.extend(jobs)
|
||||
|
||||
if len(drill_jobs) > 0:
|
||||
drill_jobs = PathUtils.sort_jobs(drill_jobs, ['xc', 'yc'], ['xc', 'zmax'])
|
||||
|
||||
for job in drill_jobs:
|
||||
output += helix_cut((job["xc"], job["yc"]), job["r_out"], job["r_in"], obj.DeltaR.Value,
|
||||
job["zmax"], job["zmin"], obj.StepDown.Value,
|
||||
job["zsafe"], tool.Diameter,
|
||||
toolLoad.VertFeed.Value, toolLoad.HorizFeed.Value,
|
||||
obj.Direction, obj.StartSide)
|
||||
output += '\n'
|
||||
|
||||
obj.Path = Path.Path(output)
|
||||
# if obj.ViewObject:
|
||||
# obj.ViewObject.Visibility = True
|
||||
|
||||
|
||||
class ViewProviderPathHelix(object):
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
return
|
||||
|
||||
def getIcon(self):
|
||||
return ":/icons/Path-Helix.svg"
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
FreeCADGui.Control.closeDialog()
|
||||
taskpanel = TaskPanel(vobj.Object)
|
||||
FreeCADGui.Control.showDialog(taskpanel)
|
||||
return True
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
|
||||
class CommandPathHelix(object):
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path-Helix',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathHelix", "PathHelix"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathHelix", "Creates a helix cut from selected circles")}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
import FreeCADGui
|
||||
# import Path
|
||||
from PathScripts import PathUtils
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathHelix", "Create a helix cut"))
|
||||
FreeCADGui.addModule("PathScripts.PathHelix")
|
||||
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "PathHelix")
|
||||
ObjectPathHelix(obj)
|
||||
ViewProviderPathHelix(obj.ViewObject)
|
||||
|
||||
obj.Features = cylinders_in_selection()
|
||||
obj.DeltaR = 1.0
|
||||
|
||||
if not obj.ToolController:
|
||||
obj.ToolController = PathUtils.findToolController(obj)
|
||||
|
||||
toolLoad = obj.ToolController
|
||||
|
||||
if toolLoad is not None:
|
||||
obj.ToolNumber = toolLoad.ToolNumber
|
||||
tool = toolLoad.Proxy.getTool(toolLoad)
|
||||
if tool:
|
||||
# start with 25% overlap
|
||||
obj.DeltaR = tool.Diameter * 0.75
|
||||
|
||||
obj.Active = True
|
||||
obj.Comment = ""
|
||||
return out
|
||||
|
||||
def opSetDefaultValues(self, obj):
|
||||
obj.Direction = "CW"
|
||||
obj.StartSide = "inside"
|
||||
obj.StartSide = "Inside"
|
||||
obj.StepOver = 100
|
||||
|
||||
obj.Clearance = 10.0
|
||||
obj.StepDown = 1.0
|
||||
obj.UseStartDepth = False
|
||||
obj.StartDepth = 1.0
|
||||
obj.UseFinalDepth = False
|
||||
obj.FinalDepth = 0.0
|
||||
obj.ThroughDepth = 0.0
|
||||
|
||||
PathUtils.addToJob(obj)
|
||||
|
||||
obj.ViewObject.startEditing()
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
def print_exceptions(func):
|
||||
from functools import wraps
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return func(*args, **kwargs)
|
||||
except:
|
||||
ex_type, ex, tb = sys.exc_info()
|
||||
FreeCAD.Console.PrintError("".join(traceback.format_exception(ex_type, ex, tb)) + "\n")
|
||||
raise
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
def print_all_exceptions(cls):
|
||||
for entry in dir(cls):
|
||||
obj = getattr(cls, entry)
|
||||
if not entry.startswith("__") and hasattr(obj, "__call__"):
|
||||
setattr(cls, entry, print_exceptions(obj))
|
||||
return cls
|
||||
|
||||
|
||||
@print_all_exceptions
|
||||
class TaskPanel(object):
|
||||
|
||||
def __init__(self, obj):
|
||||
#from Units import Quantity
|
||||
from PathScripts import PathUtils
|
||||
|
||||
self.obj = obj
|
||||
self.previous_value = {}
|
||||
self.form = QtGui.QToolBox()
|
||||
|
||||
ui = FreeCADGui.UiLoader()
|
||||
|
||||
grayed_out = "background-color: #d0d0d0;"
|
||||
|
||||
def nextToolBoxItem(label, iconFile):
|
||||
widget = QtGui.QWidget()
|
||||
layout = QtGui.QGridLayout()
|
||||
widget.setLayout(layout)
|
||||
icon = QtGui.QIcon(iconFile)
|
||||
self.form.addItem(widget, icon, label)
|
||||
return layout
|
||||
|
||||
def addFiller():
|
||||
row = layout.rowCount()
|
||||
widget = QtGui.QWidget()
|
||||
layout.addWidget(widget, row, 0, 1, 2)
|
||||
layout.setRowStretch(row, 1)
|
||||
|
||||
layout = nextToolBoxItem("Geometry", ":/icons/PartDesign_InternalExternalGear.svg")
|
||||
|
||||
def addWidget(widget):
|
||||
row = layout.rowCount()
|
||||
layout.addWidget(widget, row, 0, 1, 2)
|
||||
|
||||
def addWidgets(widget1, widget2):
|
||||
row = layout.rowCount()
|
||||
layout.addWidget(widget1, row, 0)
|
||||
layout.addWidget(widget2, row, 1)
|
||||
|
||||
def addQuantity(property, labelstring, activator=None, max=None):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
widget = ui.createWidget("Gui::InputField")
|
||||
|
||||
if activator:
|
||||
self.previous_value[activator] = getattr(self.obj, activator)
|
||||
currently_active = getattr(self.obj, activator)
|
||||
label = QtGui.QCheckBox(labelstring)
|
||||
|
||||
def change(state):
|
||||
setattr(self.obj, activator, label.isChecked())
|
||||
if label.isChecked():
|
||||
widget.setStyleSheet("")
|
||||
else:
|
||||
widget.setStyleSheet(grayed_out)
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
label.stateChanged.connect(change)
|
||||
label.setChecked(currently_active)
|
||||
if not currently_active:
|
||||
widget.setStyleSheet(grayed_out)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(activator))
|
||||
else:
|
||||
label = QtGui.QLabel(labelstring)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
quantity = getattr(self.obj, property)
|
||||
widget.setText(quantity.UserString)
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
if max:
|
||||
# cannot use widget.setMaximum() as apparently ui.createWidget()
|
||||
# returns the object up-casted to QWidget.
|
||||
widget.setProperty("maximum", max)
|
||||
|
||||
def change(quantity):
|
||||
setattr(self.obj, property, quantity)
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
QtCore.QObject.connect(widget, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), change)
|
||||
|
||||
addWidgets(label, widget)
|
||||
return label, widget
|
||||
|
||||
def addCheckBox(property, label):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
widget = QtGui.QCheckBox(label)
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
|
||||
def change(state):
|
||||
setattr(self.obj, property, widget.isChecked())
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
widget.stateChanged.connect(change)
|
||||
|
||||
widget.setChecked(getattr(self.obj, property))
|
||||
addWidget(widget)
|
||||
|
||||
def addEnumeration(property, label, options):
|
||||
self.previous_value[property] = getattr(self.obj, property)
|
||||
label = QtGui.QLabel(label)
|
||||
label.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
widget = QtGui.QComboBox()
|
||||
widget.setToolTip(self.obj.getDocumentationOfProperty(property))
|
||||
for option_label, option_value in options:
|
||||
widget.addItem(option_label)
|
||||
|
||||
def change(index):
|
||||
setattr(self.obj, property, options[index][1])
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
widget.currentIndexChanged.connect(change)
|
||||
addWidgets(label, widget)
|
||||
|
||||
self.featureTree = QtGui.QTreeWidget()
|
||||
self.featureTree.setMinimumHeight(200)
|
||||
self.featureTree.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
|
||||
# self.featureTree.setDragDropMode(QtGui.QAbstractItemView.DragDrop)
|
||||
# self.featureTree.setDefaultDropAction(QtCore.Qt.MoveAction)
|
||||
self.fillFeatureTree()
|
||||
sm = self.featureTree.selectionModel()
|
||||
sm.selectionChanged.connect(self.selectFeatures)
|
||||
addWidget(self.featureTree)
|
||||
self.featureTree.expandAll()
|
||||
|
||||
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)
|
||||
|
||||
# End of "Features" section
|
||||
|
||||
layout = nextToolBoxItem("Drill parameters", ":/icons/Path-OperationB.svg")
|
||||
addCheckBox("Active", "Operation is active")
|
||||
|
||||
toolLoad = PathUtils.findToolController(obj)
|
||||
tool = toolLoad and toolLoad.Proxy.getTool(toolLoad)
|
||||
|
||||
if not tool:
|
||||
drmax = None
|
||||
else:
|
||||
drmax = tool.Diameter
|
||||
|
||||
addQuantity("DeltaR", "Step in Radius", max=drmax)
|
||||
addQuantity("StepDown", "Step in Z")
|
||||
addEnumeration("Direction", "Cut direction",
|
||||
[("Clockwise", "CW"), ("Counter-Clockwise", "CCW")])
|
||||
addEnumeration("StartSide", "Start Side",
|
||||
[("Start from inside", "inside"), ("Start from outside", "outside")])
|
||||
|
||||
# End of "Drill parameters" section
|
||||
addFiller()
|
||||
|
||||
layout = nextToolBoxItem("Cutting Depths", ":/icons/Path-Depths.svg")
|
||||
addQuantity("Clearance", "Clearance Distance")
|
||||
addQuantity("StartDepth", "Absolute start height", "UseStartDepth")
|
||||
|
||||
fdcheckbox, fdinput = addQuantity("FinalDepth", "Absolute final height", "UseFinalDepth")
|
||||
tdlabel, tdinput = addQuantity("ThroughDepth", "Extra drill depth\nfor open holes")
|
||||
|
||||
# End of "Cutting Depths" section
|
||||
addFiller()
|
||||
|
||||
# make ThroughDepth and FinalDepth mutually exclusive
|
||||
def fd_change(state):
|
||||
if fdcheckbox.isChecked():
|
||||
tdinput.setStyleSheet(grayed_out)
|
||||
else:
|
||||
tdinput.setStyleSheet("")
|
||||
fdcheckbox.stateChanged.connect(fd_change)
|
||||
|
||||
def td_change(quantity):
|
||||
fdcheckbox.setChecked(False)
|
||||
QtCore.QObject.connect(tdinput, QtCore.SIGNAL("valueChanged(const Base::Quantity &)"), td_change)
|
||||
|
||||
if obj.UseFinalDepth:
|
||||
tdinput.setStyleSheet(grayed_out)
|
||||
|
||||
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 feature not 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.featureTree.clear()
|
||||
self.fillFeatureTree()
|
||||
self.featureTree.expandAll()
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def delCylinders(self):
|
||||
del_features = []
|
||||
|
||||
def delete_feature(item, base=None):
|
||||
kind, feature = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "feature")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent().parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
del_features.append((base, feature))
|
||||
item.parent().takeChild(item.parent().indexOfChild(item))
|
||||
|
||||
def delete_hole(item, base=None):
|
||||
kind, center = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "hole")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
for i in reversed(range(item.childCount())):
|
||||
delete_feature(item.child(i), base=base)
|
||||
item.parent().takeChild(item.parent().indexOfChild(item))
|
||||
|
||||
def delete_base(item):
|
||||
kind, base = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "base")
|
||||
for i in reversed(range(item.childCount())):
|
||||
delete_hole(item.child(i), base=base)
|
||||
self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(item))
|
||||
|
||||
for item in self.featureTree.selectedItems():
|
||||
kind, info = item.data(0, QtCore.Qt.UserRole)
|
||||
if kind == "base":
|
||||
delete_base(item)
|
||||
elif kind == "hole":
|
||||
parent = item.parent()
|
||||
delete_hole(item)
|
||||
if parent.childCount() == 0:
|
||||
self.featureTree.takeTopLevelItem(self.featureTree.indexOfTopLevelItem(parent))
|
||||
elif kind == "feature":
|
||||
parent = item.parent()
|
||||
delete_feature(item)
|
||||
if parent.childCount() == 0:
|
||||
parent.parent().takeChild(parent.parent().indexOfChild(parent))
|
||||
else:
|
||||
raise Exception("No such item kind: {0}".format(kind))
|
||||
|
||||
for base, features in cylinders_in_selection():
|
||||
for feature in features:
|
||||
del_features.append((base, feature))
|
||||
|
||||
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))
|
||||
|
||||
self.obj.Features = new_features
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def fillFeatureTree(self):
|
||||
for base, features in self.obj.Features:
|
||||
base_item = QtGui.QTreeWidgetItem()
|
||||
base_item.setText(0, base.Name)
|
||||
base_item.setData(0, QtCore.Qt.UserRole, ("base", base))
|
||||
self.featureTree.addTopLevelItem(base_item)
|
||||
for center, by_radius in features_by_centers(base, features).items():
|
||||
hole_item = QtGui.QTreeWidgetItem()
|
||||
hole_item.setText(0, "Hole at ({0[0]:.2f}, {0[1]:.2f})".format(center))
|
||||
hole_item.setData(0, QtCore.Qt.UserRole, ("hole", center))
|
||||
base_item.addChild(hole_item)
|
||||
for radius in sorted(by_radius.keys(), reverse=True):
|
||||
feature = by_radius[radius]
|
||||
cylinder = getattr(base.Shape, feature)
|
||||
cyl_item = QtGui.QTreeWidgetItem()
|
||||
cyl_item.setText(0, "Diameter {0:.2f}, {1}".format(
|
||||
2 * cylinder.Surface.Radius, feature))
|
||||
cyl_item.setData(0, QtCore.Qt.UserRole, ("feature", feature))
|
||||
hole_item.addChild(cyl_item)
|
||||
|
||||
def selectFeatures(self, selected, deselected):
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
|
||||
def select_feature(item, base=None):
|
||||
kind, feature = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "feature")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent().parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
FreeCADGui.Selection.addSelection(base, feature)
|
||||
|
||||
def select_hole(item, base=None):
|
||||
kind, center = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "hole")
|
||||
|
||||
if base is None:
|
||||
base_item = item.parent()
|
||||
_, base = base_item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
select_feature(item.child(i), base=base)
|
||||
|
||||
def select_base(item):
|
||||
kind, base = item.data(0, QtCore.Qt.UserRole)
|
||||
assert(kind == "base")
|
||||
|
||||
for i in range(item.childCount()):
|
||||
select_hole(item.child(i), base=base)
|
||||
|
||||
for item in self.featureTree.selectedItems():
|
||||
kind, info = item.data(0, QtCore.Qt.UserRole)
|
||||
|
||||
if kind == "base":
|
||||
select_base(item)
|
||||
elif kind == "hole":
|
||||
select_hole(item)
|
||||
elif kind == "feature":
|
||||
select_feature(item)
|
||||
|
||||
def needsFullSpace(self):
|
||||
return True
|
||||
|
||||
def accept(self):
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
def reject(self):
|
||||
for property in self.previous_value:
|
||||
setattr(self.obj, property, self.previous_value[property])
|
||||
self.obj.Proxy.execute(self.obj)
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# import FreeCADGui
|
||||
FreeCADGui.addCommand('Path_Helix', CommandPathHelix())
|
||||
def Create(name):
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
proxy = ObjectHelix(obj)
|
||||
return obj
|
||||
|
||||
81
src/Mod/Path/PathScripts/PathHelixGui.py
Normal file
81
src/Mod/Path/PathScripts/PathHelixGui.py
Normal file
@@ -0,0 +1,81 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * 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 *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathHelix as PathHelix
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
if True:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule())
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
|
||||
def getForm(self):
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpHelixEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
PathLog.track()
|
||||
self.obj.Direction = str(self.form.direction.currentText())
|
||||
self.obj.StartSide = str(self.form.startSide.currentText())
|
||||
self.obj.StepOver = self.form.stepOverPercent.value()
|
||||
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
|
||||
def setFields(self, obj):
|
||||
PathLog.track()
|
||||
|
||||
self.form.stepOverPercent.setValue(self.obj.StepOver)
|
||||
self.selectInComboBox(self.obj.Direction, self.form.direction)
|
||||
self.selectInComboBox(self.obj.StartSide, self.form.startSide)
|
||||
|
||||
self.setupToolController(self.obj, self.form.toolController)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
signals = []
|
||||
|
||||
signals.append(self.form.stepOverPercent.editingFinished)
|
||||
signals.append(self.form.direction.currentIndexChanged)
|
||||
signals.append(self.form.startSide.currentIndexChanged)
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
|
||||
return signals
|
||||
|
||||
PathOpGui.SetupOperation('Helix',
|
||||
PathHelix.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path-Helix',
|
||||
QtCore.QT_TRANSLATE_NOOP("PathHelix", "Helix"),
|
||||
"P, O",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathHelix", "Creates a Path Helix object from a features of a base object"))
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathHelixGui... done\n")
|
||||
@@ -509,6 +509,9 @@ class TaskPanel(object):
|
||||
for page in self.featurePages:
|
||||
page.pageUpdateData(obj, prop)
|
||||
|
||||
def needsFullSpace(self):
|
||||
return True
|
||||
|
||||
class SelObserver:
|
||||
|
||||
def __init__(self, factory):
|
||||
|
||||
@@ -164,6 +164,7 @@ def select(op):
|
||||
opsel = {}
|
||||
opsel['Contour'] = contourselect
|
||||
opsel['Drilling'] = drillselect
|
||||
opsel['Helix'] = drillselect
|
||||
opsel['MillFace'] = pocketselect
|
||||
opsel['Pocket'] = pocketselect
|
||||
opsel['Profile Edges'] = eselect
|
||||
|
||||
Reference in New Issue
Block a user