Merge pull request #5411 from sliptonic/bug/translationDressup
Bug/translation dressup, drilling, vcarve
This commit is contained in:
@@ -26,8 +26,16 @@ import math
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PathScripts.PathGeom import CmdMoveArc
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -36,23 +44,30 @@ __doc__ = """Axis remapping Dressup object and FreeCAD command. This dressup re
|
||||
For example, you can re-map the Y axis to A to control a 4th axis rotary."""
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']
|
||||
rapidcommands = ['G0', 'G00']
|
||||
arccommands = ['G2', 'G3', 'G02', 'G03']
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectDressup:
|
||||
|
||||
def __init__(self, obj):
|
||||
maplist = ["X->A", "Y->A", "X->B", "Y->B", "X->C", "Y->C"]
|
||||
obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify"))
|
||||
obj.addProperty("App::PropertyEnumeration", "AxisMap", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The input mapping axis"))
|
||||
obj.addProperty("App::PropertyDistance", "Radius", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The radius of the wrapped axis"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Base",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"AxisMap",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The input mapping axis"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"Radius",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The radius of the wrapped axis"),
|
||||
)
|
||||
obj.AxisMap = maplist
|
||||
obj.AxisMap = "Y->A"
|
||||
obj.Proxy = self
|
||||
@@ -64,22 +79,26 @@ class ObjectDressup:
|
||||
return None
|
||||
|
||||
def _linear2angular(self, radius, length):
|
||||
'''returns an angular distance in degrees to achieve a linear move of a given lenth'''
|
||||
"""returns an angular distance in degrees to achieve a linear move of a given lenth"""
|
||||
circum = 2 * math.pi * float(radius)
|
||||
return 360 * (float(length) / circum)
|
||||
|
||||
def _stripArcs(self, path, d):
|
||||
'''converts all G2/G3 commands into G1 commands'''
|
||||
"""converts all G2/G3 commands into G1 commands"""
|
||||
newcommandlist = []
|
||||
currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0}
|
||||
currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0}
|
||||
|
||||
for p in path:
|
||||
if p.Name in arccommands:
|
||||
curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z'])
|
||||
if p.Name in CmdMoveArc:
|
||||
curVec = FreeCAD.Vector(
|
||||
currLocation["X"], currLocation["Y"], currLocation["Z"]
|
||||
)
|
||||
arcwire = PathGeom.edgeForCmd(p, curVec)
|
||||
pointlist = arcwire.discretize(Deflection=d)
|
||||
for point in pointlist:
|
||||
newcommand = Path.Command("G1", {'X': point.x, 'Y': point.y, 'Z': point.z})
|
||||
newcommand = Path.Command(
|
||||
"G1", {"X": point.x, "Y": point.y, "Z": point.z}
|
||||
)
|
||||
newcommandlist.append(newcommand)
|
||||
currLocation.update(newcommand.Parameters)
|
||||
else:
|
||||
@@ -99,26 +118,34 @@ class ObjectDressup:
|
||||
if obj.Base.Path:
|
||||
if obj.Base.Path.Commands:
|
||||
pp = obj.Base.Path.Commands
|
||||
if len([i for i in pp if i.Name in arccommands]) == 0:
|
||||
if len([i for i in pp if i.Name in CmdMoveArc]) == 0:
|
||||
pathlist = pp
|
||||
else:
|
||||
pathlist = self._stripArcs(pp, d)
|
||||
|
||||
newcommandlist = []
|
||||
currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0}
|
||||
currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0}
|
||||
|
||||
for c in pathlist:
|
||||
newparams = dict(c.Parameters)
|
||||
remapvar = newparams.pop(inAxis, None)
|
||||
if remapvar is not None:
|
||||
newparams[outAxis] = self._linear2angular(obj.Radius, remapvar)
|
||||
locdiff = dict(set(newparams.items()) - set(currLocation.items()))
|
||||
if len(locdiff) == 1 and outAxis in locdiff: # pure rotation. Calculate rotational feed rate
|
||||
if 'F' in c.Parameters:
|
||||
feed = c.Parameters['F']
|
||||
newparams[outAxis] = self._linear2angular(
|
||||
obj.Radius, remapvar
|
||||
)
|
||||
locdiff = dict(
|
||||
set(newparams.items()) - set(currLocation.items())
|
||||
)
|
||||
if (
|
||||
len(locdiff) == 1 and outAxis in locdiff
|
||||
): # pure rotation. Calculate rotational feed rate
|
||||
if "F" in c.Parameters:
|
||||
feed = c.Parameters["F"]
|
||||
else:
|
||||
feed = currLocation['F']
|
||||
newparams.update({"F": self._linear2angular(obj.Radius, feed)})
|
||||
feed = currLocation["F"]
|
||||
newparams.update(
|
||||
{"F": self._linear2angular(obj.Radius, feed)}
|
||||
)
|
||||
newcommand = Path.Command(c.Name, newparams)
|
||||
newcommandlist.append(newcommand)
|
||||
currLocation.update(newparams)
|
||||
@@ -131,7 +158,7 @@ class ObjectDressup:
|
||||
obj.Path = path
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if 'Restore' not in obj.State and prop == "Radius":
|
||||
if "Restore" not in obj.State and prop == "Radius":
|
||||
job = PathUtils.findParentJob(obj)
|
||||
if job:
|
||||
job.Proxy.setCenterOfRotation(self.center(obj))
|
||||
@@ -141,12 +168,11 @@ class ObjectDressup:
|
||||
|
||||
|
||||
class TaskPanel:
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/AxisMapEdit.ui")
|
||||
self.radius = PathGui.QuantitySpinBox(self.form.radius, obj, 'Radius')
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDragKnife", "Edit Dragknife Dress-up"))
|
||||
self.radius = PathGui.QuantitySpinBox(self.form.radius, obj, "Radius")
|
||||
FreeCAD.ActiveDocument.openTransaction("Edit Dragknife Dress-up")
|
||||
|
||||
def reject(self):
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
@@ -187,7 +213,6 @@ class TaskPanel:
|
||||
|
||||
|
||||
class ViewProviderDressup:
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.obj = vobj.Object
|
||||
|
||||
@@ -226,7 +251,7 @@ class ViewProviderDressup:
|
||||
return None
|
||||
|
||||
def onDelete(self, arg1=None, arg2=None):
|
||||
'''this makes sure that the base operation is added back to the project and visible'''
|
||||
"""this makes sure that the base operation is added back to the project and visible"""
|
||||
# pylint: disable=unused-argument
|
||||
if arg1.Object and arg1.Object.Base:
|
||||
FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True
|
||||
@@ -241,10 +266,14 @@ class CommandPathDressup:
|
||||
# pylint: disable=no-init
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Dressup',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "Axis Map Dress-up"),
|
||||
'Accel': "",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "Remap one axis to another.")}
|
||||
return {
|
||||
"Pixmap": "Path_Dressup",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_DressupAxisMap", "Axis Map Dress-up"),
|
||||
"Accel": "",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_DressupAxisMap", "Remap one axis to another."
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -258,35 +287,47 @@ class CommandPathDressup:
|
||||
# check that the selection contains exactly what we want
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "Please select one path object\n")
|
||||
)
|
||||
return
|
||||
if not selection[0].isDerivedFrom("Path::Feature"):
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "The selected object is not a path\n")
|
||||
)
|
||||
return
|
||||
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "Please select a Path object")
|
||||
)
|
||||
return
|
||||
|
||||
# everything ok!
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupAxisMap", "Create Dress-up"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Dress-up")
|
||||
FreeCADGui.addModule("PathScripts.PathDressupAxisMap")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "AxisMapDressup")')
|
||||
FreeCADGui.doCommand('PathScripts.PathDressupAxisMap.ObjectDressup(obj)')
|
||||
FreeCADGui.doCommand('base = FreeCAD.ActiveDocument.' + selection[0].Name)
|
||||
FreeCADGui.doCommand('job = PathScripts.PathUtils.findParentJob(base)')
|
||||
FreeCADGui.doCommand('obj.Base = base')
|
||||
FreeCADGui.doCommand('obj.Radius = 45')
|
||||
FreeCADGui.doCommand('job.Proxy.addOperation(obj, base)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy = PathScripts.PathDressupAxisMap.ViewProviderDressup(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('Gui.ActiveDocument.getObject(base.Name).Visibility = False')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Document.setEdit(obj.ViewObject, 0)')
|
||||
FreeCADGui.doCommand(
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "AxisMapDressup")'
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathDressupAxisMap.ObjectDressup(obj)")
|
||||
FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name)
|
||||
FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)")
|
||||
FreeCADGui.doCommand("obj.Base = base")
|
||||
FreeCADGui.doCommand("obj.Radius = 45")
|
||||
FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)")
|
||||
FreeCADGui.doCommand(
|
||||
"obj.ViewObject.Proxy = PathScripts.PathDressupAxisMap.ViewProviderDressup(obj.ViewObject)"
|
||||
)
|
||||
FreeCADGui.doCommand(
|
||||
"Gui.ActiveDocument.getObject(base.Name).Visibility = False"
|
||||
)
|
||||
FreeCADGui.doCommand("obj.ViewObject.Document.setEdit(obj.ViewObject, 0)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_DressupAxisMap', CommandPathDressup())
|
||||
FreeCADGui.addCommand("Path_DressupAxisMap", CommandPathDressup())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathDressup... done\n")
|
||||
|
||||
@@ -30,11 +30,14 @@ import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
from PySide import QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PathScripts.PathGeom import CmdMoveArc, CmdMoveStraight
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
"""Z Depth Correction Dressup. This dressup takes a probe file as input and does bilinear interpolation of the Zdepths to correct for a surface which is not parallel to the milling table/bed. The probe file should conform to the format specified by the linuxcnc G38 probe logging: 9-number coordinate consisting of XYZABCUVW http://linuxcnc.org/docs/html/gcode/g-code.html#gcode:g38
|
||||
"""
|
||||
@@ -43,32 +46,51 @@ LOGLEVEL = False
|
||||
|
||||
LOG_MODULE = PathLog.thisModule()
|
||||
|
||||
if LOGLEVEL:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, LOG_MODULE)
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']
|
||||
rapidcommands = ['G0', 'G00']
|
||||
arccommands = ['G2', 'G3', 'G02', 'G03']
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectDressup:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("Path_DressupAxisMap", "The base path to modify"))
|
||||
obj.addProperty("App::PropertyFile", "probefile", "ProbeData", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "The point file from the surface probing."))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Base",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFile",
|
||||
"probefile",
|
||||
"ProbeData",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The point file from the surface probing."
|
||||
),
|
||||
)
|
||||
obj.Proxy = self
|
||||
obj.addProperty("Part::PropertyPartShape", "interpSurface", "Path")
|
||||
obj.addProperty("App::PropertyDistance", "ArcInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Deflection distance for arc interpolation"))
|
||||
obj.addProperty("App::PropertyDistance", "SegInterpolate", "Interpolate", QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrectp", "break segments into smaller segments of this length."))
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ArcInterpolate",
|
||||
"Interpolate",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Deflection distance for arc interpolation"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"SegInterpolate",
|
||||
"Interpolate",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"break segments into smaller segments of this length.",
|
||||
),
|
||||
)
|
||||
obj.ArcInterpolate = 0.1
|
||||
obj.SegInterpolate = 1.0
|
||||
|
||||
@@ -95,12 +117,12 @@ class ObjectDressup:
|
||||
if filename == "":
|
||||
return
|
||||
|
||||
f1 = open(filename, 'r')
|
||||
f1 = open(filename, "r")
|
||||
|
||||
try:
|
||||
pointlist = []
|
||||
for line in f1.readlines():
|
||||
if line == '\n':
|
||||
if line == "\n":
|
||||
continue
|
||||
w = line.split()
|
||||
xval = round(float(w[0]), 2)
|
||||
@@ -148,32 +170,47 @@ class ObjectDressup:
|
||||
pathlist = obj.Base.Path.Commands
|
||||
|
||||
newcommandlist = []
|
||||
currLocation = {'X': 0, 'Y': 0, 'Z': 0, 'F': 0}
|
||||
currLocation = {"X": 0, "Y": 0, "Z": 0, "F": 0}
|
||||
|
||||
for c in pathlist:
|
||||
PathLog.debug(c)
|
||||
PathLog.debug(" curLoc:{}".format(currLocation))
|
||||
newparams = dict(c.Parameters)
|
||||
zval = newparams.get("Z", currLocation['Z'])
|
||||
if c.Name in movecommands:
|
||||
curVec = FreeCAD.Vector(currLocation['X'], currLocation['Y'], currLocation['Z'])
|
||||
zval = newparams.get("Z", currLocation["Z"])
|
||||
if c.Name in CmdMoveStraight + CmdMoveArc:
|
||||
curVec = FreeCAD.Vector(
|
||||
currLocation["X"],
|
||||
currLocation["Y"],
|
||||
currLocation["Z"],
|
||||
)
|
||||
arcwire = PathGeom.edgeForCmd(c, curVec)
|
||||
if arcwire is None:
|
||||
continue
|
||||
if c.Name in arccommands:
|
||||
if c.Name in CmdMoveArc:
|
||||
pointlist = arcwire.discretize(Deflection=curveD)
|
||||
else:
|
||||
disc_number = int(arcwire.Length / sampleD)
|
||||
if disc_number > 1:
|
||||
pointlist = arcwire.discretize(Number=int(arcwire.Length / sampleD))
|
||||
pointlist = arcwire.discretize(
|
||||
Number=int(arcwire.Length / sampleD)
|
||||
)
|
||||
else:
|
||||
pointlist = [v.Point for v in arcwire.Vertexes]
|
||||
for point in pointlist:
|
||||
offset = self._bilinearInterpolate(surface, point.x, point.y)
|
||||
newcommand = Path.Command("G1", {'X': point.x, 'Y': point.y, 'Z': point.z + offset})
|
||||
offset = self._bilinearInterpolate(
|
||||
surface, point.x, point.y
|
||||
)
|
||||
newcommand = Path.Command(
|
||||
"G1",
|
||||
{
|
||||
"X": point.x,
|
||||
"Y": point.y,
|
||||
"Z": point.z + offset,
|
||||
},
|
||||
)
|
||||
newcommandlist.append(newcommand)
|
||||
currLocation.update(newcommand.Parameters)
|
||||
currLocation['Z'] = zval
|
||||
currLocation["Z"] = zval
|
||||
|
||||
else:
|
||||
# Non Feed Command
|
||||
@@ -184,12 +221,13 @@ class ObjectDressup:
|
||||
|
||||
|
||||
class TaskPanel:
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ZCorrectEdit.ui")
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Edit Z Correction Dress-up"))
|
||||
self.interpshape = FreeCAD.ActiveDocument.addObject("Part::Feature", "InterpolationSurface")
|
||||
FreeCAD.ActiveDocument.openTransaction("Edit Z Correction Dress-up")
|
||||
self.interpshape = FreeCAD.ActiveDocument.addObject(
|
||||
"Part::Feature", "InterpolationSurface"
|
||||
)
|
||||
self.interpshape.Shape = obj.interpSurface
|
||||
self.interpshape.ViewObject.Transparency = 60
|
||||
self.interpshape.ViewObject.ShapeColor = (1.00000, 1.00000, 0.01961)
|
||||
@@ -218,9 +256,9 @@ class TaskPanel:
|
||||
|
||||
if PathLog.getLevel(LOG_MODULE) == PathLog.Level.DEBUG:
|
||||
for obj in FreeCAD.ActiveDocument.Objects:
|
||||
if obj.Name.startswith('Shape'):
|
||||
if obj.Name.startswith("Shape"):
|
||||
FreeCAD.ActiveDocument.removeObject(obj.Name)
|
||||
print('object name %s' % self.obj.Name)
|
||||
print("object name %s" % self.obj.Name)
|
||||
if hasattr(self.obj.Proxy, "shapes"):
|
||||
PathLog.info("showing shapes attribute")
|
||||
for shapes in self.obj.Proxy.shapes.itervalues():
|
||||
@@ -249,14 +287,18 @@ class TaskPanel:
|
||||
self.form.SetProbePointFileName.clicked.connect(self.SetProbePointFileName)
|
||||
|
||||
def SetProbePointFileName(self):
|
||||
filename = QtGui.QFileDialog.getOpenFileName(self.form, translate("Path_Probe", "Select Probe Point File"), None, translate("Path_Probe", "All Files (*.*)"))
|
||||
filename = QtGui.QFileDialog.getOpenFileName(
|
||||
self.form,
|
||||
translate("Path_Probe", "Select Probe Point File"),
|
||||
None,
|
||||
translate("Path_Probe", "All Files (*.*)"),
|
||||
)
|
||||
if filename and filename[0]:
|
||||
self.obj.probefile = str(filename[0])
|
||||
self.setFields()
|
||||
|
||||
|
||||
class ViewProviderDressup:
|
||||
|
||||
def __init__(self, vobj):
|
||||
vobj.Proxy = self
|
||||
|
||||
@@ -289,7 +331,7 @@ class ViewProviderDressup:
|
||||
return None
|
||||
|
||||
def onDelete(self, arg1=None, arg2=None):
|
||||
'''this makes sure that the base operation is added back to the project and visible'''
|
||||
"""this makes sure that the base operation is added back to the project and visible"""
|
||||
FreeCADGui.ActiveDocument.getObject(arg1.Object.Base.Name).Visibility = True
|
||||
job = PathUtils.findParentJob(arg1.Object)
|
||||
job.Proxy.addOperation(arg1.Object.Base)
|
||||
@@ -298,18 +340,23 @@ class ViewProviderDressup:
|
||||
|
||||
|
||||
class CommandPathDressup:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Dressup',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Z Depth Correction Dress-up"),
|
||||
'Accel': "",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupZCorrect", "Use Probe Map to correct Z depth")}
|
||||
return {
|
||||
"Pixmap": "Path_Dressup",
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Path_DressupZCorrect", "Z Depth Correction Dress-up"
|
||||
),
|
||||
"Accel": "",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_DressupZCorrect", "Use Probe Map to correct Z depth"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
for o in FreeCAD.ActiveDocument.Objects:
|
||||
if o.Name[:3] == "Job":
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
@@ -317,32 +364,44 @@ class CommandPathDressup:
|
||||
# check that the selection contains exactly what we want
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select one path object\n"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "Please select one path object\n")
|
||||
)
|
||||
return
|
||||
if not selection[0].isDerivedFrom("Path::Feature"):
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "The selected object is not a path\n"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "The selected object is not a path\n")
|
||||
)
|
||||
return
|
||||
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
|
||||
FreeCAD.Console.PrintError(translate("Path_Dressup", "Please select a Path object"))
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Dressup", "Please select a Path object")
|
||||
)
|
||||
return
|
||||
|
||||
# everything ok!
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupZCorrect", "Create Dress-up"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Dress-up")
|
||||
FreeCADGui.addModule("PathScripts.PathDressupZCorrect")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")')
|
||||
FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ObjectDressup(obj)')
|
||||
FreeCADGui.doCommand('obj.Base = FreeCAD.ActiveDocument.' + selection[0].Name)
|
||||
FreeCADGui.doCommand('PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
FreeCADGui.doCommand('Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Document.setEdit(obj.ViewObject, 0)')
|
||||
FreeCADGui.doCommand(
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", "ZCorrectDressup")'
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathDressupZCorrect.ObjectDressup(obj)")
|
||||
FreeCADGui.doCommand("obj.Base = FreeCAD.ActiveDocument." + selection[0].Name)
|
||||
FreeCADGui.doCommand(
|
||||
"PathScripts.PathDressupZCorrect.ViewProviderDressup(obj.ViewObject)"
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)")
|
||||
FreeCADGui.doCommand(
|
||||
"Gui.ActiveDocument.getObject(obj.Base.Name).Visibility = False"
|
||||
)
|
||||
FreeCADGui.doCommand("obj.ViewObject.Document.setEdit(obj.ViewObject, 0)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_DressupZCorrect', CommandPathDressup())
|
||||
FreeCADGui.addCommand("Path_DressupZCorrect", CommandPathDressup())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathDressup... done\n")
|
||||
|
||||
@@ -25,7 +25,6 @@ from __future__ import print_function
|
||||
|
||||
|
||||
from Generators import drill_generator as generator
|
||||
from PySide import QtCore
|
||||
import FreeCAD
|
||||
import Part
|
||||
import Path
|
||||
@@ -35,6 +34,7 @@ import PathScripts.PathCircularHoleBase as PathCircularHoleBase
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__title__ = "Path Drilling Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -42,21 +42,56 @@ __url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Path Drilling operation."
|
||||
__contributors__ = "IMBack!"
|
||||
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"""Proxy object for Drilling operation."""
|
||||
|
||||
@classmethod
|
||||
def propertyEnumerations(self, dataType="data"):
|
||||
"""helixOpPropertyEnumerations(dataType="data")... return property enumeration lists of specified dataType.
|
||||
Args:
|
||||
dataType = 'data', 'raw', 'translated'
|
||||
Notes:
|
||||
'data' is list of internal string literals used in code
|
||||
'raw' is list of (translated_text, data_string) tuples
|
||||
'translated' is list of translated string literals
|
||||
"""
|
||||
|
||||
# Enumeration lists for App::PropertyEnumeration properties
|
||||
enums = {
|
||||
"ReturnLevel": [
|
||||
(translate("Path_Drilling", "G99"), "G99"),
|
||||
(translate("Path_Drilling", "G98"), "G98"),
|
||||
], # How high to retract after a drilling move
|
||||
"ExtraOffset": [
|
||||
(translate("Path_Drilling", "None"), "None"),
|
||||
(translate("Path_Drilling", "Drill Tip"), "Drill Tip"),
|
||||
(translate("Path_Drilling", "2x Drill Tip"), "2x Drill Tip"),
|
||||
], # extra drilling depth to clear drill taper
|
||||
}
|
||||
|
||||
if dataType == "raw":
|
||||
return enums
|
||||
|
||||
data = list()
|
||||
idx = 0 if dataType == "translated" else 1
|
||||
|
||||
PathLog.debug(enums)
|
||||
|
||||
for k, v in enumerate(enums):
|
||||
data.append((v, [tup[idx] for tup in enums[v]]))
|
||||
PathLog.debug(data)
|
||||
|
||||
return data
|
||||
|
||||
def circularHoleFeatures(self, obj):
|
||||
"""circularHoleFeatures(obj) ... drilling works on anything, turn on all Base geometries and Locations."""
|
||||
return (
|
||||
@@ -69,7 +104,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"App::PropertyLength",
|
||||
"PeckDepth",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Incremental Drill depth before retracting to clear chips",
|
||||
),
|
||||
@@ -78,27 +113,25 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"App::PropertyBool",
|
||||
"PeckEnabled",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP("App::Property", "Enable pecking"),
|
||||
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"
|
||||
),
|
||||
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"),
|
||||
QT_TRANSLATE_NOOP("App::Property", "Enable dwell"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"AddTipLength",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Calculate the tip length and subtract from final depth",
|
||||
),
|
||||
@@ -107,7 +140,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"App::PropertyEnumeration",
|
||||
"ReturnLevel",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Controls how tool retracts Default=G99"
|
||||
),
|
||||
)
|
||||
@@ -115,7 +148,7 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"App::PropertyDistance",
|
||||
"RetractHeight",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The height where feed starts and height during retract tool when path is finished while in a peck operation",
|
||||
),
|
||||
@@ -124,17 +157,11 @@ class ObjectDrilling(PathCircularHoleBase.ObjectOp):
|
||||
"App::PropertyEnumeration",
|
||||
"ExtraOffset",
|
||||
"Drill",
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"App::Property", "How far the drill depth is extended"
|
||||
),
|
||||
QT_TRANSLATE_NOOP("App::Property", "How far the drill depth is extended"),
|
||||
)
|
||||
|
||||
obj.ReturnLevel = ["G99", "G98"] # Canned Cycle Return Level
|
||||
obj.ExtraOffset = [
|
||||
"None",
|
||||
"Drill Tip",
|
||||
"2x Drill Tip",
|
||||
] # Canned Cycle Return Level
|
||||
for n in self.propertyEnumerations():
|
||||
setattr(obj, n[0], n[1])
|
||||
|
||||
def circularHoleExecute(self, obj, holes):
|
||||
"""circularHoleExecute(obj, holes) ... generate drill operation for each hole in holes."""
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathCircularHoleBaseGui as PathCircularHoleBaseGui
|
||||
import PathScripts.PathDrilling as PathDrilling
|
||||
import PathScripts.PathGui as PathGui
|
||||
@@ -37,23 +37,27 @@ __url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "UI and Command for Path Drilling Operation."
|
||||
__contributors__ = "IMBack!"
|
||||
|
||||
LOGLEVEL = False
|
||||
|
||||
if LOGLEVEL:
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.NOTICE, PathLog.thisModule())
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
'''Controller for the drilling operation's page'''
|
||||
"""Controller for the drilling operation's page"""
|
||||
|
||||
def initPage(self, obj):
|
||||
# pylint: disable=attribute-defined-outside-init
|
||||
self.peckDepthSpinBox = PathGui.QuantitySpinBox(self.form.peckDepth, obj, 'PeckDepth')
|
||||
self.peckRetractSpinBox = PathGui.QuantitySpinBox(self.form.peckRetractHeight, obj, 'RetractHeight')
|
||||
self.dwellTimeSpinBox = PathGui.QuantitySpinBox(self.form.dwellTime, obj, 'DwellTime')
|
||||
self.peckDepthSpinBox = PathGui.QuantitySpinBox(
|
||||
self.form.peckDepth, obj, "PeckDepth"
|
||||
)
|
||||
self.peckRetractSpinBox = PathGui.QuantitySpinBox(
|
||||
self.form.peckRetractHeight, obj, "RetractHeight"
|
||||
)
|
||||
self.dwellTimeSpinBox = PathGui.QuantitySpinBox(
|
||||
self.form.dwellTime, obj, "DwellTime"
|
||||
)
|
||||
|
||||
def registerSignalHandlers(self, obj):
|
||||
self.form.peckEnabled.toggled.connect(self.form.peckDepth.setEnabled)
|
||||
@@ -76,17 +80,38 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
self.form.dwellTimelabel.setEnabled(True)
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... return UI'''
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDrillingEdit.ui")
|
||||
"""getForm() ... return UI"""
|
||||
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpDrillingEdit.ui")
|
||||
|
||||
def updateQuantitySpinBoxes(self, index = None):
|
||||
comboToPropertyMap = [("ExtraOffset", "ExtraOffset")]
|
||||
enumTups = PathDrilling.ObjectDrilling.propertyEnumerations(dataType="raw")
|
||||
self.populateCombobox(form, enumTups, comboToPropertyMap)
|
||||
|
||||
return form
|
||||
|
||||
def populateCombobox(self, form, enumTups, comboBoxesPropertyMap):
|
||||
"""fillComboboxes(form, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations
|
||||
** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol.
|
||||
Args:
|
||||
form = UI form
|
||||
enumTups = list of (translated_text, data_string) tuples
|
||||
comboBoxesPropertyMap = list of (translated_text, data_string) tuples
|
||||
"""
|
||||
# Load appropriate enumerations in each combobox
|
||||
for cb, prop in comboBoxesPropertyMap:
|
||||
box = getattr(form, cb) # Get the combobox
|
||||
box.clear() # clear the combobox
|
||||
for text, data in enumTups[prop]: # load enumerations
|
||||
box.addItem(text, data)
|
||||
|
||||
def updateQuantitySpinBoxes(self, index=None):
|
||||
# pylint: disable=unused-argument
|
||||
self.peckDepthSpinBox.updateSpinBox()
|
||||
self.peckRetractSpinBox.updateSpinBox()
|
||||
self.dwellTimeSpinBox.updateSpinBox()
|
||||
|
||||
def getFields(self, obj):
|
||||
'''setFields(obj) ... update obj's properties with values from the UI'''
|
||||
"""setFields(obj) ... update obj's properties with values from the UI"""
|
||||
PathLog.track()
|
||||
self.peckDepthSpinBox.updateProperty()
|
||||
self.peckRetractSpinBox.updateProperty()
|
||||
@@ -96,14 +121,14 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
obj.DwellEnabled = self.form.dwellEnabled.isChecked()
|
||||
if obj.PeckEnabled != self.form.peckEnabled.isChecked():
|
||||
obj.PeckEnabled = self.form.peckEnabled.isChecked()
|
||||
if obj.ExtraOffset != str(self.form.ExtraOffset.currentText()):
|
||||
obj.ExtraOffset = str(self.form.ExtraOffset.currentText())
|
||||
if obj.ExtraOffset != str(self.form.ExtraOffset.currentData()):
|
||||
obj.ExtraOffset = str(self.form.ExtraOffset.currentData())
|
||||
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... update UI with obj properties' values'''
|
||||
"""setFields(obj) ... update UI with obj properties' values"""
|
||||
PathLog.track()
|
||||
self.updateQuantitySpinBoxes()
|
||||
|
||||
@@ -122,9 +147,8 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals which cause the receiver to update the model'''
|
||||
"""getSignalsForUpdate(obj) ... return list of signals which cause the receiver to update the model"""
|
||||
signals = []
|
||||
|
||||
signals.append(self.form.peckRetractHeight.editingFinished)
|
||||
@@ -139,15 +163,21 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
|
||||
return signals
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop in ['PeckDepth', 'RetractHeight'] and not prop in ['Base', 'Disabled']:
|
||||
if prop in ["PeckDepth", "RetractHeight"] and not prop in ["Base", "Disabled"]:
|
||||
self.updateQuantitySpinBoxes()
|
||||
|
||||
Command = PathOpGui.SetupOperation('Drilling',
|
||||
PathDrilling.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Drilling',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Drilling", "Drilling"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Drilling", "Creates a Path Drilling object from a features of a base object"),
|
||||
PathDrilling.SetupProperties)
|
||||
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Drilling",
|
||||
PathDrilling.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Drilling",
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Drilling", "Drilling"),
|
||||
QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path_Drilling",
|
||||
"Creates a Path Drilling object from a features of a base object",
|
||||
),
|
||||
PathDrilling.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathDrillingGui... done\n")
|
||||
|
||||
@@ -29,35 +29,34 @@ import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathPreferences as PathPreferences
|
||||
|
||||
import traceback
|
||||
|
||||
import math
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
from PySide import QtCore
|
||||
|
||||
__doc__ = "Class and implementation of Path Vcarve operation"
|
||||
|
||||
PRIMARY = 0
|
||||
PRIMARY = 0
|
||||
SECONDARY = 1
|
||||
EXTERIOR1 = 2
|
||||
EXTERIOR2 = 3
|
||||
COLINEAR = 4
|
||||
TWIN = 5
|
||||
COLINEAR = 4
|
||||
TWIN = 5
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
VD = []
|
||||
Vertex = {}
|
||||
|
||||
_sorting = 'global'
|
||||
_sorting = "global"
|
||||
|
||||
|
||||
def _collectVoronoiWires(vd):
|
||||
@@ -104,13 +103,13 @@ def _collectVoronoiWires(vd):
|
||||
we = []
|
||||
vFirst = knots[0]
|
||||
vStart = vFirst
|
||||
vLast = vFirst
|
||||
vLast = vFirst
|
||||
if len(vertex[vStart]):
|
||||
while vStart is not None:
|
||||
vLast = vStart
|
||||
edges = vertex[vStart]
|
||||
vLast = vStart
|
||||
edges = vertex[vStart]
|
||||
if len(edges) > 0:
|
||||
edge = edges[0]
|
||||
edge = edges[0]
|
||||
vStart = traverse(vStart, edge, we)
|
||||
else:
|
||||
vStart = None
|
||||
@@ -133,11 +132,11 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)):
|
||||
return (p, l)
|
||||
|
||||
begin = {}
|
||||
end = {}
|
||||
end = {}
|
||||
|
||||
for i, w in enumerate(wires):
|
||||
begin[i] = w[0].Vertices[0].toPoint()
|
||||
end[i] = w[-1].Vertices[1].toPoint()
|
||||
end[i] = w[-1].Vertices[1].toPoint()
|
||||
|
||||
result = []
|
||||
while begin:
|
||||
@@ -145,23 +144,24 @@ def _sortVoronoiWires(wires, start=FreeCAD.Vector(0, 0, 0)):
|
||||
(eIdx, eLen) = closestTo(start, end)
|
||||
if bLen < eLen:
|
||||
result.append(wires[bIdx])
|
||||
start = end[bIdx]
|
||||
del begin[bIdx]
|
||||
del end[bIdx]
|
||||
start = end[bIdx]
|
||||
del begin[bIdx]
|
||||
del end[bIdx]
|
||||
else:
|
||||
result.append([e.Twin for e in reversed(wires[eIdx])])
|
||||
start = begin[eIdx]
|
||||
del begin[eIdx]
|
||||
del end[eIdx]
|
||||
del begin[eIdx]
|
||||
del end[eIdx]
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class _Geometry(object):
|
||||
'''POD class so the limits only have to be calculated once.'''
|
||||
"""POD class so the limits only have to be calculated once."""
|
||||
|
||||
def __init__(self, zStart, zStop, zScale):
|
||||
self.start = zStart
|
||||
self.stop = zStop
|
||||
self.stop = zStop
|
||||
self.scale = zScale
|
||||
|
||||
@classmethod
|
||||
@@ -170,8 +170,8 @@ class _Geometry(object):
|
||||
rMin = float(tool.TipDiameter) / 2.0
|
||||
toolangle = math.tan(math.radians(tool.CuttingEdgeAngle.Value / 2.0))
|
||||
zScale = 1.0 / toolangle
|
||||
zStop = zStart - rMax * zScale
|
||||
zOff = rMin * zScale
|
||||
zStop = zStart - rMax * zScale
|
||||
zOff = rMin * zScale
|
||||
|
||||
return _Geometry(zStart + zOff, max(zStop + zOff, zFinal), zScale)
|
||||
|
||||
@@ -182,45 +182,74 @@ class _Geometry(object):
|
||||
|
||||
return cls.FromTool(obj.ToolController.Tool, zStart, finalDepth)
|
||||
|
||||
|
||||
def _calculate_depth(MIC, geom):
|
||||
# given a maximum inscribed circle (MIC) and tool angle,
|
||||
# return depth of cut relative to zStart.
|
||||
depth = geom.start - round(MIC * geom.scale, 4)
|
||||
PathLog.debug('zStart value: {} depth: {}'.format(geom.start, depth))
|
||||
PathLog.debug("zStart value: {} depth: {}".format(geom.start, depth))
|
||||
|
||||
return max(depth, geom.stop)
|
||||
|
||||
|
||||
def _getPartEdge(edge, depths):
|
||||
dist = edge.getDistances()
|
||||
zBegin = _calculate_depth(dist[0], depths)
|
||||
zEnd = _calculate_depth(dist[1], depths)
|
||||
zEnd = _calculate_depth(dist[1], depths)
|
||||
return edge.toShape(zBegin, zEnd)
|
||||
|
||||
|
||||
class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
'''Proxy class for Vcarve operation.'''
|
||||
"""Proxy class for Vcarve operation."""
|
||||
|
||||
def opFeatures(self, obj):
|
||||
'''opFeatures(obj) ... return all standard features and edges based geomtries'''
|
||||
return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureDepths | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant
|
||||
"""opFeatures(obj) ... return all standard features and edges based geomtries"""
|
||||
return (
|
||||
PathOp.FeatureTool
|
||||
| PathOp.FeatureHeights
|
||||
| PathOp.FeatureDepths
|
||||
| PathOp.FeatureBaseFaces
|
||||
| PathOp.FeatureCoolant
|
||||
)
|
||||
|
||||
def setupAdditionalProperties(self, obj):
|
||||
if not hasattr(obj, 'BaseShapes'):
|
||||
obj.addProperty("App::PropertyLinkList", "BaseShapes", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Additional base objects to be engraved"))
|
||||
obj.setEditorMode('BaseShapes', 2) # hide
|
||||
if not hasattr(obj, "BaseShapes"):
|
||||
obj.addProperty(
|
||||
"App::PropertyLinkList",
|
||||
"BaseShapes",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Additional base objects to be engraved"
|
||||
),
|
||||
)
|
||||
obj.setEditorMode("BaseShapes", 2) # hide
|
||||
|
||||
def initOperation(self, obj):
|
||||
'''initOperation(obj) ... create vcarve specific properties.'''
|
||||
obj.addProperty("App::PropertyFloat", "Discretize", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"The deflection value for discretizing arcs"))
|
||||
obj.addProperty("App::PropertyFloat", "Colinear", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve",
|
||||
"Cutoff for removing colinear segments (degrees). \
|
||||
default=10.0."))
|
||||
obj.addProperty("App::PropertyFloat", "Tolerance", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathVcarve", ""))
|
||||
"""initOperation(obj) ... create vcarve specific properties."""
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Discretize",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The deflection value for discretizing arcs"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Colinear",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Cutoff for removing colinear segments (degrees). \
|
||||
default=10.0.",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"Tolerance",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Vcarve Tolerance"),
|
||||
)
|
||||
obj.Colinear = 10.0
|
||||
obj.Discretize = 0.01
|
||||
obj.Tolerance = PathPreferences.defaultGeometryTolerance()
|
||||
@@ -237,27 +266,31 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
return edges
|
||||
|
||||
def buildPathMedial(self, obj, faces):
|
||||
'''constructs a medial axis path using openvoronoi'''
|
||||
"""constructs a medial axis path using openvoronoi"""
|
||||
|
||||
def insert_many_wires(vd, wires):
|
||||
for wire in wires:
|
||||
PathLog.debug('discretize value: {}'.format(obj.Discretize))
|
||||
PathLog.debug("discretize value: {}".format(obj.Discretize))
|
||||
pts = wire.discretize(QuasiDeflection=obj.Discretize)
|
||||
ptv = [FreeCAD.Vector(p.x, p.y) for p in pts]
|
||||
ptv.append(ptv[0])
|
||||
|
||||
for i in range(len(pts)):
|
||||
vd.addSegment(ptv[i], ptv[i+1])
|
||||
vd.addSegment(ptv[i], ptv[i + 1])
|
||||
|
||||
def cutWire(edges):
|
||||
path = []
|
||||
path.append(Path.Command("G0 Z{}".format(obj.SafeHeight.Value)))
|
||||
e = edges[0]
|
||||
p = e.valueAt(e.FirstParameter)
|
||||
path.append(Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value)))
|
||||
path.append(
|
||||
Path.Command("G0 X{} Y{} Z{}".format(p.x, p.y, obj.SafeHeight.Value))
|
||||
)
|
||||
hSpeed = obj.ToolController.HorizFeed.Value
|
||||
vSpeed = obj.ToolController.VertFeed.Value
|
||||
path.append(Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed)))
|
||||
path.append(
|
||||
Path.Command("G1 X{} Y{} Z{} F{}".format(p.x, p.y, p.z, vSpeed))
|
||||
)
|
||||
for e in edges:
|
||||
path.extend(PathGeom.cmdsForEdge(e, hSpeed=hSpeed, vSpeed=vSpeed))
|
||||
|
||||
@@ -274,19 +307,22 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
for e in vd.Edges:
|
||||
e.Color = PRIMARY if e.isPrimary() else SECONDARY
|
||||
vd.colorExterior(EXTERIOR1)
|
||||
vd.colorExterior(EXTERIOR2,
|
||||
lambda v: not f.isInside(v.toPoint(f.BoundBox.ZMin),
|
||||
obj.Tolerance, True))
|
||||
vd.colorExterior(
|
||||
EXTERIOR2,
|
||||
lambda v: not f.isInside(
|
||||
v.toPoint(f.BoundBox.ZMin), obj.Tolerance, True
|
||||
),
|
||||
)
|
||||
vd.colorColinear(COLINEAR, obj.Colinear)
|
||||
vd.colorTwins(TWIN)
|
||||
|
||||
wires = _collectVoronoiWires(vd)
|
||||
if _sorting != 'global':
|
||||
if _sorting != "global":
|
||||
wires = _sortVoronoiWires(wires)
|
||||
voronoiWires.extend(wires)
|
||||
VD.append((f, vd, wires))
|
||||
|
||||
if _sorting == 'global':
|
||||
if _sorting == "global":
|
||||
voronoiWires = _sortVoronoiWires(voronoiWires)
|
||||
|
||||
geom = _Geometry.FromObj(obj, self.model[0])
|
||||
@@ -301,14 +337,23 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
self.commandlist = pathlist
|
||||
|
||||
def opExecute(self, obj):
|
||||
'''opExecute(obj) ... process engraving operation'''
|
||||
"""opExecute(obj) ... process engraving operation"""
|
||||
PathLog.track()
|
||||
|
||||
if not hasattr(obj.ToolController.Tool, "CuttingEdgeAngle"):
|
||||
PathLog.error(translate("Path_Vcarve", "VCarve requires an engraving cutter with CuttingEdgeAngle"))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path_Vcarve",
|
||||
"VCarve requires an engraving cutter with CuttingEdgeAngle",
|
||||
)
|
||||
)
|
||||
|
||||
if obj.ToolController.Tool.CuttingEdgeAngle >= 180.0:
|
||||
PathLog.error(translate("Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees."))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path_Vcarve", "Engraver Cutting Edge Angle must be < 180 degrees."
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
@@ -325,27 +370,33 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
|
||||
if not faces:
|
||||
for model in self.model:
|
||||
if model.isDerivedFrom('Sketcher::SketchObject') or model.isDerivedFrom('Part::Part2DObject'):
|
||||
if model.isDerivedFrom(
|
||||
"Sketcher::SketchObject"
|
||||
) or model.isDerivedFrom("Part::Part2DObject"):
|
||||
faces.extend(model.Shape.Faces)
|
||||
|
||||
if faces:
|
||||
self.buildPathMedial(obj, faces)
|
||||
else:
|
||||
PathLog.error(translate('PathVcarve', 'The Job Base Object has no engraveable element. Engraving operation will produce no output.'))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathVcarve",
|
||||
"The Job Base Object has no engraveable element. Engraving operation will produce no output.",
|
||||
)
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
#PathLog.error(e)
|
||||
#traceback.print_exc()
|
||||
PathLog.error(translate('PathVcarve', 'Error processing Base object. Engraving operation will produce no output.'))
|
||||
#raise e
|
||||
PathLog.error(
|
||||
"Error processing Base object. Engraving operation will produce no output."
|
||||
)
|
||||
|
||||
def opUpdateDepths(self, obj, ignoreErrors=False):
|
||||
'''updateDepths(obj) ... engraving is always done at the top most z-value'''
|
||||
"""updateDepths(obj) ... engraving is always done at the top most z-value"""
|
||||
job = PathUtils.findParentJob(obj)
|
||||
self.opSetDefaultValues(obj, job)
|
||||
|
||||
def opSetDefaultValues(self, obj, job):
|
||||
'''opSetDefaultValues(obj) ... set depths for vcarving'''
|
||||
"""opSetDefaultValues(obj) ... set depths for vcarving"""
|
||||
if PathOp.FeatureDepths & self.opFeatures(obj):
|
||||
if job and len(job.Model.Group) > 0:
|
||||
bb = job.Proxy.modelBoundBox(job)
|
||||
@@ -355,15 +406,20 @@ class ObjectVcarve(PathEngraveBase.ObjectOp):
|
||||
obj.OpFinalDepth = -0.1
|
||||
|
||||
def isToolSupported(self, obj, tool):
|
||||
'''isToolSupported(obj, tool) ... returns True if v-carve op can work with tool.'''
|
||||
return hasattr(tool, 'Diameter') and hasattr(tool, 'CuttingEdgeAngle') and hasattr(tool, 'TipDiameter')
|
||||
"""isToolSupported(obj, tool) ... returns True if v-carve op can work with tool."""
|
||||
return (
|
||||
hasattr(tool, "Diameter")
|
||||
and hasattr(tool, "CuttingEdgeAngle")
|
||||
and hasattr(tool, "TipDiameter")
|
||||
)
|
||||
|
||||
|
||||
def SetupProperties():
|
||||
return ["Discretize"]
|
||||
|
||||
|
||||
def Create(name, obj=None, parentJob=None):
|
||||
'''Create(name) ... Creates and returns a Vcarve operation.'''
|
||||
"""Create(name) ... Creates and returns a Vcarve operation."""
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectVcarve(obj, name, parentJob)
|
||||
|
||||
@@ -22,32 +22,31 @@
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathGui as PGui # ensure Path/Gui/Resources are loaded
|
||||
import PathScripts.PathVcarve as PathVcarve
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
|
||||
__title__ = "Path Vcarve Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Vcarve operation page controller and command implementation."
|
||||
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
'''Enhanced base geometry page to also allow special base objects.'''
|
||||
"""Enhanced base geometry page to also allow special base objects."""
|
||||
|
||||
def super(self):
|
||||
return super(TaskPanelBaseGeometryPage, self)
|
||||
@@ -60,17 +59,25 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
job = PathUtils.findParentJob(self.obj)
|
||||
base = job.Proxy.resourceClone(job, sel.Object)
|
||||
if not base:
|
||||
PathLog.notice((translate("Path", "%s is not a Base Model object of the job %s") + "\n") % (sel.Object.Label, job.Label))
|
||||
PathLog.notice(
|
||||
(
|
||||
translate("Path", "%s is not a Base Model object of the job %s")
|
||||
+ "\n"
|
||||
)
|
||||
% (sel.Object.Label, job.Label)
|
||||
)
|
||||
continue
|
||||
if base in shapes:
|
||||
PathLog.notice((translate("Path", "Base shape %s already in the list") + "\n") % (sel.Object.Label))
|
||||
PathLog.notice(
|
||||
"Base shape %s already in the list".format(sel.Object.Label)
|
||||
)
|
||||
continue
|
||||
if base.isDerivedFrom('Part::Part2DObject'):
|
||||
if base.isDerivedFrom("Part::Part2DObject"):
|
||||
if sel.HasSubObjects:
|
||||
# selectively add some elements of the drawing to the Base
|
||||
for sub in sel.SubElementNames:
|
||||
if 'Vertex' in sub:
|
||||
PathLog.info(translate("Path", "Ignoring vertex"))
|
||||
if "Vertex" in sub:
|
||||
PathLog.info("Ignoring vertex")
|
||||
else:
|
||||
self.obj.Proxy.addBase(self.obj, base, sub)
|
||||
else:
|
||||
@@ -107,20 +114,22 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
sub = item.data(self.super().DataObjectSub)
|
||||
if not sub:
|
||||
shapes.append(obj)
|
||||
PathLog.debug("Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes))
|
||||
PathLog.debug(
|
||||
"Setting new base shapes: %s -> %s" % (self.obj.BaseShapes, shapes)
|
||||
)
|
||||
self.obj.BaseShapes = shapes
|
||||
return self.super().updateBase()
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Vcarve operation.'''
|
||||
"""Page controller class for the Vcarve operation."""
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpVcarveEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's proprties'''
|
||||
"""getFields(obj) ... transfers values from UI to obj's proprties"""
|
||||
if obj.Discretize != self.form.discretize.value():
|
||||
obj.Discretize = self.form.discretize.value()
|
||||
if obj.Colinear != self.form.colinearFilter.value():
|
||||
@@ -129,14 +138,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
"""setFields(obj) ... transfers obj's property values to UI"""
|
||||
self.form.discretize.setValue(obj.Discretize)
|
||||
self.form.colinearFilter.setValue(obj.Colinear)
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
|
||||
signals = []
|
||||
signals.append(self.form.discretize.editingFinished)
|
||||
signals.append(self.form.colinearFilter.editingFinished)
|
||||
@@ -145,16 +154,18 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
return signals
|
||||
|
||||
def taskPanelBaseGeometryPage(self, obj, features):
|
||||
'''taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries.'''
|
||||
"""taskPanelBaseGeometryPage(obj, features) ... return page for adding base geometries."""
|
||||
return TaskPanelBaseGeometryPage(obj, features)
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Vcarve',
|
||||
PathVcarve.Create,
|
||||
TaskPanelOpPage,
|
||||
'Path_Vcarve',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"),
|
||||
PathVcarve.SetupProperties)
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Vcarve",
|
||||
PathVcarve.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Vcarve",
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Vcarve"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Vcarve", "Creates a medial line engraving path"),
|
||||
PathVcarve.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathVcarveGui... done\n")
|
||||
|
||||
Reference in New Issue
Block a user