Merge pull request #5410 from sliptonic/bug/translateDeburr

[Path] Bug/translate deburr, fixture, inspect, probe
This commit is contained in:
sliptonic
2022-01-22 13:00:34 -06:00
committed by GitHub
6 changed files with 483 additions and 195 deletions

View File

@@ -29,54 +29,68 @@ import PathScripts.PathOp as PathOp
import PathScripts.PathOpTools as PathOpTools
import math
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
__title__ = "Path Deburr Operation"
__author__ = "sliptonic (Brad Collette), Schildkroet"
__url__ = "http://www.freecadweb.org"
__doc__ = "Deburr operation."
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
def toolDepthAndOffset(width, extraDepth, tool, printInfo):
'''toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given\n
parameters.'''
"""toolDepthAndOffset(width, extraDepth, tool) ... return tuple for given\n
parameters."""
if not hasattr(tool, 'Diameter'):
raise ValueError('Deburr requires tool with diameter\n')
if not hasattr(tool, "Diameter"):
raise ValueError("Deburr requires tool with diameter\n")
suppressInfo = False
if hasattr(tool, 'CuttingEdgeAngle'):
if hasattr(tool, "CuttingEdgeAngle"):
angle = float(tool.CuttingEdgeAngle)
if PathGeom.isRoughly(angle, 180) or PathGeom.isRoughly(angle, 0):
angle = 180
toolOffset = float(tool.Diameter) / 2
else:
if hasattr(tool, 'TipDiameter'):
if hasattr(tool, "TipDiameter"):
toolOffset = float(tool.TipDiameter) / 2
elif hasattr(tool, 'FlatRadius'):
elif hasattr(tool, "FlatRadius"):
toolOffset = float(tool.FlatRadius)
else:
toolOffset = 0.0
if printInfo and not suppressInfo:
FreeCAD.Console.PrintMessage(translate('PathDeburr', "The selected tool has no FlatRadius and no TipDiameter property. Assuming {}\n".format("Endmill" if angle == 180 else "V-Bit")))
FreeCAD.Console.PrintMessage(
translate(
"PathDeburr",
"The selected tool has no FlatRadius and no TipDiameter property. Assuming {}\n".format(
"Endmill" if angle == 180 else "V-Bit"
),
)
)
suppressInfo = True
else:
angle = 180
toolOffset = float(tool.Diameter) / 2
if printInfo:
FreeCAD.Console.PrintMessage(translate('PathDeburr', 'The selected tool has no CuttingEdgeAngle property. Assuming Endmill\n'))
FreeCAD.Console.PrintMessage(
translate(
"PathDeburr",
"The selected tool has no CuttingEdgeAngle property. Assuming Endmill\n",
)
)
suppressInfo = True
tan = math.tan(math.radians(angle / 2))
@@ -90,40 +104,119 @@ def toolDepthAndOffset(width, extraDepth, tool, printInfo):
class ObjectDeburr(PathEngraveBase.ObjectOp):
'''Proxy class for Deburr operation.'''
"""Proxy class for Deburr operation."""
def opFeatures(self, obj):
return PathOp.FeatureTool | PathOp.FeatureHeights | PathOp.FeatureStepDown | PathOp.FeatureBaseEdges | PathOp.FeatureBaseFaces | PathOp.FeatureCoolant | PathOp.FeatureBaseGeometry
return (
PathOp.FeatureTool
| PathOp.FeatureHeights
| PathOp.FeatureStepDown
| PathOp.FeatureBaseEdges
| PathOp.FeatureBaseFaces
| PathOp.FeatureCoolant
| PathOp.FeatureBaseGeometry
)
def initOperation(self, obj):
PathLog.track(obj.Label)
obj.addProperty('App::PropertyDistance', 'Width', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The desired width of the chamfer'))
obj.addProperty('App::PropertyDistance', 'ExtraDepth', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'The additional depth of the tool path'))
obj.addProperty('App::PropertyEnumeration', 'Join', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'How to join chamfer segments'))
obj.Join = ['Round', 'Miter']
obj.setEditorMode('Join', 2) # hide for now
obj.addProperty('App::PropertyEnumeration', 'Direction', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Direction of Operation'))
obj.Direction = ['CW', 'CCW']
obj.addProperty('App::PropertyEnumeration', 'Side', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Side of Operation'))
obj.Side = ['Outside', 'Inside']
obj.setEditorMode('Side', 2) # Hide property, it's calculated by op
obj.addProperty('App::PropertyInteger', 'EntryPoint', 'Deburr',
QtCore.QT_TRANSLATE_NOOP('PathDeburr', 'Select the segment, there the operations starts'))
obj.addProperty(
"App::PropertyDistance",
"Width",
"Deburr",
QT_TRANSLATE_NOOP("App::Property", "The desired width of the chamfer"),
)
obj.addProperty(
"App::PropertyDistance",
"ExtraDepth",
"Deburr",
QT_TRANSLATE_NOOP("App::Property", "The additional depth of the tool path"),
)
obj.addProperty(
"App::PropertyEnumeration",
"Join",
"Deburr",
QT_TRANSLATE_NOOP("App::Property", "How to join chamfer segments"),
)
# obj.Join = ["Round", "Miter"]
obj.setEditorMode("Join", 2) # hide for now
obj.addProperty(
"App::PropertyEnumeration",
"Direction",
"Deburr",
QT_TRANSLATE_NOOP("App::Property", "Direction of Operation"),
)
# obj.Direction = ["CW", "CCW"]
obj.addProperty(
"App::PropertyEnumeration",
"Side",
"Deburr",
QT_TRANSLATE_NOOP("App::Property", "Side of Operation"),
)
obj.Side = ["Outside", "Inside"]
obj.setEditorMode("Side", 2) # Hide property, it's calculated by op
obj.addProperty(
"App::PropertyInteger",
"EntryPoint",
"Deburr",
QT_TRANSLATE_NOOP(
"App::Property", "Select the segment, there the operations starts"
),
)
ENUMS = self.propertyEnumerations()
for n in ENUMS:
setattr(obj, n[0], n[1])
@classmethod
def propertyEnumerations(self, dataType="data"):
"""opPropertyEnumerations(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 = {
"Direction": [
(translate("Path", "CW"), "CW"),
(translate("Path", "CCW"), "CCW"),
], # this is the direction that the profile runs
"Join": [
(translate("PathDeburr", "Round"), "Round"),
(translate("PathDeburr", "Miter"), "Miter"),
], # this is the direction that the profile runs
}
if dataType == "raw":
return enums
data = list()
idx = 0 if dataType == "translated" else 1
PathLog.debug(enums)
for k, v in enumerate(enums):
# data[k] = [tup[idx] for tup in v]
data.append((v, [tup[idx] for tup in enums[v]]))
PathLog.debug(data)
return data
def opOnDocumentRestored(self, obj):
obj.setEditorMode('Join', 2) # hide for now
obj.setEditorMode("Join", 2) # hide for now
def opExecute(self, obj):
PathLog.track(obj.Label)
if not hasattr(self, 'printInfo'):
if not hasattr(self, "printInfo"):
self.printInfo = True
try:
(depth, offset, extraOffset, suppressInfo) = toolDepthAndOffset(obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo)
(depth, offset, extraOffset, suppressInfo) = toolDepthAndOffset(
obj.Width.Value, obj.ExtraDepth.Value, self.tool, self.printInfo
)
self.printInfo = not suppressInfo
except ValueError as e:
msg = "{} \n No path will be generated".format(e)
@@ -147,13 +240,15 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
for f in subs:
sub = base.Shape.getElement(f)
if type(sub) == Part.Edge: # Edge
if type(sub) == Part.Edge: # Edge
edges.append(sub)
elif type(sub) == Part.Face and sub.normalAt(0, 0) != FreeCAD.Vector(0, 0, 1): # Angled face
elif type(sub) == Part.Face and sub.normalAt(0, 0) != FreeCAD.Vector(
0, 0, 1
): # Angled face
# If an angled face is selected, the lower edge is projected to the height of the upper edge,
# to simulate an edge
# Find z value of upper edge
for edge in sub.Edges:
for p0 in edge.Vertexes:
@@ -161,7 +256,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
max_h = p0.Point.z
# Find biggest radius for top/bottom
for edge in sub.Edges:
for edge in sub.Edges:
if Part.Circle == type(edge.Curve):
if edge.Vertexes[0].Point.z == max_h:
if edge.Curve.Radius > radius_top:
@@ -169,63 +264,139 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
else:
if edge.Curve.Radius > radius_bottom:
radius_bottom = edge.Curve.Radius
# Search for lower edge and raise it to height of upper edge
for edge in sub.Edges:
if Part.Circle == type(edge.Curve): # Edge is a circle
if Part.Circle == type(edge.Curve): # Edge is a circle
if edge.Vertexes[0].Point.z < max_h:
if edge.Closed: # Circle
if edge.Closed: # Circle
# New center
center = FreeCAD.Vector(edge.Curve.Center.x, edge.Curve.Center.y, max_h)
new_edge = Part.makeCircle(edge.Curve.Radius, center, FreeCAD.Vector(0, 0, 1))
center = FreeCAD.Vector(
edge.Curve.Center.x, edge.Curve.Center.y, max_h
)
new_edge = Part.makeCircle(
edge.Curve.Radius,
center,
FreeCAD.Vector(0, 0, 1),
)
edges.append(new_edge)
# Modify offset for inner angled faces
if radius_bottom < radius_top:
offset -= 2 * extraOffset
break
else: # Arc
if edge.Vertexes[0].Point.z == edge.Vertexes[1].Point.z:
else: # Arc
if (
edge.Vertexes[0].Point.z
== edge.Vertexes[1].Point.z
):
# Arc vertexes are on same layer
l1 = math.sqrt((edge.Vertexes[0].Point.x - edge.Curve.Center.x)**2 + (edge.Vertexes[0].Point.y - edge.Curve.Center.y)**2)
l2 = math.sqrt((edge.Vertexes[1].Point.x - edge.Curve.Center.x)**2 + (edge.Vertexes[1].Point.y - edge.Curve.Center.y)**2)
l1 = math.sqrt(
(
edge.Vertexes[0].Point.x
- edge.Curve.Center.x
)
** 2
+ (
edge.Vertexes[0].Point.y
- edge.Curve.Center.y
)
** 2
)
l2 = math.sqrt(
(
edge.Vertexes[1].Point.x
- edge.Curve.Center.x
)
** 2
+ (
edge.Vertexes[1].Point.y
- edge.Curve.Center.y
)
** 2
)
# New center
center = FreeCAD.Vector(edge.Curve.Center.x, edge.Curve.Center.y, max_h)
center = FreeCAD.Vector(
edge.Curve.Center.x,
edge.Curve.Center.y,
max_h,
)
# Calculate angles based on x-axis (0 - PI/2)
start_angle = math.acos((edge.Vertexes[0].Point.x - edge.Curve.Center.x) / l1)
end_angle = math.acos((edge.Vertexes[1].Point.x - edge.Curve.Center.x) / l2)
start_angle = math.acos(
(
edge.Vertexes[0].Point.x
- edge.Curve.Center.x
)
/ l1
)
end_angle = math.acos(
(
edge.Vertexes[1].Point.x
- edge.Curve.Center.x
)
/ l2
)
# Angles are based on x-axis (Mirrored on x-axis) -> negative y value means negative angle
if edge.Vertexes[0].Point.y < edge.Curve.Center.y:
if (
edge.Vertexes[0].Point.y
< edge.Curve.Center.y
):
start_angle *= -1
if edge.Vertexes[1].Point.y < edge.Curve.Center.y:
if (
edge.Vertexes[1].Point.y
< edge.Curve.Center.y
):
end_angle *= -1
# Create new arc
new_edge = Part.ArcOfCircle(Part.Circle(center, FreeCAD.Vector(0,0,1), edge.Curve.Radius), start_angle, end_angle).toShape()
new_edge = Part.ArcOfCircle(
Part.Circle(
center,
FreeCAD.Vector(0, 0, 1),
edge.Curve.Radius,
),
start_angle,
end_angle,
).toShape()
edges.append(new_edge)
# Modify offset for inner angled faces
if radius_bottom < radius_top:
offset -= 2 * extraOffset
break
else: # Line
if edge.Vertexes[0].Point.z == edge.Vertexes[1].Point.z and edge.Vertexes[0].Point.z < max_h:
new_edge = Part.Edge(Part.LineSegment(FreeCAD.Vector(edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, max_h), FreeCAD.Vector(edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y, max_h)))
else: # Line
if (
edge.Vertexes[0].Point.z == edge.Vertexes[1].Point.z
and edge.Vertexes[0].Point.z < max_h
):
new_edge = Part.Edge(
Part.LineSegment(
FreeCAD.Vector(
edge.Vertexes[0].Point.x,
edge.Vertexes[0].Point.y,
max_h,
),
FreeCAD.Vector(
edge.Vertexes[1].Point.x,
edge.Vertexes[1].Point.y,
max_h,
),
)
)
edges.append(new_edge)
elif sub.Wires:
basewires.extend(sub.Wires)
else: # Flat face
else: # Flat face
basewires.append(Part.Wire(sub.Edges))
self.edges = edges # pylint: disable=attribute-defined-outside-init
@@ -244,7 +415,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
wires.append(wire)
# Set direction of op
forward = (obj.Direction == 'CW')
forward = obj.Direction == "CW"
# Set value of side
obj.Side = side[0]
@@ -258,7 +429,7 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
while z + obj.StepDown.Value < depth:
z = z + obj.StepDown.Value
zValues.append(z)
zValues.append(depth)
PathLog.track(obj.Label, depth, zValues)
@@ -269,30 +440,30 @@ class ObjectDeburr(PathEngraveBase.ObjectOp):
self.buildpathocc(obj, wires, zValues, True, forward, obj.EntryPoint)
def opRejectAddBase(self, obj, base, sub):
'''The chamfer op can only deal with features of the base model, all others are rejected.'''
"""The chamfer op can only deal with features of the base model, all others are rejected."""
return base not in self.model
def opSetDefaultValues(self, obj, job):
PathLog.track(obj.Label, job.Label)
obj.Width = '1 mm'
obj.ExtraDepth = '0.5 mm'
obj.Join = 'Round'
obj.setExpression('StepDown', '0 mm')
obj.StepDown = '0 mm'
obj.Direction = 'CW'
obj.Width = "1 mm"
obj.ExtraDepth = "0.5 mm"
obj.Join = "Round"
obj.setExpression("StepDown", "0 mm")
obj.StepDown = "0 mm"
obj.Direction = "CW"
obj.Side = "Outside"
obj.EntryPoint = 0
def SetupProperties():
setup = []
setup.append('Width')
setup.append('ExtraDepth')
setup.append("Width")
setup.append("ExtraDepth")
return setup
def Create(name, obj=None, parentJob=None):
'''Create(name) ... Creates and returns a Deburr operation.'''
"""Create(name) ... Creates and returns a Deburr operation."""
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectDeburr(obj, name, parentJob)

View File

@@ -27,6 +27,7 @@ import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathOpGui as PathOpGui
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Path Deburr Operation UI"
__author__ = "sliptonic (Brad Collette), Schildkroet"
@@ -34,16 +35,18 @@ __url__ = "https://www.freecadweb.org"
__doc__ = "Deburr operation page controller and command implementation."
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())
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)
@@ -53,51 +56,69 @@ class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Deburr operation.'''
"""Page controller class for the Deburr operation."""
_ui_form = ":/panels/PageOpDeburrEdit.ui"
def getForm(self):
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpDeburrEdit.ui")
form = FreeCADGui.PySideUic.loadUi(self._ui_form)
comboToPropertyMap = [("direction", "Direction")]
enumTups = PathDeburr.ObjectDeburr.propertyEnumerations(dataType="raw")
self.populateCombobox(form, enumTups, comboToPropertyMap)
return form
def initPage(self, obj):
self.opImagePath = "{}Mod/Path/Images/Ops/{}".format(FreeCAD.getHomePath(), 'chamfer.svg') # pylint: disable=attribute-defined-outside-init
self.opImage = QtGui.QPixmap(self.opImagePath) # pylint: disable=attribute-defined-outside-init
self.opImagePath = "{}Mod/Path/Images/Ops/{}".format(
FreeCAD.getHomePath(), "chamfer.svg"
) # pylint: disable=attribute-defined-outside-init
self.opImage = QtGui.QPixmap(
self.opImagePath
) # pylint: disable=attribute-defined-outside-init
self.form.opImage.setPixmap(self.opImage)
iconMiter = QtGui.QIcon(':/icons/edge-join-miter-not.svg')
iconMiter.addFile(':/icons/edge-join-miter.svg', state=QtGui.QIcon.On)
iconRound = QtGui.QIcon(':/icons/edge-join-round-not.svg')
iconRound.addFile(':/icons/edge-join-round.svg', state=QtGui.QIcon.On)
iconMiter = QtGui.QIcon(":/icons/edge-join-miter-not.svg")
iconMiter.addFile(":/icons/edge-join-miter.svg", state=QtGui.QIcon.On)
iconRound = QtGui.QIcon(":/icons/edge-join-round-not.svg")
iconRound.addFile(":/icons/edge-join-round.svg", state=QtGui.QIcon.On)
self.form.joinMiter.setIcon(iconMiter)
self.form.joinRound.setIcon(iconRound)
def getFields(self, obj):
PathGui.updateInputField(obj, 'Width', self.form.value_W)
PathGui.updateInputField(obj, 'ExtraDepth', self.form.value_h)
PathGui.updateInputField(obj, "Width", self.form.value_W)
PathGui.updateInputField(obj, "ExtraDepth", self.form.value_h)
if self.form.joinRound.isChecked():
obj.Join = 'Round'
obj.Join = "Round"
elif self.form.joinMiter.isChecked():
obj.Join = 'Miter'
obj.Join = "Miter"
if obj.Direction != str(self.form.direction.currentText()):
obj.Direction = str(self.form.direction.currentText())
if obj.Direction != str(self.form.direction.currentData()):
obj.Direction = str(self.form.direction.currentData())
self.updateToolController(obj, self.form.toolController)
self.updateCoolant(obj, self.form.coolantController)
def setFields(self, obj):
self.form.value_W.setText(FreeCAD.Units.Quantity(obj.Width.Value, FreeCAD.Units.Length).UserString)
self.form.value_h.setText(FreeCAD.Units.Quantity(obj.ExtraDepth.Value, FreeCAD.Units.Length).UserString)
self.form.value_W.setText(
FreeCAD.Units.Quantity(obj.Width.Value, FreeCAD.Units.Length).UserString
)
self.form.value_h.setText(
FreeCAD.Units.Quantity(
obj.ExtraDepth.Value, FreeCAD.Units.Length
).UserString
)
self.setupToolController(obj, self.form.toolController)
self.setupCoolant(obj, self.form.coolantController)
self.form.joinRound.setChecked('Round' == obj.Join)
self.form.joinMiter.setChecked('Miter' == obj.Join)
self.form.joinRound.setChecked("Round" == obj.Join)
self.form.joinMiter.setChecked("Miter" == obj.Join)
self.form.joinFrame.hide()
self.selectInComboBox(obj.Direction, self.form.direction)
def updateWidth(self):
PathGui.updateInputField(self.obj, 'Width', self.form.value_W)
PathGui.updateInputField(self.obj, "Width", self.form.value_W)
def updateExtraDepth(self):
PathGui.updateInputField(self.obj, 'ExtraDepth', self.form.value_h)
PathGui.updateInputField(self.obj, "ExtraDepth", self.form.value_h)
def getSignalsForUpdate(self, obj):
signals = []
@@ -114,16 +135,20 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.value_h.editingFinished.connect(self.updateExtraDepth)
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('Deburr',
PathDeburr.Create,
TaskPanelOpPage,
'Path_Deburr',
QtCore.QT_TRANSLATE_NOOP("PathDeburr", "Deburr"),
QtCore.QT_TRANSLATE_NOOP("PathDeburr", "Creates a Deburr Path along Edges or around Faces"),
PathDeburr.SetupProperties)
Command = PathOpGui.SetupOperation(
"Deburr",
PathDeburr.Create,
TaskPanelOpPage,
"Path_Deburr",
QT_TRANSLATE_NOOP("Path_Deburr", "Deburr"),
QT_TRANSLATE_NOOP(
"Path_Deburr", "Creates a Deburr Path along Edges or around Faces"
),
PathDeburr.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathDeburrGui... done\n")

View File

@@ -189,7 +189,7 @@ PathUtils.addToJob(obj)
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand("PathFixture", CommandPathFixture())
FreeCADGui.addCommand("Path_Fixture", CommandPathFixture())
FreeCAD.Console.PrintLog("Loading PathFixture... done\n")

View File

@@ -27,19 +27,17 @@ from PySide import QtCore, QtGui
import FreeCAD
import FreeCADGui
import Path
from PySide.QtCore import QT_TRANSLATE_NOOP
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
class GCodeHighlighter(QtGui.QSyntaxHighlighter):
def __init__(self, parent=None):
def convertcolor(c):
return QtGui.QColor(int((c >> 24) & 0xFF), int((c >> 16) & 0xFF), int((c >> 8) & 0xFF))
return QtGui.QColor(
int((c >> 24) & 0xFF), int((c >> 16) & 0xFF), int((c >> 8) & 0xFF)
)
super(GCodeHighlighter, self).__init__(parent)
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Editor")
@@ -63,19 +61,18 @@ class GCodeHighlighter(QtGui.QSyntaxHighlighter):
self.highlightingRules = []
numberFormat = QtGui.QTextCharFormat()
numberFormat.setForeground(colors[0])
self.highlightingRules.append(
(QtCore.QRegExp("[\\-0-9\\.]"), numberFormat))
self.highlightingRules.append((QtCore.QRegExp("[\\-0-9\\.]"), numberFormat))
keywordFormat = QtGui.QTextCharFormat()
keywordFormat.setForeground(colors[1])
keywordFormat.setFontWeight(QtGui.QFont.Bold)
keywordPatterns = ["\\bG[0-9]+\\b", "\\bM[0-9]+\\b"]
self.highlightingRules.extend(
[(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns])
[(QtCore.QRegExp(pattern), keywordFormat) for pattern in keywordPatterns]
)
speedFormat = QtGui.QTextCharFormat()
speedFormat.setFontWeight(QtGui.QFont.Bold)
speedFormat.setForeground(colors[2])
self.highlightingRules.append(
(QtCore.QRegExp("\\bF[0-9\\.]+\\b"), speedFormat))
self.highlightingRules.append((QtCore.QRegExp("\\bF[0-9\\.]+\\b"), speedFormat))
def highlightBlock(self, text):
@@ -93,7 +90,7 @@ class GCodeEditorDialog(QtGui.QDialog):
def __init__(self, PathObj, parent=FreeCADGui.getMainWindow()):
self.PathObj = PathObj.Path
if hasattr(PathObj, 'ToolController'):
if hasattr(PathObj, "ToolController"):
self.tool = PathObj.ToolController.Tool
else:
self.tool = None
@@ -103,10 +100,19 @@ class GCodeEditorDialog(QtGui.QDialog):
p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
c = p.GetUnsigned("DefaultHighlightPathColor", 4286382335)
Q = QtGui.QColor(int((c >> 24) & 0xFF), int((c >> 16) & 0xFF), int((c >> 8) & 0xFF))
highlightcolor = (Q.red() / 255., Q.green() / 255., Q.blue() / 255., Q.alpha() / 255.)
Q = QtGui.QColor(
int((c >> 24) & 0xFF), int((c >> 16) & 0xFF), int((c >> 8) & 0xFF)
)
highlightcolor = (
Q.red() / 255.0,
Q.green() / 255.0,
Q.blue() / 255.0,
Q.alpha() / 255.0,
)
self.selectionobj = FreeCAD.ActiveDocument.addObject("Path::Feature", "selection")
self.selectionobj = FreeCAD.ActiveDocument.addObject(
"Path::Feature", "selection"
)
self.selectionobj.ViewObject.LineWidth = 4
self.selectionobj.ViewObject.NormalColor = highlightcolor
@@ -123,14 +129,21 @@ class GCodeEditorDialog(QtGui.QDialog):
# Note
lab = QtGui.QLabel()
lab.setText(translate("Path_Inspect", "<b>Note</b>: Pressing OK will commit any change you make above to the object, but if the object is parametric, these changes will be overridden on recompute."))
lab.setText(
translate(
"Path_Inspect",
"<b>Note</b>: Pressing OK will commit any change you make above to the object, but if the object is parametric, these changes will be overridden on recompute.",
)
)
lab.setWordWrap(True)
layout.addWidget(lab)
# OK and Cancel buttons
self.buttons = QtGui.QDialogButtonBox(
QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Cancel,
QtCore.Qt.Horizontal, self)
QtCore.Qt.Horizontal,
self,
)
layout.addWidget(self.buttons)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
@@ -138,19 +151,19 @@ class GCodeEditorDialog(QtGui.QDialog):
self.finished.connect(self.cleanup)
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
Xpos = int(prefs.GetString('inspecteditorX', "0"))
Ypos = int(prefs.GetString('inspecteditorY', "0"))
height = int(prefs.GetString('inspecteditorH', "500"))
width = int(prefs.GetString('inspecteditorW', "600"))
Xpos = int(prefs.GetString("inspecteditorX", "0"))
Ypos = int(prefs.GetString("inspecteditorY", "0"))
height = int(prefs.GetString("inspecteditorH", "500"))
width = int(prefs.GetString("inspecteditorW", "600"))
self.move(Xpos, Ypos)
self.resize(width, height)
def cleanup(self):
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
prefs.SetString('inspecteditorX', str(self.x()))
prefs.SetString('inspecteditorY', str(self.y()))
prefs.SetString('inspecteditorW', str(self.width()))
prefs.SetString('inspecteditorH', str(self.height()))
prefs.SetString("inspecteditorX", str(self.x()))
prefs.SetString("inspecteditorY", str(self.y()))
prefs.SetString("inspecteditorW", str(self.width()))
prefs.SetString("inspecteditorH", str(self.height()))
FreeCAD.ActiveDocument.removeObject(self.selectionobj.Name)
def highlightpath(self):
@@ -191,7 +204,7 @@ class GCodeEditorDialog(QtGui.QDialog):
p = Path.Path()
firstrapid = Path.Command("G0", {"X": prevX, "Y": prevY, "Z": prevZ})
selectionpath = [firstrapid] + commands[startrow:endrow + 1]
selectionpath = [firstrapid] + commands[startrow : endrow + 1]
p.Commands = selectionpath
self.selectionobj.Path = p
@@ -207,21 +220,28 @@ def show(obj):
prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
# default Max Highlighter Size = 512 Ko
defaultMHS = 512 * 1024
mhs = prefs.GetUnsigned('inspecteditorMaxHighlighterSize', defaultMHS)
mhs = prefs.GetUnsigned("inspecteditorMaxHighlighterSize", defaultMHS)
if hasattr(obj, "Path"):
if obj.Path:
dia = GCodeEditorDialog(obj)
dia.editor.setText(obj.Path.toGCode())
gcodeSize = len(dia.editor.toPlainText())
if (gcodeSize <= mhs):
if gcodeSize <= mhs:
# because of poor performance, syntax highlighting is
# limited to mhs octets (default 512 KB).
# It seems than the response time curve has an inflexion near 500 KB
# beyond 500 KB, the response time increases exponentially.
dia.highlighter = GCodeHighlighter(dia.editor.document())
else:
FreeCAD.Console.PrintMessage(translate("Path", "GCode size too big ({} o), disabling syntax highlighter.".format(gcodeSize)))
FreeCAD.Console.PrintMessage(
translate(
"Path",
"GCode size too big ({} o), disabling syntax highlighter.".format(
gcodeSize
),
)
)
result = dia.exec_()
# exec_() returns 0 or 1 depending on the button pressed (Ok or
# Cancel)
@@ -234,12 +254,15 @@ def show(obj):
class CommandPathInspect:
def GetResources(self):
return {'Pixmap': 'Path_Inspect',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Inspect", "Inspect G-code"),
'Accel': "P, I",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Inspect", "Inspects the G-code contents of a path")}
return {
"Pixmap": "Path_Inspect",
"MenuText": QT_TRANSLATE_NOOP("Path_Inspect", "Inspect G-code"),
"Accel": "P, I",
"ToolTip": QT_TRANSLATE_NOOP(
"Path_Inspect", "Inspects the G-code contents of a path"
),
}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
@@ -253,19 +276,26 @@ class CommandPathInspect:
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
FreeCAD.Console.PrintError(
translate("Path_Inspect", "Please select exactly one path object") + "\n")
translate("Path_Inspect", "Please select exactly one path object")
+ "\n"
)
return
if not(selection[0].isDerivedFrom("Path::Feature")):
if not (selection[0].isDerivedFrom("Path::Feature")):
FreeCAD.Console.PrintError(
translate("Path_Inspect", "Please select exactly one path object") + "\n")
translate("Path_Inspect", "Please select exactly one path object")
+ "\n"
)
return
# if everything is ok, execute
FreeCADGui.addModule("PathScripts.PathInspect")
FreeCADGui.doCommand(
'PathScripts.PathInspect.show(FreeCAD.ActiveDocument.' + selection[0].Name + ')')
"PathScripts.PathInspect.show(FreeCAD.ActiveDocument."
+ selection[0].Name
+ ")"
)
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_Inspect', CommandPathInspect())
FreeCADGui.addCommand("Path_Inspect", CommandPathInspect())

View File

@@ -27,8 +27,8 @@ import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
from PySide.QtCore import QT_TRANSLATE_NOOP
from PySide import QtCore
__title__ = "Path Probing Operation"
__author__ = "sliptonic (Brad Collette)"
@@ -42,63 +42,106 @@ else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class ObjectProbing(PathOp.ObjectOp):
'''Proxy object for Probing operation.'''
"""Proxy object for Probing operation."""
def opFeatures(self, obj):
'''opFeatures(obj) ... Probing works on the stock object.'''
"""opFeatures(obj) ... Probing works on the stock object."""
return PathOp.FeatureDepths | PathOp.FeatureHeights | PathOp.FeatureTool
def initOperation(self, obj):
obj.addProperty("App::PropertyLength", "Xoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe"))
obj.addProperty("App::PropertyLength", "Yoffset", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe"))
obj.addProperty("App::PropertyInteger", "PointCountX", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in X direction"))
obj.addProperty("App::PropertyInteger", "PointCountY", "Probe", QtCore.QT_TRANSLATE_NOOP("App::Property", "Number of points to probe in Y direction"))
obj.addProperty("App::PropertyFile", "OutputFileName", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The output location for the probe data to be written"))
obj.addProperty(
"App::PropertyLength",
"Xoffset",
"Probe",
QT_TRANSLATE_NOOP("App::Property", "X offset between tool and probe"),
)
obj.addProperty(
"App::PropertyLength",
"Yoffset",
"Probe",
QT_TRANSLATE_NOOP("App::Property", "Y offset between tool and probe"),
)
obj.addProperty(
"App::PropertyInteger",
"PointCountX",
"Probe",
QT_TRANSLATE_NOOP(
"App::Property", "Number of points to probe in X direction"
),
)
obj.addProperty(
"App::PropertyInteger",
"PointCountY",
"Probe",
QT_TRANSLATE_NOOP(
"App::Property", "Number of points to probe in Y direction"
),
)
obj.addProperty(
"App::PropertyFile",
"OutputFileName",
"Path",
QT_TRANSLATE_NOOP(
"App::Property", "The output location for the probe data to be written"
),
)
def nextpoint(self, startpoint=0.0, endpoint=0.0, count=3):
curstep = 0
dist = (endpoint - startpoint) / (count - 1)
while curstep <= count-1:
while curstep <= count - 1:
yield startpoint + (curstep * dist)
curstep += 1
def opExecute(self, obj):
'''opExecute(obj) ... generate probe locations.'''
"""opExecute(obj) ... generate probe locations."""
PathLog.track()
self.commandlist.append(Path.Command("(Begin Probing)"))
stock = PathUtils.findParentJob(obj).Stock
bb = stock.Shape.BoundBox
openstring = '(PROBEOPEN {})'.format(obj.OutputFileName)
openstring = "(PROBEOPEN {})".format(obj.OutputFileName)
self.commandlist.append(Path.Command(openstring))
self.commandlist.append(Path.Command("G0", {"Z": obj.ClearanceHeight.Value}))
for y in self.nextpoint(bb.YMin, bb.YMax, obj.PointCountY):
for x in self.nextpoint(bb.XMin, bb.XMax, obj.PointCountX):
self.commandlist.append(Path.Command("G0", {"X": x + obj.Xoffset.Value, "Y": y + obj.Yoffset.Value, "Z": obj.SafeHeight.Value}))
self.commandlist.append(Path.Command("G38.2", {"Z": obj.FinalDepth.Value, "F": obj.ToolController.VertFeed.Value}))
self.commandlist.append(
Path.Command(
"G0",
{
"X": x + obj.Xoffset.Value,
"Y": y + obj.Yoffset.Value,
"Z": obj.SafeHeight.Value,
},
)
)
self.commandlist.append(
Path.Command(
"G38.2",
{
"Z": obj.FinalDepth.Value,
"F": obj.ToolController.VertFeed.Value,
},
)
)
self.commandlist.append(Path.Command("G0", {"Z": obj.SafeHeight.Value}))
self.commandlist.append(Path.Command("(PROBECLOSE)"))
def opSetDefaultValues(self, obj, job):
'''opSetDefaultValues(obj, job) ... set default value for RetractHeight'''
"""opSetDefaultValues(obj, job) ... set default value for RetractHeight"""
def SetupProperties():
setup = ['Xoffset', 'Yoffset', 'PointCountX', 'PointCountY', 'OutputFileName']
setup = ["Xoffset", "Yoffset", "PointCountX", "PointCountY", "OutputFileName"]
return setup
def Create(name, obj=None, parentJob=None):
'''Create(name) ... Creates and returns a Probing operation.'''
"""Create(name) ... Creates and returns a Probing operation."""
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
proxy = ObjectProbing(obj, name, parentJob)

View File

@@ -22,11 +22,13 @@
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.PathProbe as PathProbe
import PathScripts.PathOpGui as PathOpGui
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
from PySide.QtCore import QT_TRANSLATE_NOOP
from PySide import QtCore, QtGui
__title__ = "Path Probing Operation UI"
@@ -35,38 +37,46 @@ __url__ = "http://www.freecadweb.org"
__doc__ = "Probing operation page controller and command implementation."
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
translate = FreeCAD.Qt.translate
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
'''Page controller class for the Probing operation.'''
"""Page controller class for the Probing operation."""
def getForm(self):
'''getForm() ... returns UI'''
"""getForm() ... returns UI"""
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpProbeEdit.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"""
self.updateToolController(obj, self.form.toolController)
PathGui.updateInputField(obj, 'Xoffset', self.form.Xoffset)
PathGui.updateInputField(obj, 'Yoffset', self.form.Yoffset)
PathGui.updateInputField(obj, "Xoffset", self.form.Xoffset)
PathGui.updateInputField(obj, "Yoffset", self.form.Yoffset)
obj.PointCountX = self.form.PointCountX.value()
obj.PointCountY = self.form.PointCountY.value()
obj.OutputFileName = str(self.form.OutputFileName.text())
def setFields(self, obj):
'''setFields(obj) ... transfers obj's property values to UI'''
"""setFields(obj) ... transfers obj's property values to UI"""
self.setupToolController(obj, self.form.toolController)
self.form.Xoffset.setText(FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString)
self.form.Yoffset.setText(FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString)
self.form.Xoffset.setText(
FreeCAD.Units.Quantity(obj.Xoffset.Value, FreeCAD.Units.Length).UserString
)
self.form.Yoffset.setText(
FreeCAD.Units.Quantity(obj.Yoffset.Value, FreeCAD.Units.Length).UserString
)
self.form.OutputFileName.setText(obj.OutputFileName)
self.form.PointCountX.setValue(obj.PointCountX)
self.form.PointCountY.setValue(obj.PointCountY)
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.toolController.currentIndexChanged)
signals.append(self.form.PointCountX.valueChanged)
@@ -78,16 +88,25 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
return signals
def SetOutputFileName(self):
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Probe", "Select Output File"), None, translate("Path_Probe", "All Files (*.*)"))
filename = QtGui.QFileDialog.getSaveFileName(
self.form,
translate("Path_Probe", "Select Output File"),
None,
translate("Path_Probe", "All Files (*.*)"),
)
if filename and filename[0]:
self.obj.OutputFileName = str(filename[0])
self.setFields(self.obj)
Command = PathOpGui.SetupOperation('Probe', PathProbe.Create, TaskPanelOpPage,
'Path_Probe',
QtCore.QT_TRANSLATE_NOOP("Probe", "Probe"),
QtCore.QT_TRANSLATE_NOOP("Probe", "Create a Probing Grid from a job stock"),
PathProbe.SetupProperties)
Command = PathOpGui.SetupOperation(
"Probe",
PathProbe.Create,
TaskPanelOpPage,
"Path_Probe",
QtCore.QT_TRANSLATE_NOOP("Path_Probe", "Probe"),
QtCore.QT_TRANSLATE_NOOP("Path_Probe", "Create a Probing Grid from a job stock"),
PathProbe.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathProbeGui... done\n")