Merge pull request #5422 from sliptonic/bug/translationThreadMill

[PATH] Bug/translation  (threadmill, tools, toolcontroller, adaptive)
This commit is contained in:
sliptonic
2022-01-24 09:45:45 -06:00
committed by GitHub
20 changed files with 2383 additions and 1064 deletions

View File

@@ -105,6 +105,7 @@
<file>panels/PageDepthsEdit.ui</file>
<file>panels/PageDiametersEdit.ui</file>
<file>panels/PageHeightsEdit.ui</file>
<file>panels/PageOpAdaptiveEdit.ui</file>
<file>panels/PageOpCustomEdit.ui</file>
<file>panels/PageOpDeburrEdit.ui</file>
<file>panels/PageOpDrillingEdit.ui</file>

View File

@@ -0,0 +1,276 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>437</width>
<height>764</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame_2">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Tool Controller</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="ToolController"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Coolant</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="coolantController"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="15" column="1">
<widget class="QDoubleSpinBox" name="LiftDistance">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How much to lift the tool up during the rapid linking moves over cleared regions.&lt;/p&gt;&lt;p&gt;If linking path is not clear tool is raised to clearence height.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Lift Distance</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QDoubleSpinBox" name="HelixDiameterLimit">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If &amp;gt;0 it limits the helix ramp diameter&lt;/p&gt;&lt;p&gt;otherwise the 75 percent of tool diameter is used&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Helix Ramp Angle</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QDoubleSpinBox" name="HelixConeAngle">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Angle of the helix entry cone&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="19" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Stock to Leave</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Cut Region</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="Side">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Cut inside or outside of the selected shapes&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Step Over Percent</string>
</property>
</widget>
</item>
<item row="24" column="0">
<widget class="QCheckBox" name="useOutline">
<property name="text">
<string>Use Outline</string>
</property>
</widget>
</item>
<item row="17" column="1">
<widget class="QDoubleSpinBox" name="KeepToolDownRatio">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Max length of keep-tool-down linking path compared to direct distance between points.&lt;/p&gt;&lt;p&gt;If exceeded link will be done by raising the tool to clearence height.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Helix Max Diameter</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Helix Cone Angle</string>
</property>
</widget>
</item>
<item row="17" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Keep Tool Down Ratio</string>
</property>
</widget>
</item>
<item row="19" column="1">
<widget class="QDoubleSpinBox" name="StockToLeave">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;How much material to leave (i.e. for finishing operation)&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="22" column="0">
<widget class="QCheckBox" name="ForceInsideOut">
<property name="text">
<string>Force Clearing Inside-out</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Operation Type</string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QDoubleSpinBox" name="HelixAngle">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Angle of the helix ramp entry&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="23" column="0">
<widget class="QCheckBox" name="FinishingProfile">
<property name="text">
<string>Finishing Profile</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="OperationType">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Type of adaptive operation&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="9" column="0" colspan="2">
<widget class="QFrame" name="frame_3">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="threadFitLabel">
<property name="text">
<string>Accuracy vs Performance</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="Tolerance">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Influences calculation performance vs stability and accuracy&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>5</number>
</property>
<property name="maximum">
<number>15</number>
</property>
<property name="value">
<number>10</number>
</property>
<property name="sliderPosition">
<number>10</number>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="tickInterval">
<number>1</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="4" column="1">
<widget class="QSpinBox" name="StepOver">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Optimal value for tool stepover&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>100</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="StopButton">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -30,18 +30,8 @@
<item row="0" column="1">
<widget class="QComboBox" name="threadOrientation">
<property name="currentIndex">
<number>1</number>
<number>-1</number>
</property>
<item>
<property name="text">
<string>Left Hand</string>
</property>
</item>
<item>
<property name="text">
<string>Right Hand</string>
</property>
</item>
</widget>
</item>
<item row="1" column="0">
@@ -52,23 +42,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="threadType">
<item>
<property name="text">
<string>Custom</string>
</property>
</item>
<item>
<property name="text">
<string>Metric - internal</string>
</property>
</item>
<item>
<property name="text">
<string>SAE - internal</string>
</property>
</item>
</widget>
<widget class="QComboBox" name="threadType"/>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="threadName"/>
@@ -208,18 +182,7 @@
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="opDirection">
<item>
<property name="text">
<string>Climb</string>
</property>
</item>
<item>
<property name="text">
<string>Conventional</string>
</property>
</item>
</widget>
<widget class="QComboBox" name="opDirection"/>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="leadInOut">
@@ -236,7 +199,7 @@
<customwidgets>
<customwidget>
<class>Gui::QuantitySpinBox</class>
<extends>QDoubleSpinBox</extends>
<extends>QWidget</extends>
<header>Gui/QuantitySpinBox.h</header>
</customwidget>
</customwidgets>

View File

@@ -150,7 +150,7 @@ class PathWorkbench(Workbench):
projcmdlist.append("Path_Sanity")
prepcmdlist.append("Path_Shape")
extracmdlist.extend(["Path_Area", "Path_Area_Workplane"])
specialcmdlist.append("Path_Thread_Milling")
specialcmdlist.append("Path_ThreadMilling")
twodopcmdlist.append("Path_Slot")
if PathPreferences.advancedOCLFeaturesEnabled():

File diff suppressed because it is too large Load Diff

View File

@@ -22,142 +22,41 @@
# ***************************************************************************
import PathScripts.PathOpGui as PathOpGui
from PySide import QtCore, QtGui
from PySide import QtCore
import PathScripts.PathAdaptive as PathAdaptive
import PathScripts.PathFeatureExtensionsGui as PathFeatureExtensionsGui
import FreeCADGui
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
def initPage(self, obj):
self.setTitle("Adaptive path operation")
def getForm(self):
form = QtGui.QWidget()
layout = QtGui.QVBoxLayout()
formLayout = QtGui.QFormLayout()
"""getForm() ... return UI"""
# tool controller
form.ToolController = QtGui.QComboBox()
formLayout.addRow(QtGui.QLabel("Tool Controller"), form.ToolController)
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpAdaptiveEdit.ui")
comboToPropertyMap = [("Side", "Side"), ("OperationType", "OperationType")]
# Coolant controller
form.coolantController = QtGui.QComboBox()
formLayout.addRow(QtGui.QLabel("Coolant Mode"), form.coolantController)
enumTups = PathAdaptive.PathAdaptive.propertyEnumerations(dataType="raw")
# cut region
form.Side = QtGui.QComboBox()
form.Side.addItem("Inside")
form.Side.addItem("Outside")
form.Side.setToolTip("Cut inside or outside of the selected shapes")
formLayout.addRow(QtGui.QLabel("Cut Region"), form.Side)
# operation type
form.OperationType = QtGui.QComboBox()
form.OperationType.addItem("Clearing")
form.OperationType.addItem("Profiling")
form.OperationType.setToolTip("Type of adaptive operation")
formLayout.addRow(QtGui.QLabel("Operation Type"), form.OperationType)
# step over
form.StepOver = QtGui.QSpinBox()
form.StepOver.setMinimum(15)
form.StepOver.setMaximum(75)
form.StepOver.setSingleStep(1)
form.StepOver.setValue(25)
form.StepOver.setToolTip("Optimal value for tool stepover")
formLayout.addRow(QtGui.QLabel("Step Over Percent"), form.StepOver)
# tolerance
form.Tolerance = QtGui.QSlider(QtCore.Qt.Horizontal)
form.Tolerance.setMinimum(5)
form.Tolerance.setMaximum(15)
form.Tolerance.setTickInterval(1)
form.Tolerance.setValue(10)
form.Tolerance.setTickPosition(QtGui.QSlider.TicksBelow)
form.Tolerance.setToolTip("Influences calculation performance vs stability and accuracy.")
formLayout.addRow(QtGui.QLabel("Accuracy vs Performance"), form.Tolerance)
# helix angle
form.HelixAngle = QtGui.QDoubleSpinBox()
form.HelixAngle.setMinimum(1)
form.HelixAngle.setMaximum(89)
form.HelixAngle.setSingleStep(1)
form.HelixAngle.setValue(5)
form.HelixAngle.setToolTip("Angle of the helix ramp entry")
formLayout.addRow(QtGui.QLabel("Helix Ramp Angle"), form.HelixAngle)
# helix cone angle
form.HelixConeAngle = QtGui.QDoubleSpinBox()
form.HelixConeAngle.setMinimum(0)
form.HelixConeAngle.setMaximum(6)
form.HelixConeAngle.setSingleStep(1)
form.HelixConeAngle.setValue(0)
form.HelixConeAngle.setToolTip("Angle of the helix entry cone")
formLayout.addRow(QtGui.QLabel("Helix Cone Angle"), form.HelixConeAngle)
# helix diam. limit
form.HelixDiameterLimit = QtGui.QDoubleSpinBox()
form.HelixDiameterLimit.setMinimum(0.0)
form.HelixDiameterLimit.setMaximum(90)
form.HelixDiameterLimit.setSingleStep(0.1)
form.HelixDiameterLimit.setValue(0)
form.HelixDiameterLimit.setToolTip("If >0 it limits the helix ramp diameter\notherwise the 75 percent of tool diameter is used as helix diameter")
formLayout.addRow(QtGui.QLabel("Helix Max Diameter"), form.HelixDiameterLimit)
# lift distance
form.LiftDistance = QtGui.QDoubleSpinBox()
form.LiftDistance.setMinimum(0.0)
form.LiftDistance.setMaximum(1000)
form.LiftDistance.setSingleStep(0.1)
form.LiftDistance.setValue(1.0)
form.LiftDistance.setToolTip("How much to lift the tool up during the rapid linking moves over cleared regions.\nIf linking path is not clear tool is raised to clearence height.")
formLayout.addRow(QtGui.QLabel("Lift Distance"), form.LiftDistance)
# KeepToolDownRatio
form.KeepToolDownRatio = QtGui.QDoubleSpinBox()
form.KeepToolDownRatio.setMinimum(1.0)
form.KeepToolDownRatio.setMaximum(10)
form.KeepToolDownRatio.setSingleStep(1)
form.KeepToolDownRatio.setValue(3.0)
form.KeepToolDownRatio.setToolTip("Max length of keep-tool-down linking path compared to direct distance between points.\nIf exceeded link will be done by raising the tool to clearence height.")
formLayout.addRow(QtGui.QLabel("Keep Tool Down Ratio"), form.KeepToolDownRatio)
# stock to leave
form.StockToLeave = QtGui.QDoubleSpinBox()
form.StockToLeave.setMinimum(0.0)
form.StockToLeave.setMaximum(1000)
form.StockToLeave.setSingleStep(0.1)
form.StockToLeave.setValue(0)
form.StockToLeave.setToolTip("How much material to leave (i.e. for finishing operation)")
formLayout.addRow(QtGui.QLabel("Stock to Leave"), form.StockToLeave)
# Force inside out
form.ForceInsideOut = QtGui.QCheckBox()
form.ForceInsideOut.setChecked(True)
formLayout.addRow(QtGui.QLabel("Force Clearing Inside-Out"), form.ForceInsideOut)
# Finishing profile
form.FinishingProfile = QtGui.QCheckBox()
form.FinishingProfile.setChecked(True)
formLayout.addRow(QtGui.QLabel("Finishing Profile"), form.FinishingProfile)
# Use outline checkbox
form.useOutline = QtGui.QCheckBox()
form.useOutline.setChecked(False)
formLayout.addRow(QtGui.QLabel("Use outline"), form.useOutline)
layout.addLayout(formLayout)
# stop button
form.StopButton = QtGui.QPushButton("Stop")
form.StopButton.setCheckable(True)
layout.addWidget(form.StopButton)
form.setLayout(layout)
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 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.button.clicked)
signals.append(self.form.Side.currentIndexChanged)
@@ -189,10 +88,10 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.form.HelixConeAngle.setValue(obj.HelixConeAngle)
self.form.HelixDiameterLimit.setValue(obj.HelixDiameterLimit)
self.form.LiftDistance.setValue(obj.LiftDistance)
if hasattr(obj, 'KeepToolDownRatio'):
if hasattr(obj, "KeepToolDownRatio"):
self.form.KeepToolDownRatio.setValue(obj.KeepToolDownRatio)
if hasattr(obj, 'StockToLeave'):
if hasattr(obj, "StockToLeave"):
self.form.StockToLeave.setValue(obj.StockToLeave)
# self.form.ProcessHoles.setChecked(obj.ProcessHoles)
@@ -202,17 +101,17 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
self.setupToolController(obj, self.form.ToolController)
self.setupCoolant(obj, self.form.coolantController)
self.form.StopButton.setChecked(obj.Stopped)
obj.setEditorMode('AdaptiveInputState', 2) # hide this property
obj.setEditorMode('AdaptiveOutputState', 2) # hide this property
obj.setEditorMode('StopProcessing', 2) # hide this property
obj.setEditorMode('Stopped', 2) # hide this property
obj.setEditorMode("AdaptiveInputState", 2) # hide this property
obj.setEditorMode("AdaptiveOutputState", 2) # hide this property
obj.setEditorMode("StopProcessing", 2) # hide this property
obj.setEditorMode("Stopped", 2) # hide this property
def getFields(self, obj):
if obj.Side != str(self.form.Side.currentText()):
obj.Side = str(self.form.Side.currentText())
if obj.Side != str(self.form.Side.currentData()):
obj.Side = str(self.form.Side.currentData())
if obj.OperationType != str(self.form.OperationType.currentText()):
obj.OperationType = str(self.form.OperationType.currentText())
if obj.OperationType != str(self.form.OperationType.currentData()):
obj.OperationType = str(self.form.OperationType.currentData())
obj.StepOver = self.form.StepOver.value()
obj.Tolerance = 1.0 * self.form.Tolerance.value() / 100.0
@@ -221,37 +120,41 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
obj.HelixDiameterLimit = self.form.HelixDiameterLimit.value()
obj.LiftDistance = self.form.LiftDistance.value()
if hasattr(obj, 'KeepToolDownRatio'):
if hasattr(obj, "KeepToolDownRatio"):
obj.KeepToolDownRatio = self.form.KeepToolDownRatio.value()
if hasattr(obj, 'StockToLeave'):
if hasattr(obj, "StockToLeave"):
obj.StockToLeave = self.form.StockToLeave.value()
obj.ForceInsideOut = self.form.ForceInsideOut.isChecked()
obj.FinishingProfile = self.form.FinishingProfile.isChecked()
obj.UseOutline = self.form.useOutline.isChecked()
obj.Stopped = self.form.StopButton.isChecked()
if(obj.Stopped):
if obj.Stopped:
self.form.StopButton.setChecked(False) # reset the button
obj.StopProcessing = True
self.updateToolController(obj, self.form.ToolController)
self.updateCoolant(obj, self.form.coolantController)
obj.setEditorMode('AdaptiveInputState', 2) # hide this property
obj.setEditorMode('AdaptiveOutputState', 2) # hide this property
obj.setEditorMode('StopProcessing', 2) # hide this property
obj.setEditorMode('Stopped', 2) # hide this property
obj.setEditorMode("AdaptiveInputState", 2) # hide this property
obj.setEditorMode("AdaptiveOutputState", 2) # hide this property
obj.setEditorMode("StopProcessing", 2) # hide this property
obj.setEditorMode("Stopped", 2) # hide this property
def taskPanelBaseLocationPage(self, obj, features):
if not hasattr(self, 'extensionsPanel'):
self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage(obj, features) # pylint: disable=attribute-defined-outside-init
if not hasattr(self, "extensionsPanel"):
self.extensionsPanel = PathFeatureExtensionsGui.TaskPanelExtensionPage(
obj, features
) # pylint: disable=attribute-defined-outside-init
return self.extensionsPanel
Command = PathOpGui.SetupOperation('Adaptive',
PathAdaptive.Create,
TaskPanelOpPage,
'Path_Adaptive',
QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive"),
QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive clearing and profiling"),
PathAdaptive.SetupProperties)
Command = PathOpGui.SetupOperation(
"Adaptive",
PathAdaptive.Create,
TaskPanelOpPage,
"Path_Adaptive",
QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive"),
QtCore.QT_TRANSLATE_NOOP("Path_Adaptive", "Adaptive clearing and profiling"),
PathAdaptive.SetupProperties,
)

View File

@@ -24,37 +24,46 @@ import FreeCADGui
import PathScripts.PathPreferences as PathPreferences
import PySide
# Qt translation handling
def translate(context, text, disambig=None):
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
class AdvancedPreferencesPage:
def __init__(self, parent=None):
self.form = FreeCADGui.PySideUic.loadUi(':preferences/Advanced.ui')
self.form = FreeCADGui.PySideUic.loadUi(":preferences/Advanced.ui")
self.form.WarningSuppressAllSpeeds.stateChanged.connect(self.updateSelection)
self.form.EnableAdvancedOCLFeatures.stateChanged.connect(self.updateSelection)
def saveSettings(self):
PathPreferences.setPreferencesAdvanced(
self.form.EnableAdvancedOCLFeatures.isChecked(),
self.form.WarningSuppressAllSpeeds.isChecked(),
self.form.WarningSuppressRapidSpeeds.isChecked(),
self.form.WarningSuppressSelectionMode.isChecked(),
self.form.WarningSuppressOpenCamLib.isChecked())
self.form.EnableAdvancedOCLFeatures.isChecked(),
self.form.WarningSuppressAllSpeeds.isChecked(),
self.form.WarningSuppressRapidSpeeds.isChecked(),
self.form.WarningSuppressSelectionMode.isChecked(),
self.form.WarningSuppressOpenCamLib.isChecked(),
)
def loadSettings(self):
self.form.WarningSuppressAllSpeeds.setChecked(PathPreferences.suppressAllSpeedsWarning())
self.form.WarningSuppressRapidSpeeds.setChecked(PathPreferences.suppressRapidSpeedsWarning(False))
self.form.WarningSuppressSelectionMode.setChecked(PathPreferences.suppressSelectionModeWarning())
self.form.EnableAdvancedOCLFeatures.setChecked(PathPreferences.advancedOCLFeaturesEnabled())
self.form.WarningSuppressOpenCamLib.setChecked(PathPreferences.suppressOpenCamLibWarning())
self.form.WarningSuppressAllSpeeds.setChecked(
PathPreferences.suppressAllSpeedsWarning()
)
self.form.WarningSuppressRapidSpeeds.setChecked(
PathPreferences.suppressRapidSpeedsWarning(False)
)
self.form.WarningSuppressSelectionMode.setChecked(
PathPreferences.suppressSelectionModeWarning()
)
self.form.EnableAdvancedOCLFeatures.setChecked(
PathPreferences.advancedOCLFeaturesEnabled()
)
self.form.WarningSuppressOpenCamLib.setChecked(
PathPreferences.suppressOpenCamLibWarning()
)
self.updateSelection()
def updateSelection(self, state=None):
self.form.WarningSuppressOpenCamLib.setEnabled(self.form.EnableAdvancedOCLFeatures.isChecked())
self.form.WarningSuppressOpenCamLib.setEnabled(
self.form.EnableAdvancedOCLFeatures.isChecked()
)
if self.form.WarningSuppressAllSpeeds.isChecked():
self.form.WarningSuppressRapidSpeeds.setChecked(True)
self.form.WarningSuppressRapidSpeeds.setEnabled(False)
else:
self.form.WarningSuppressRapidSpeeds.setEnabled(True)

View File

@@ -402,7 +402,7 @@ def select(op):
opsel["Vcarve"] = vcarveselect
opsel["Probe"] = probeselect
opsel["Custom"] = customselect
opsel["Thread Milling"] = drillselect
opsel["ThreadMilling"] = drillselect
opsel["TurnFace"] = turnselect
opsel["TurnProfile"] = turnselect
opsel["TurnPartoff"] = turnselect

View File

@@ -28,26 +28,25 @@ import PathScripts.PathCircularHoleBase as PathCircularHoleBase
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathOp as PathOp
import PathScripts.PathUtils as PathUtils
import math
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Path Thread Milling Operation"
__author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "Path thread milling 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 radiiInternal(majorDia, minorDia, toolDia, toolCrest = None):
'''internlThreadRadius(majorDia, minorDia, toolDia, toolCrest) ... returns the maximum radius for thread.'''
def radiiInternal(majorDia, minorDia, toolDia, toolCrest=None):
"""internlThreadRadius(majorDia, minorDia, toolDia, toolCrest) ... returns the maximum radius for thread."""
PathLog.track(majorDia, minorDia, toolDia, toolCrest)
if toolCrest is None:
toolCrest = 0.0
@@ -57,20 +56,25 @@ def radiiInternal(majorDia, minorDia, toolDia, toolCrest = None):
# - The major diameter is 3/8 * H bigger than the pitch diameter
# Since we already have the outer diameter it's simpler to just add 1/8 * H
# to get the outer tip of the thread.
H = ((majorDia - minorDia) / 2.0 ) * 1.6 # (D - d)/2 = 5/8 * H
H = ((majorDia - minorDia) / 2.0) * 1.6 # (D - d)/2 = 5/8 * H
outerTip = majorDia / 2.0 + H / 8.0
# Compensate for the crest of the tool
toolTip = outerTip - toolCrest * 0.8660254037844386 # math.sqrt(3)/2 ... 60deg triangle height
toolTip = (
outerTip - toolCrest * 0.8660254037844386
) # math.sqrt(3)/2 ... 60deg triangle height
return ((minorDia - toolDia) / 2.0, toolTip - toolDia / 2.0)
def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest = None):
def threadPasses(count, radii, majorDia, minorDia, toolDia, toolCrest=None):
PathLog.track(count, radii, majorDia, minorDia, toolDia, toolCrest)
minor, major = radii(majorDia, minorDia, toolDia, toolCrest)
dr = float(major - minor) / count
dr = float(major - minor) / count
return [major - dr * (count - (i + 1)) for i in range(count)]
class _InternalThread(object):
'''Helper class for dealing with different thread types'''
"""Helper class for dealing with different thread types"""
def __init__(self, cmd, zStart, zFinal, pitch):
self.cmd = cmd
if zStart < zFinal:
@@ -82,33 +86,34 @@ class _InternalThread(object):
self.zFinal = zFinal
def overshoots(self, z):
'''overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds'''
"""overshoots(z) ... returns true if adding another half helix goes beyond the thread bounds"""
if self.pitch < 0:
return z + self.hPitch < self.zFinal
return z + self.hPitch > self.zFinal
def adjustX(self, x, dx):
'''adjustX(x, dx) ... move x by dx, the direction depends on the thread settings'''
"""adjustX(x, dx) ... move x by dx, the direction depends on the thread settings"""
if self.isG3() == (self.pitch > 0):
return x + dx
return x - dx
def adjustY(self, y, dy):
'''adjustY(y, dy) ... move y by dy, the direction depends on the thread settings'''
"""adjustY(y, dy) ... move y by dy, the direction depends on the thread settings"""
if self.isG3():
return y - dy
return y - dy
def isG3(self):
'''isG3() ... returns True if this is a G3 command'''
return self.cmd in ['G3', 'G03', 'g3', 'g03']
"""isG3() ... returns True if this is a G3 command"""
return self.cmd in ["G3", "G03", "g3", "g03"]
def isUp(self):
'''isUp() ... returns True if the thread goes from the bottom up'''
"""isUp() ... returns True if the thread goes from the bottom up"""
return self.pitch > 0
def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut):
'''internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread'''
"""internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius) ... returns the g-code to mill the given internal thread"""
thread = _InternalThread(cmd, zStart, zFinal, pitch)
yMin = loc.y - radius
@@ -118,30 +123,29 @@ def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut):
# at this point the tool is at a safe height (depending on the previous thread), so we can move
# into position first, and then drop to the start height. If there is any material in the way this
# op hasn't been setup properly.
path.append(Path.Command('G0', {'X': loc.x, 'Y': loc.y}))
path.append(Path.Command('G0', {'Z': thread.zStart}))
path.append(Path.Command("G0", {"X": loc.x, "Y": loc.y}))
path.append(Path.Command("G0", {"Z": thread.zStart}))
if leadInOut:
path.append(Path.Command(thread.cmd, {'Y': yMax, 'J': (yMax - loc.y) / 2}))
path.append(Path.Command(thread.cmd, {"Y": yMax, "J": (yMax - loc.y) / 2}))
else:
path.append(Path.Command('G1', {'Y': yMax}))
path.append(Path.Command("G1", {"Y": yMax}))
z = thread.zStart
r = -radius
i = 0
while True:
z = thread.zStart + i*thread.hPitch
z = thread.zStart + i * thread.hPitch
if thread.overshoots(z):
break
if 0 == (i & 0x01):
y = yMin
else:
y = yMax
path.append(Path.Command(thread.cmd, {'Y': y, 'Z': z + thread.hPitch, 'J': r}))
path.append(Path.Command(thread.cmd, {"Y": y, "Z": z + thread.hPitch, "J": r}))
r = -r
i = i + 1
z = thread.zStart + i*thread.hPitch
z = thread.zStart + i * thread.hPitch
if PathGeom.isRoughly(z, thread.zFinal):
x = loc.x
else:
@@ -151,48 +155,183 @@ def internalThreadCommands(loc, cmd, zStart, zFinal, pitch, radius, leadInOut):
dx = math.sin(k * math.pi)
y = thread.adjustY(loc.y, r * dy)
x = thread.adjustX(loc.x, r * dx)
path.append(Path.Command(thread.cmd, {'X': x, 'Y': y, 'Z': thread.zFinal, 'J': r}))
path.append(
Path.Command(thread.cmd, {"X": x, "Y": y, "Z": thread.zFinal, "J": r})
)
if leadInOut:
path.append(Path.Command(thread.cmd, {'X': loc.x, 'Y': loc.y, 'I': (loc.x - x) / 2, 'J': (loc.y - y) / 2}))
path.append(
Path.Command(
thread.cmd,
{"X": loc.x, "Y": loc.y, "I": (loc.x - x) / 2, "J": (loc.y - y) / 2},
)
)
else:
path.append(Path.Command('G1', {'X': loc.x, 'Y': loc.y}))
path.append(Path.Command("G1", {"X": loc.x, "Y": loc.y}))
return path
class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
'''Proxy object for thread milling operation.'''
LeftHand = 'LeftHand'
RightHand = 'RightHand'
ThreadTypeCustom = 'Custom'
ThreadTypeMetricInternal = 'Metric - internal'
ThreadTypeImperialInternal = 'Imperial - internal'
DirectionClimb = 'Climb'
DirectionConventional = 'Conventional'
class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
"""Proxy object for thread milling operation."""
LeftHand = "LeftHand"
RightHand = "RightHand"
ThreadTypeCustom = "Custom"
ThreadTypeMetricInternal = "MetricInternal"
ThreadTypeImperialInternal = "ImperialInternal"
DirectionClimb = "Climb"
DirectionConventional = "Conventional"
ThreadOrientations = [LeftHand, RightHand]
ThreadTypes = [ThreadTypeCustom, ThreadTypeMetricInternal, ThreadTypeImperialInternal]
Directions = [DirectionClimb, DirectionConventional]
ThreadTypes = [
ThreadTypeCustom,
ThreadTypeMetricInternal,
ThreadTypeImperialInternal,
]
Directions = [DirectionClimb, DirectionConventional]
@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 = {
"ThreadType": [
(translate("Path_ThreadMilling", "Custom"), "Custom"),
(translate("Path_ThreadMilling", "Metric Internal"), "MetricInternal"),
(
translate("Path_ThreadMilling", "Imperial Internal"),
"ImperialInternal",
),
], # this is the direction that the profile runs
"ThreadOrientation": [
(translate("Path_ThreadMilling", "LeftHand"), "LeftHand"),
(translate("Path_ThreadMilling", "RightHand"), "RightHand"),
], # side of profile that cutter is on in relation to direction of profile
"Direction": [
(translate("Path_ThreadMilling", "Climb"), "Climb"),
(translate("Path_ThreadMilling", "Conventional"), "Conventional"),
], # side of profile that cutter is on in relation to direction of profile
}
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):
return PathOp.FeatureBaseGeometry
def initCircularHoleOperation(self, obj):
obj.addProperty("App::PropertyEnumeration", "ThreadOrientation", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread orientation"))
obj.ThreadOrientation = self.ThreadOrientations
obj.addProperty("App::PropertyEnumeration", "ThreadType", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Currently only internal"))
obj.ThreadType = self.ThreadTypes
obj.addProperty("App::PropertyString", "ThreadName", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Defines which standard thread was chosen"))
obj.addProperty("App::PropertyLength", "MajorDiameter", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's major diameter"))
obj.addProperty("App::PropertyLength", "MinorDiameter", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's minor diameter"))
obj.addProperty("App::PropertyLength", "Pitch", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's pitch - used for metric threads"))
obj.addProperty("App::PropertyInteger", "TPI", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set thread's TPI (turns per inch) - used for imperial threads"))
obj.addProperty("App::PropertyInteger", "ThreadFit", "Thread", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set how many passes are used to cut the thread"))
obj.addProperty("App::PropertyInteger", "Passes", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set how many passes are used to cut the thread"))
obj.addProperty("App::PropertyEnumeration", "Direction", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Direction of thread cutting operation"))
obj.addProperty("App::PropertyBool", "LeadInOut", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Set to True to get lead in and lead out arcs at the start and end of the thread cut"))
obj.addProperty("App::PropertyLink", "ClearanceOp", "Operation", QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Operation to clear the inside of the thread"))
obj.Direction = self.Directions
obj.addProperty(
"App::PropertyEnumeration",
"ThreadOrientation",
"Thread",
QT_TRANSLATE_NOOP("App::Property", "Set thread orientation"),
)
# obj.ThreadOrientation = self.ThreadOrientations
obj.addProperty(
"App::PropertyEnumeration",
"ThreadType",
"Thread",
QT_TRANSLATE_NOOP("App::Property", "Currently only internal"),
)
# obj.ThreadType = self.ThreadTypes
obj.addProperty(
"App::PropertyString",
"ThreadName",
"Thread",
QT_TRANSLATE_NOOP(
"App::Property", "Defines which standard thread was chosen"
),
)
obj.addProperty(
"App::PropertyLength",
"MajorDiameter",
"Thread",
QT_TRANSLATE_NOOP("App::Property", "Set thread's major diameter"),
)
obj.addProperty(
"App::PropertyLength",
"MinorDiameter",
"Thread",
QT_TRANSLATE_NOOP("App::Property", "Set thread's minor diameter"),
)
obj.addProperty(
"App::PropertyLength",
"Pitch",
"Thread",
QT_TRANSLATE_NOOP(
"App::Property", "Set thread's pitch - used for metric threads"
),
)
obj.addProperty(
"App::PropertyInteger",
"TPI",
"Thread",
QT_TRANSLATE_NOOP(
"App::Property",
"Set thread's TPI (turns per inch) - used for imperial threads",
),
)
obj.addProperty(
"App::PropertyInteger",
"ThreadFit",
"Thread",
QT_TRANSLATE_NOOP(
"App::Property", "Set how many passes are used to cut the thread"
),
)
obj.addProperty(
"App::PropertyInteger",
"Passes",
"Operation",
QT_TRANSLATE_NOOP(
"App::Property", "Set how many passes are used to cut the thread"
),
)
obj.addProperty(
"App::PropertyEnumeration",
"Direction",
"Operation",
QT_TRANSLATE_NOOP("App::Property", "Direction of thread cutting operation"),
)
obj.addProperty(
"App::PropertyBool",
"LeadInOut",
"Operation",
QT_TRANSLATE_NOOP(
"App::Property",
"Set to True to get lead in and lead out arcs at the start and end of the thread cut",
),
)
obj.addProperty(
"App::PropertyLink",
"ClearanceOp",
"Operation",
QT_TRANSLATE_NOOP(
"App::Property", "Operation to clear the inside of the thread"
),
)
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
def threadStartDepth(self, obj):
if obj.ThreadOrientation == self.RightHand:
@@ -225,25 +364,25 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
PathLog.track(obj.Label)
if obj.ThreadOrientation == self.RightHand:
if obj.Direction == self.DirectionClimb:
PathLog.track(obj.Label, 'G2')
return 'G2'
PathLog.track(obj.Label, 'G3')
return 'G3'
PathLog.track(obj.Label, "G2")
return "G2"
PathLog.track(obj.Label, "G3")
return "G3"
if obj.Direction == self.DirectionClimb:
PathLog.track(obj.Label, 'G3')
return 'G3'
PathLog.track(obj.Label, 'G2')
return 'G2'
PathLog.track(obj.Label, "G3")
return "G3"
PathLog.track(obj.Label, "G2")
return "G2"
def threadSetup(self, obj):
# the thing to remember is that Climb, for an internal thread must always be G3
if obj.Direction == self.DirectionClimb:
if obj.ThreadOrientation == self.RightHand:
return ('G3', obj.FinalDepth.Value, obj.StartDepth.Value)
return ('G3', obj.StartDepth.Value, obj.FinalDepth.Value)
return ("G3", obj.FinalDepth.Value, obj.StartDepth.Value)
return ("G3", obj.StartDepth.Value, obj.FinalDepth.Value)
if obj.ThreadOrientation == self.RightHand:
return ('G2', obj.StartDepth.Value, obj.FinalDepth.Value)
return ('G2', obj.FinalDepth.Value, obj.StartDepth.Value)
return ("G2", obj.StartDepth.Value, obj.FinalDepth.Value)
return ("G2", obj.FinalDepth.Value, obj.StartDepth.Value)
def threadPassRadii(self, obj):
PathLog.track(obj.Label)
@@ -251,7 +390,7 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
rMinor = (obj.MinorDiameter.Value - self.tool.Diameter) / 2.0
if obj.Passes < 1:
obj.Passes = 1
rPass = (rMajor - rMinor) / obj.Passes
rPass = (rMajor - rMinor) / obj.Passes
passes = [rMajor]
for i in range(1, obj.Passes):
passes.append(rMajor - rPass * i)
@@ -260,20 +399,33 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
def executeThreadMill(self, obj, loc, gcode, zStart, zFinal, pitch):
PathLog.track(obj.Label, loc, gcode, zStart, zFinal, pitch)
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(
Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid})
)
for radius in threadPasses(obj.Passes, radiiInternal, obj.MajorDiameter.Value, obj.MinorDiameter.Value, float(self.tool.Diameter), float(self.tool.Crest)):
commands = internalThreadCommands(loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut)
for radius in threadPasses(
obj.Passes,
radiiInternal,
obj.MajorDiameter.Value,
obj.MinorDiameter.Value,
float(self.tool.Diameter),
float(self.tool.Crest),
):
commands = internalThreadCommands(
loc, gcode, zStart, zFinal, pitch, radius, obj.LeadInOut
)
for cmd in commands:
p = cmd.Parameters
if cmd.Name in ['G0']:
p.update({'F': self.vertRapid})
if cmd.Name in ['G1', 'G2', 'G3']:
p.update({'F': self.horizFeed})
if cmd.Name in ["G0"]:
p.update({"F": self.vertRapid})
if cmd.Name in ["G1", "G2", "G3"]:
p.update({"F": self.horizFeed})
cmd.Parameters = p
self.commandlist.extend(commands)
self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid}))
self.commandlist.append(
Path.Command("G0", {"Z": obj.ClearanceHeight.Value, "F": self.vertRapid})
)
def circularHoleExecute(self, obj, holes):
PathLog.track()
@@ -290,11 +442,17 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
# rapid to clearance height
for loc in holes:
self.executeThreadMill(obj, FreeCAD.Vector(loc['x'], loc['y'], 0), cmd, zStart, zFinal, pitch)
self.executeThreadMill(
obj,
FreeCAD.Vector(loc["x"], loc["y"], 0),
cmd,
zStart,
zFinal,
pitch,
)
else:
PathLog.error("No suitable Tool found for thread milling operation")
def opSetDefaultValues(self, obj, job):
obj.ThreadOrientation = self.RightHand
obj.ThreadType = self.ThreadTypeMetricInternal
@@ -306,8 +464,8 @@ class ObjectThreadMilling(PathCircularHoleBase.ObjectOp):
obj.LeadInOut = True
def isToolSupported(self, obj, tool):
'''Thread milling only supports thread milling cutters.'''
return hasattr(tool, 'Diameter') and hasattr(tool, 'Crest')
"""Thread milling only supports thread milling cutters."""
return hasattr(tool, "Diameter") and hasattr(tool, "Crest")
def SetupProperties():
@@ -327,11 +485,10 @@ def SetupProperties():
def Create(name, obj=None, parentJob=None):
'''Create(name) ... Creates and returns a thread milling operation.'''
"""Create(name) ... Creates and returns a thread milling operation."""
if obj is None:
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
obj.Proxy = ObjectThreadMilling(obj, name, parentJob)
if obj.Proxy:
obj.Proxy.findAllHoles(obj)
return obj

View File

@@ -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.PathThreadMilling as PathThreadMilling
import PathScripts.PathGui as PathGui
@@ -30,6 +30,9 @@ import PathScripts.PathLog as PathLog
import PathScripts.PathOpGui as PathOpGui
import csv
from PySide.QtCore import QT_TRANSLATE_NOOP
from PySide import QtCore
__title__ = "Path Thread Milling Operation UI."
@@ -37,52 +40,88 @@ __author__ = "sliptonic (Brad Collette)"
__url__ = "http://www.freecadweb.org"
__doc__ = "UI and Command for Path Thread Milling 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())
translate = FreeCAD.Qt.translate
def setupCombo(combo, selections):
combo.clear()
for item in selections:
combo.addItem(item)
def fillThreads(combo, dataFile):
combo.blockSignals(True)
combo.clear()
with open("{}Mod/Path/Data/Threads/{}.csv".format(FreeCAD.getHomePath(), dataFile)) as fp:
with open(
"{}Mod/Path/Data/Threads/{}.csv".format(FreeCAD.getHomePath(), dataFile)
) as fp:
reader = csv.DictReader(fp)
for row in reader:
combo.addItem(row['name'], row)
combo.addItem(row["name"], row)
combo.setEnabled(True)
combo.blockSignals(False)
class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
'''Controller for the thread milling operation's page'''
"""Controller for the thread milling operation's page"""
def initPage(self, obj):
self.majorDia = PathGui.QuantitySpinBox(self.form.threadMajor, obj, 'MajorDiameter') # pylint: disable=attribute-defined-outside-init
self.minorDia = PathGui.QuantitySpinBox(self.form.threadMinor, obj, 'MinorDiameter') # pylint: disable=attribute-defined-outside-init
self.pitch = PathGui.QuantitySpinBox(self.form.threadPitch, obj, 'Pitch') # pylint: disable=attribute-defined-outside-init
self.majorDia = PathGui.QuantitySpinBox(
self.form.threadMajor, obj, "MajorDiameter"
) # pylint: disable=attribute-defined-outside-init
self.minorDia = PathGui.QuantitySpinBox(
self.form.threadMinor, obj, "MinorDiameter"
) # pylint: disable=attribute-defined-outside-init
self.pitch = PathGui.QuantitySpinBox(
self.form.threadPitch, obj, "Pitch"
) # pylint: disable=attribute-defined-outside-init
setupCombo(self.form.threadOrientation, obj.Proxy.ThreadOrientations)
setupCombo(self.form.threadType, obj.Proxy.ThreadTypes)
setupCombo(self.form.opDirection, obj.Proxy.Directions)
# setupCombo(self.form.threadOrientation, obj.Proxy.ThreadOrientations)
# setupCombo(self.form.threadType, obj.Proxy.ThreadTypes)
# setupCombo(self.form.opDirection, obj.Proxy.Directions)
def getForm(self):
'''getForm() ... return UI'''
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpThreadMillingEdit.ui")
"""getForm() ... return UI"""
form = FreeCADGui.PySideUic.loadUi(":/panels/PageOpThreadMillingEdit.ui")
comboToPropertyMap = [
("threadOrientation", "ThreadOrientation"),
("threadType", "ThreadType"),
("opDirection", "Direction"),
]
enumTups = PathThreadMilling.ObjectThreadMilling.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 getFields(self, obj):
'''getFields(obj) ... update obj's properties with values from the UI'''
"""getFields(obj) ... update obj's properties with values from the UI"""
PathLog.track()
self.majorDia.updateProperty()
self.minorDia.updateProperty()
self.pitch.updateProperty()
obj.ThreadOrientation = self.form.threadOrientation.currentText()
obj.ThreadType = self.form.threadType.currentText()
obj.ThreadOrientation = self.form.threadOrientation.currentData()
obj.ThreadType = self.form.threadType.currentData()
obj.ThreadName = self.form.threadName.currentText()
obj.Direction = self.form.opDirection.currentText()
obj.Direction = self.form.opDirection.currentData()
obj.Passes = self.form.opPasses.value()
obj.LeadInOut = self.form.leadInOut.checkState() == QtCore.Qt.Checked
obj.TPI = self.form.threadTPI.value()
@@ -90,23 +129,22 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.updateToolController(obj, self.form.toolController)
def setFields(self, obj):
'''setFields(obj) ... update UI with obj properties' values'''
"""setFields(obj) ... update UI with obj properties' values"""
PathLog.track()
self.form.threadOrientation.setCurrentText(obj.ThreadOrientation)
self.selectInComboBox(obj.ThreadOrientation, self.form.threadOrientation)
self.selectInComboBox(obj.ThreadType, self.form.threadType)
self.selectInComboBox(obj.Direction, self.form.opDirection)
self.form.threadType.blockSignals(True)
self.form.threadName.blockSignals(True)
self.form.threadType.setCurrentText(obj.ThreadType)
self._updateFromThreadType()
self.form.threadName.setCurrentText(obj.ThreadName)
self.form.threadType.blockSignals(False)
self.form.threadName.blockSignals(False)
self.form.threadTPI.setValue(obj.TPI)
self.form.opPasses.setValue(obj.Passes)
self.form.opDirection.setCurrentText(obj.Direction)
self.form.leadInOut.setCheckState(QtCore.Qt.Checked if obj.LeadInOut else QtCore.Qt.Unchecked)
self.form.leadInOut.setCheckState(
QtCore.Qt.Checked if obj.LeadInOut else QtCore.Qt.Unchecked
)
self.majorDia.updateSpinBox()
self.minorDia.updateSpinBox()
@@ -114,16 +152,24 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.setupToolController(obj, self.form.toolController)
def _isThreadMetric(self):
return self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeMetricInternal
return (
self.form.threadType.currentData()
== PathThreadMilling.ObjectThreadMilling.ThreadTypeMetricInternal
)
def _isThreadImperial(self):
return self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeImperialInternal
return (
self.form.threadType.currentData()
== PathThreadMilling.ObjectThreadMilling.ThreadTypeImperialInternal
)
def _updateFromThreadType(self):
if self.form.threadType.currentText() == PathThreadMilling.ObjectThreadMilling.ThreadTypeCustom:
if (
self.form.threadType.currentData()
== PathThreadMilling.ObjectThreadMilling.ThreadTypeCustom
):
self.form.threadName.setEnabled(False)
self.form.threadFit.setEnabled(False)
self.form.threadFitLabel.setEnabled(False)
@@ -140,7 +186,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.form.threadTPI.setEnabled(False)
self.form.threadTPILabel.setEnabled(False)
self.form.threadTPI.setValue(0)
fillThreads(self.form.threadName, 'metric-internal')
fillThreads(self.form.threadName, "metric-internal")
if self._isThreadImperial():
self.form.threadFit.setEnabled(True)
@@ -150,24 +196,24 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.form.threadTPI.setEnabled(True)
self.form.threadTPILabel.setEnabled(True)
self.pitch.updateSpinBox(0)
fillThreads(self.form.threadName, 'imperial-internal')
fillThreads(self.form.threadName, "imperial-internal")
def _updateFromThreadName(self):
thread = self.form.threadName.currentData()
fit = float(self.form.threadFit.value()) / 100
mamin = float(thread['dMajorMin'])
mamax = float(thread['dMajorMax'])
mamin = float(thread["dMajorMin"])
mamax = float(thread["dMajorMax"])
major = mamin + (mamax - mamin) * fit
mimin = float(thread['dMinorMin'])
mimax = float(thread['dMinorMax'])
mimin = float(thread["dMinorMin"])
mimax = float(thread["dMinorMax"])
minor = mimin + (mimax - mimin) * fit
if self._isThreadMetric():
pitch = float(thread['pitch'])
pitch = float(thread["pitch"])
self.pitch.updateSpinBox(pitch)
if self._isThreadImperial():
tpi = int(thread['tpi'])
tpi = int(thread["tpi"])
self.form.threadTPI.setValue(tpi)
minor = minor * 25.4
major = major * 25.4
@@ -178,7 +224,7 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.setDirty()
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.threadMajor.editingFinished)
@@ -200,12 +246,17 @@ class TaskPanelOpPage(PathCircularHoleBaseGui.TaskPanelOpPage):
self.form.threadFit.valueChanged.connect(self._updateFromThreadName)
Command = PathOpGui.SetupOperation('Thread Milling',
PathThreadMilling.Create,
TaskPanelOpPage,
'Path_ThreadMilling',
QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Thread Milling"),
QtCore.QT_TRANSLATE_NOOP("PathThreadMilling", "Creates a Path Thread Milling operation from features of a base object"),
PathThreadMilling.SetupProperties)
Command = PathOpGui.SetupOperation(
"ThreadMilling",
PathThreadMilling.Create,
TaskPanelOpPage,
"Path_ThreadMilling",
QT_TRANSLATE_NOOP("Path_ThreadMilling", "Thread Milling"),
QT_TRANSLATE_NOOP(
"Path_ThreadMilling",
"Creates a Path Thread Milling operation from features of a base object",
),
PathThreadMilling.SetupProperties,
)
FreeCAD.Console.PrintLog("Loading PathThreadMillingGui ... done\n")

View File

@@ -25,30 +25,32 @@ import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathPropertyBag as PathPropertyBag
import PathScripts.PathUtil as PathUtil
import PySide
import json
import os
import zipfile
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__ = "Tool bits."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Class to deal with and represent a tool bit."
PropertyGroupShape = 'Shape'
PropertyGroupShape = "Shape"
_DebugFindTool = False
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule()
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 PySide.QtCore.QCoreApplication.translate(context, text, disambig)
def _findToolFile(name, containerFile, typ):
PathLog.track(name)
@@ -74,7 +76,6 @@ def _findToolFile(name, containerFile, typ):
return (True, fullPath)
return (False, None)
for p in paths:
found, path = _findFile(p, name)
if found:
@@ -83,26 +84,26 @@ def _findToolFile(name, containerFile, typ):
def findToolShape(name, path=None):
'''findToolShape(name, path) ... search for name, if relative path look in path'''
"""findToolShape(name, path) ... search for name, if relative path look in path"""
PathLog.track(name, path)
return _findToolFile(name, path, 'Shape')
return _findToolFile(name, path, "Shape")
def findToolBit(name, path=None):
'''findToolBit(name, path) ... search for name, if relative path look in path'''
"""findToolBit(name, path) ... search for name, if relative path look in path"""
PathLog.track(name, path)
if name.endswith('.fctb'):
return _findToolFile(name, path, 'Bit')
return _findToolFile("{}.fctb".format(name), path, 'Bit')
if name.endswith(".fctb"):
return _findToolFile(name, path, "Bit")
return _findToolFile("{}.fctb".format(name), path, "Bit")
# Only used in ToolBit unit test module: TestPathToolBit.py
def findToolLibrary(name, path=None):
'''findToolLibrary(name, path) ... search for name, if relative path look in path'''
"""findToolLibrary(name, path) ... search for name, if relative path look in path"""
PathLog.track(name, path)
if name.endswith('.fctl'):
return _findToolFile(name, path, 'Library')
return _findToolFile("{}.fctl".format(name), path, 'Library')
if name.endswith(".fctl"):
return _findToolFile(name, path, "Library")
return _findToolFile("{}.fctl".format(name), path, "Library")
def _findRelativePath(path, typ):
@@ -110,7 +111,7 @@ def _findRelativePath(path, typ):
relative = path
for p in PathPreferences.searchPathsTool(typ):
if path.startswith(p):
p = path[len(p):]
p = path[len(p) :]
if os.path.sep == p[0]:
p = p[1:]
if len(p) < len(relative):
@@ -130,23 +131,52 @@ def findRelativePathTool(path):
def findRelativePathLibrary(path):
return _findRelativePath(path, 'Library')
return _findRelativePath(path, "Library")
class ToolBit(object):
def __init__(self, obj, shapeFile, path=None):
PathLog.track(obj.Label, shapeFile, path)
self.obj = obj
obj.addProperty('App::PropertyFile', 'BitShape', 'Base', translate('PathToolBit', 'Shape for bit shape'))
obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit'))
obj.addProperty('App::PropertyFile', 'File', 'Base', translate('PathToolBit', 'The file of the tool'))
obj.addProperty('App::PropertyString', 'ShapeName', 'Base', translate('PathToolBit', 'The name of the shape file'))
obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit'))
obj.addProperty(
"App::PropertyFile",
"BitShape",
"Base",
QT_TRANSLATE_NOOP("App::Property", "Shape for bit shape"),
)
obj.addProperty(
"App::PropertyLink",
"BitBody",
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "The parametrized body representing the tool bit"
),
)
obj.addProperty(
"App::PropertyFile",
"File",
"Base",
QT_TRANSLATE_NOOP("App::Property", "The file of the tool"),
)
obj.addProperty(
"App::PropertyString",
"ShapeName",
"Base",
QT_TRANSLATE_NOOP("App::Property", "The name of the shape file"),
)
obj.addProperty(
"App::PropertyStringList",
"BitPropertyNames",
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "List of all properties inherited from the bit"
),
)
if path:
obj.File = path
if shapeFile is None:
obj.BitShape = 'endmill.fcstd'
obj.BitShape = "endmill.fcstd"
self._setupBitShape(obj)
self.unloadBitBody(obj)
else:
@@ -159,7 +189,7 @@ class ToolBit(object):
def __setstate__(self, state):
for obj in FreeCAD.ActiveDocument.Objects:
if hasattr(obj, 'Proxy') and obj.Proxy == self:
if hasattr(obj, "Proxy") and obj.Proxy == self:
self.obj = obj
break
return None
@@ -168,14 +198,21 @@ class ToolBit(object):
# when files are shared it is essential to be able to change/set the shape file,
# otherwise the file is hard to use
# obj.setEditorMode('BitShape', 1)
obj.setEditorMode('BitBody', 2)
obj.setEditorMode('File', 1)
obj.setEditorMode('Shape', 2)
if not hasattr(obj, 'BitPropertyNames'):
obj.addProperty('App::PropertyStringList', 'BitPropertyNames', 'Base', translate('PathToolBit', 'List of all properties inherited from the bit'))
obj.setEditorMode("BitBody", 2)
obj.setEditorMode("File", 1)
obj.setEditorMode("Shape", 2)
if not hasattr(obj, "BitPropertyNames"):
obj.addProperty(
"App::PropertyStringList",
"BitPropertyNames",
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "List of all properties inherited from the bit"
),
)
propNames = []
for prop in obj.PropertiesList:
if obj.getGroupOfProperty(prop) == 'Bit':
if obj.getGroupOfProperty(prop) == "Bit":
val = obj.getPropertyByName(prop)
typ = obj.getTypeIdOfProperty(prop)
dsc = obj.getDocumentationOfProperty(prop)
@@ -185,10 +222,10 @@ class ToolBit(object):
PathUtil.setProperty(obj, prop, val)
propNames.append(prop)
elif obj.getGroupOfProperty(prop) == 'Attribute':
elif obj.getGroupOfProperty(prop) == "Attribute":
propNames.append(prop)
obj.BitPropertyNames = propNames
obj.setEditorMode('BitPropertyNames', 2)
obj.setEditorMode("BitPropertyNames", 2)
for prop in obj.BitPropertyNames:
if obj.getGroupOfProperty(prop) == PropertyGroupShape:
@@ -202,7 +239,7 @@ class ToolBit(object):
def onChanged(self, obj, prop):
PathLog.track(obj.Label, prop)
if prop == 'BitShape' and 'Restore' not in obj.State:
if prop == "BitShape" and "Restore" not in obj.State:
self._setupBitShape(obj)
def onDelete(self, obj, arg2=None):
@@ -212,7 +249,11 @@ class ToolBit(object):
def _updateBitShape(self, obj, properties=None):
if obj.BitBody is not None:
for attributes in [o for o in obj.BitBody.Group if hasattr(o, 'Proxy') and hasattr(o.Proxy, 'getCustomProperties')]:
for attributes in [
o
for o in obj.BitBody.Group
if hasattr(o, "Proxy") and hasattr(o.Proxy, "getCustomProperties")
]:
for prop in attributes.Proxy.getCustomProperties():
# the property might not exist in our local object (new attribute in shape)
# for such attributes we just keep the default
@@ -291,7 +332,7 @@ class ToolBit(object):
dsc = orig.getDocumentationOfProperty(prop)
obj.addProperty(typ, prop, grp, dsc)
if 'App::PropertyEnumeration' == typ:
if "App::PropertyEnumeration" == typ:
setattr(obj, prop, orig.getEnumerationsOfProperty(prop))
obj.setEditorMode(prop, 1)
@@ -315,16 +356,21 @@ class ToolBit(object):
if bitBody.ViewObject:
bitBody.ViewObject.Visibility = False
PathLog.debug("bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody)))
PathLog.debug(
"bitBody.{} ({}): {}".format(bitBody.Label, bitBody.Name, type(bitBody))
)
propNames = []
for attributes in [o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)]:
for attributes in [
o for o in bitBody.Group if PathPropertyBag.IsPropertyBag(o)
]:
PathLog.debug("Process properties from {}".format(attributes.Label))
for prop in attributes.Proxy.getCustomProperties():
self._setupProperty(obj, prop, attributes)
propNames.append(prop)
if not propNames:
PathLog.error(translate('PathToolBit', 'Did not find a PropertyBag in {} - not a ToolBit shape?'.format(docName)))
PathLog.error("Did not find a PropertyBag in {} - not a ToolBit shape?".format(
docName))
# has to happen last because it could trigger op.execute evaluations
obj.BitPropertyNames = propNames
@@ -332,20 +378,32 @@ class ToolBit(object):
self._copyBitShape(obj)
def toolShapeProperties(self, obj):
'''toolShapeProperties(obj) ... return all properties defining it's shape'''
return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) == PropertyGroupShape])
"""toolShapeProperties(obj) ... return all properties defining it's shape"""
return sorted(
[
prop
for prop in obj.BitPropertyNames
if obj.getGroupOfProperty(prop) == PropertyGroupShape
]
)
def toolAdditionalProperties(self, obj):
'''toolShapeProperties(obj) ... return all properties unrelated to it's shape'''
return sorted([prop for prop in obj.BitPropertyNames if obj.getGroupOfProperty(prop) != PropertyGroupShape])
"""toolShapeProperties(obj) ... return all properties unrelated to it's shape"""
return sorted(
[
prop
for prop in obj.BitPropertyNames
if obj.getGroupOfProperty(prop) != PropertyGroupShape
]
)
def toolGroupsAndProperties(self, obj, includeShape=True):
'''toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names.'''
"""toolGroupsAndProperties(obj) ... returns a dictionary of group names with a list of property names."""
category = {}
for prop in obj.BitPropertyNames:
group = obj.getGroupOfProperty(prop)
if includeShape or group != PropertyGroupShape:
properties = category.get(group, [])
properties = category.get(group, [])
properties.append(prop)
category[group] = properties
return category
@@ -354,10 +412,10 @@ class ToolBit(object):
if obj.BitShape:
path = findToolShape(obj.BitShape)
if path:
with open(path, 'rb') as fd:
with open(path, "rb") as fd:
try:
zf = zipfile.ZipFile(fd)
pf = zf.open('thumbnails/Thumbnail.png', 'r')
pf = zf.open("thumbnails/Thumbnail.png", "r")
data = pf.read()
pf.close()
return data
@@ -368,55 +426,58 @@ class ToolBit(object):
def saveToFile(self, obj, path, setFile=True):
PathLog.track(path)
try:
with open(path, 'w') as fp:
json.dump(self.templateAttrs(obj), fp, indent=' ')
with open(path, "w") as fp:
json.dump(self.templateAttrs(obj), fp, indent=" ")
if setFile:
obj.File = path
return True
except (OSError, IOError) as e:
PathLog.error("Could not save tool {} to {} ({})".format(obj.Label, path, e))
PathLog.error(
"Could not save tool {} to {} ({})".format(obj.Label, path, e)
)
raise
def templateAttrs(self, obj):
attrs = {}
attrs['version'] = 2 # Path.Tool is version 1
attrs['name'] = obj.Label
attrs["version"] = 2 # Path.Tool is version 1
attrs["name"] = obj.Label
if PathPreferences.toolsStoreAbsolutePaths():
attrs['shape'] = obj.BitShape
attrs["shape"] = obj.BitShape
else:
# attrs['shape'] = findRelativePathShape(obj.BitShape)
# Extract the name of the shape file
__, filShp = os.path.split(obj.BitShape) # __ is an ignored placeholder acknowledged by LGTM
attrs['shape'] = str(filShp)
__, filShp = os.path.split(
obj.BitShape
) # __ is an ignored placeholder acknowledged by LGTM
attrs["shape"] = str(filShp)
params = {}
for name in obj.BitPropertyNames:
params[name] = PathUtil.getPropertyValueString(obj, name)
attrs['parameter'] = params
attrs["parameter"] = params
params = {}
attrs['attribute'] = params
attrs["attribute"] = params
return attrs
def Declaration(path):
PathLog.track(path)
with open(path, 'r') as fp:
with open(path, "r") as fp:
return json.load(fp)
class ToolBitFactory(object):
def CreateFromAttrs(self, attrs, name='ToolBit', path=None):
def CreateFromAttrs(self, attrs, name="ToolBit", path=None):
PathLog.track(attrs, path)
obj = Factory.Create(name, attrs['shape'], path)
obj.Label = attrs['name']
params = attrs['parameter']
obj = Factory.Create(name, attrs["shape"], path)
obj.Label = attrs["name"]
params = attrs["parameter"]
for prop in params:
PathUtil.setProperty(obj, prop, params[prop])
obj.Proxy._updateBitShape(obj)
obj.Proxy.unloadBitBody(obj)
return obj
def CreateFrom(self, path, name='ToolBit'):
def CreateFrom(self, path, name="ToolBit"):
PathLog.track(name, path)
try:
data = Declaration(path)
@@ -426,9 +487,9 @@ class ToolBitFactory(object):
PathLog.error("%s not a valid tool file (%s)" % (path, e))
raise
def Create(self, name='ToolBit', shapeFile=None, path=None):
def Create(self, name="ToolBit", shapeFile=None, path=None):
PathLog.track(name, shapeFile, path)
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name)
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", name)
obj.Proxy = ToolBit(obj, shapeFile, path)
return obj

View File

@@ -20,25 +20,37 @@
# * *
# ***************************************************************************
from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import PathScripts
import PathScripts.PathLog as PathLog
import os
from PySide import QtCore
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
class CommandToolBitCreate:
'''
"""
Command used to create a new Tool.
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_ToolBit',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Create Tool"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Creates a new ToolBit object")}
return {
"Pixmap": "Path_ToolBit",
"MenuText": QT_TRANSLATE_NOOP("Path_ToolBitCreate", "Create Tool"),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_ToolBitCreate", "Creates a new ToolBit object"
),
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
@@ -47,26 +59,33 @@ class CommandToolBitCreate:
obj = PathScripts.PathToolBit.Factory.Create()
obj.ViewObject.Proxy.setCreate(obj.ViewObject)
class CommandToolBitSave:
'''
"""
Command used to save an existing Tool to a file.
'''
"""
def __init__(self, saveAs):
self.saveAs = saveAs
def GetResources(self):
if self.saveAs:
menuTxt = QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save Tool as...")
menuTxt = QT_TRANSLATE_NOOP("Path_ToolBitSaveAs", "Save Tool as...")
else:
menuTxt = QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save Tool")
return {'Pixmap': 'Path_ToolBit',
'MenuText': menuTxt,
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Save an existing ToolBit object to a file")}
menuTxt = QT_TRANSLATE_NOOP("Path_ToolBitSave", "Save Tool")
return {
"Pixmap": "Path_ToolBit",
"MenuText": menuTxt,
"ToolTip": QT_TRANSLATE_NOOP(
"Path_ToolBitSave", "Save an existing ToolBit object to a file"
),
}
def selectedTool(self):
sel = FreeCADGui.Selection.getSelectionEx()
if 1 == len(sel) and isinstance(sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit):
if 1 == len(sel) and isinstance(
sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit
):
return sel[0].Object
return None
@@ -80,6 +99,7 @@ class CommandToolBitSave:
def Activated(self):
from PySide import QtGui
tool = self.selectedTool()
if tool:
path = None
@@ -87,35 +107,47 @@ class CommandToolBitSave:
if tool.File:
fname = tool.File
else:
fname = os.path.join(PathScripts.PathPreferences.lastPathToolBit(), tool.Label + '.fctb')
foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Tool", fname, "*.fctb")
fname = os.path.join(
PathScripts.PathPreferences.lastPathToolBit(),
tool.Label + ".fctb",
)
foo = QtGui.QFileDialog.getSaveFileName(
QtGui.QApplication.activeWindow(), "Tool", fname, "*.fctb"
)
if foo:
path = foo[0]
else:
path = tool.File
if path:
if not path.endswith('.fctb'):
path += '.fctb'
if not path.endswith(".fctb"):
path += ".fctb"
tool.Proxy.saveToFile(tool, path)
PathScripts.PathPreferences.setLastPathToolBit(os.path.dirname(path))
class CommandToolBitLoad:
'''
"""
Command used to load an existing Tool from a file into the current document.
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_ToolBit',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Load Tool"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBit", "Load an existing ToolBit object from a file")}
return {
"Pixmap": "Path_ToolBit",
"MenuText": QT_TRANSLATE_NOOP("Path_ToolBitLoad", "Load Tool"),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_ToolBitLoad", "Load an existing ToolBit object from a file"
),
}
def selectedTool(self):
sel = FreeCADGui.Selection.getSelectionEx()
if 1 == len(sel) and isinstance(sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit):
if 1 == len(sel) and isinstance(
sel[0].Object.Proxy, PathScripts.PathToolBit.ToolBit
):
return sel[0].Object
return None
@@ -126,12 +158,18 @@ class CommandToolBitLoad:
if PathScripts.PathToolBitGui.LoadTools():
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Path_ToolBitCreate', CommandToolBitCreate())
FreeCADGui.addCommand('Path_ToolBitLoad', CommandToolBitLoad())
FreeCADGui.addCommand('Path_ToolBitSave', CommandToolBitSave(False))
FreeCADGui.addCommand('Path_ToolBitSaveAs', CommandToolBitSave(True))
CommandList = ['Path_ToolBitCreate', 'Path_ToolBitLoad', 'Path_ToolBitSave', 'Path_ToolBitSaveAs']
if FreeCAD.GuiUp:
FreeCADGui.addCommand("Path_ToolBitCreate", CommandToolBitCreate())
FreeCADGui.addCommand("Path_ToolBitLoad", CommandToolBitLoad())
FreeCADGui.addCommand("Path_ToolBitSave", CommandToolBitSave(False))
FreeCADGui.addCommand("Path_ToolBitSaveAs", CommandToolBitSave(True))
CommandList = [
"Path_ToolBitCreate",
"Path_ToolBitLoad",
"Path_ToolBitSave",
"Path_ToolBitSaveAs",
]
FreeCAD.Console.PrintLog("Loading PathToolBitCmd... done\n")

View File

@@ -20,32 +20,30 @@
# * *
# ***************************************************************************
import FreeCAD
from PySide import QtCore, QtGui
import FreeCADGui
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathPropertyEditor as PathPropertyEditor
import PathScripts.PathToolBit as PathToolBit
import PathScripts.PathUtil as PathUtil
import os
import re
from PySide import QtCore, QtGui
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)
class _Delegate(QtGui.QStyledItemDelegate):
'''Handles the creation of an appropriate editing widget for a given property.'''
ObjectRole = QtCore.Qt.UserRole + 1
"""Handles the creation of an appropriate editing widget for a given property."""
ObjectRole = QtCore.Qt.UserRole + 1
PropertyRole = QtCore.Qt.UserRole + 2
EditorRole = QtCore.Qt.UserRole + 3
EditorRole = QtCore.Qt.UserRole + 3
def createEditor(self, parent, option, index):
editor = index.data(self.EditorRole)
@@ -64,14 +62,18 @@ class _Delegate(QtGui.QStyledItemDelegate):
# called to update the model with the data from the widget
editor = index.data(self.EditorRole)
editor.setModelData(widget)
index.model().setData(index, PathUtil.getPropertyValueString(editor.obj, editor.prop), QtCore.Qt.DisplayRole)
index.model().setData(
index,
PathUtil.getPropertyValueString(editor.obj, editor.prop),
QtCore.Qt.DisplayRole,
)
class ToolBitEditor(object):
'''UI and controller for editing a ToolBit.
"""UI and controller for editing a ToolBit.
The controller embeds the UI to the parentWidget which has to have a
layout attached to it.
'''
"""
def __init__(self, tool, parentWidget=None, loadBitBody=True):
PathLog.track()
@@ -84,7 +86,7 @@ class ToolBitEditor(object):
self.tool = tool
self.loadbitbody = loadBitBody
if not tool.BitShape:
self.tool.BitShape = 'endmill.fcstd'
self.tool.BitShape = "endmill.fcstd"
if self.loadbitbody:
self.tool.Proxy.loadBitBody(self.tool)
@@ -107,7 +109,7 @@ class ToolBitEditor(object):
# which aren't being needed anymore.
def labelText(name):
return re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name))
return re.sub("([A-Z][a-z]+)", r" \1", re.sub("([A-Z]+)", r" \1", name))
layout = self.form.bitParams.layout()
ui = FreeCADGui.UiLoader()
@@ -125,12 +127,12 @@ class ToolBitEditor(object):
label.show()
qsb.show()
else:
qsb = ui.createWidget('Gui::QuantitySpinBox')
qsb = ui.createWidget("Gui::QuantitySpinBox")
editor = PathGui.QuantitySpinBox(qsb, tool, name)
label = QtGui.QLabel(labelText(name))
label = QtGui.QLabel(labelText(name))
self.widgets.append((label, qsb, editor))
PathLog.debug("create row: {} [{}] {}".format(nr, name, type(qsb)))
if hasattr(qsb, 'editingFinished'):
if hasattr(qsb, "editingFinished"):
qsb.editingFinished.connect(self.updateTool)
if nr >= layout.rowCount():
@@ -156,10 +158,10 @@ class ToolBitEditor(object):
PathLog.track()
setup = True
if not hasattr(self, 'delegate'):
if not hasattr(self, "delegate"):
self.delegate = _Delegate(self.form.attrTree)
self.model = QtGui.QStandardItemModel(self.form.attrTree)
self.model.setHorizontalHeaderLabels(['Property', 'Value'])
self.model.setHorizontalHeaderLabels(["Property", "Value"])
else:
self.model.removeRows(0, self.model.rowCount())
setup = False
@@ -175,21 +177,22 @@ class ToolBitEditor(object):
label.setEditable(False)
value = QtGui.QStandardItem()
value.setData(PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole)
value.setData(
PathUtil.getPropertyValueString(tool, prop), QtCore.Qt.DisplayRole
)
value.setData(tool, _Delegate.ObjectRole)
value.setData(prop, _Delegate.PropertyRole)
group.appendRow([label, value])
self.model.appendRow(group)
if setup:
self.form.attrTree.setModel(self.model)
self.form.attrTree.setItemDelegateForColumn(1, self.delegate)
self.form.attrTree.expandAll()
self.form.attrTree.resizeColumnToContents(0)
self.form.attrTree.resizeColumnToContents(1)
#self.form.attrTree.collapseAll()
# self.form.attrTree.collapseAll()
def accept(self):
PathLog.track()
@@ -215,7 +218,7 @@ class ToolBitEditor(object):
# editors fires an event and tries to access its old property, which
# might not exist anymore.
for lbl, qsb, editor in self.widgets:
editor.attachTo(self.tool, 'File')
editor.attachTo(self.tool, "File")
self.tool.BitShape = shapePath
self.setupTool(self.tool)
self.form.toolName.setText(self.tool.Label)
@@ -260,7 +263,9 @@ class ToolBitEditor(object):
path = self.tool.BitShape
if not path:
path = PathPreferences.lastPathToolShape()
foo = QtGui.QFileDialog.getOpenFileName(self.form, "Path - Tool Shape", path, "*.fcstd")
foo = QtGui.QFileDialog.getOpenFileName(
self.form, "Path - Tool Shape", path, "*.fcstd"
)
if foo and foo[0]:
PathPreferences.setLastPathToolShape(os.path.dirname(foo[0]))
self.form.shapePath.setText(foo[0])

View File

@@ -20,6 +20,8 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import PathScripts.PathIconViewProvider as PathIconViewProvider
@@ -29,26 +31,24 @@ import PathScripts.PathToolBit as PathToolBit
import PathScripts.PathToolBitEdit as PathToolBitEdit
import os
from PySide import QtCore, QtGui
__title__ = "Tool Bit UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for a ToolBit"
# 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())
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule(PathLog.thisModule())
translate = FreeCAD.Qt.translate
class ViewProvider(object):
'''ViewProvider for a ToolBit.
It's sole job is to provide an icon and invoke the TaskPanel on edit.'''
"""ViewProvider for a ToolBit.
It's sole job is to provide an icon and invoke the TaskPanel on edit."""
def __init__(self, vobj, name):
PathLog.track(name, vobj.Object)
@@ -67,9 +67,9 @@ class ViewProvider(object):
png = self.obj.Proxy.getBitThumbnail(self.obj)
if png:
pixmap = QtGui.QPixmap()
pixmap.loadFromData(png, 'PNG')
pixmap.loadFromData(png, "PNG")
return QtGui.QIcon(pixmap)
return ':/icons/Path_ToolBit.svg'
return ":/icons/Path_ToolBit.svg"
def __getstate__(self):
return None
@@ -84,7 +84,7 @@ class ViewProvider(object):
def getDisplayMode(self, mode):
# pylint: disable=unused-argument
return 'Default'
return "Default"
def _openTaskPanel(self, vobj, deleteOnReject):
PathLog.track()
@@ -117,15 +117,16 @@ class ViewProvider(object):
if os.path.exists(vobj.Object.BitShape):
self.setEdit(vobj)
else:
msg = translate('PathToolBit',
'Toolbit cannot be edited: Shapefile not found')
diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, 'Error', msg)
msg = translate(
"PathToolBit", "Toolbit cannot be edited: Shapefile not found"
)
diag = QtGui.QMessageBox(QtGui.QMessageBox.Warning, "Error", msg)
diag.setWindowModality(QtCore.Qt.ApplicationModal)
diag.exec_()
class TaskPanel:
'''TaskPanel for the SetupSheet - if it is being edited directly.'''
"""TaskPanel for the SetupSheet - if it is being edited directly."""
def __init__(self, vobj, deleteOnReject):
PathLog.track(vobj.Object.Label)
@@ -134,15 +135,14 @@ class TaskPanel:
self.editor = PathToolBitEdit.ToolBitEditor(self.obj)
self.form = self.editor.form
self.deleteOnReject = deleteOnReject
FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit',
'Edit ToolBit'))
FreeCAD.ActiveDocument.openTransaction("Edit ToolBit")
def reject(self):
FreeCAD.ActiveDocument.abortTransaction()
self.editor.reject()
FreeCADGui.Control.closeDialog()
if self.deleteOnReject:
FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Uncreate ToolBit'))
FreeCAD.ActiveDocument.openTransaction("Uncreate ToolBit")
self.editor.reject()
FreeCAD.ActiveDocument.removeObject(self.obj.Name)
FreeCAD.ActiveDocument.commitTransaction()
@@ -169,18 +169,18 @@ class TaskPanel:
class ToolBitGuiFactory(PathToolBit.ToolBitFactory):
def Create(self, name='ToolBit', shapeFile=None, path=None):
'''Create(name = 'ToolBit') ... creates a new tool bit.
It is assumed the tool will be edited immediately so the internal bit body is still attached.'''
def Create(self, name="ToolBit", shapeFile=None, path=None):
"""Create(name = 'ToolBit') ... creates a new tool bit.
It is assumed the tool will be edited immediately so the internal bit body is still attached."""
PathLog.track(name, shapeFile, path)
FreeCAD.ActiveDocument.openTransaction(translate('PathToolBit', 'Create ToolBit'))
FreeCAD.ActiveDocument.openTransaction("Create ToolBit")
tool = PathToolBit.ToolBitFactory.Create(self, name, shapeFile, path)
PathIconViewProvider.Attach(tool.ViewObject, name)
FreeCAD.ActiveDocument.commitTransaction()
return tool
def isValidFileName(filename):
print(filename)
try:
@@ -194,9 +194,9 @@ def GetNewToolFile(parent=None):
if parent is None:
parent = QtGui.QApplication.activeWindow()
foo = QtGui.QFileDialog.getSaveFileName(parent, 'Tool',
PathPreferences.lastPathToolBit(),
'*.fctb')
foo = QtGui.QFileDialog.getSaveFileName(
parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb"
)
if foo and foo[0]:
if not isValidFileName(foo[0]):
msgBox = QtGui.QMessageBox()
@@ -212,9 +212,9 @@ def GetNewToolFile(parent=None):
def GetToolFile(parent=None):
if parent is None:
parent = QtGui.QApplication.activeWindow()
foo = QtGui.QFileDialog.getOpenFileName(parent, 'Tool',
PathPreferences.lastPathToolBit(),
'*.fctb')
foo = QtGui.QFileDialog.getOpenFileName(
parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb"
)
if foo and foo[0]:
PathPreferences.setLastPathToolBit(os.path.dirname(foo[0]))
return foo[0]
@@ -224,9 +224,9 @@ def GetToolFile(parent=None):
def GetToolFiles(parent=None):
if parent is None:
parent = QtGui.QApplication.activeWindow()
foo = QtGui.QFileDialog.getOpenFileNames(parent, 'Tool',
PathPreferences.lastPathToolBit(),
'*.fctb')
foo = QtGui.QFileDialog.getOpenFileNames(
parent, "Tool", PathPreferences.lastPathToolBit(), "*.fctb"
)
if foo and foo[0]:
PathPreferences.setLastPathToolBit(os.path.dirname(foo[0][0]))
return foo[0]
@@ -243,8 +243,9 @@ def GetToolShapeFile(parent=None):
elif not os.path.isdir(location):
location = PathPreferences.filePath()
fname = QtGui.QFileDialog.getOpenFileName(parent, 'Select Tool Shape',
location, '*.fcstd')
fname = QtGui.QFileDialog.getOpenFileName(
parent, "Select Tool Shape", location, "*.fcstd"
)
if fname and fname[0]:
if fname != location:
newloc = os.path.dirname(fname[0])
@@ -255,21 +256,21 @@ def GetToolShapeFile(parent=None):
def LoadTool(parent=None):
'''
"""
LoadTool(parent=None) ... Open a file dialog to load a tool from a file.
'''
"""
foo = GetToolFile(parent)
return PathToolBit.Factory.CreateFrom(foo) if foo else foo
def LoadTools(parent=None):
'''
"""
LoadTool(parent=None) ... Open a file dialog to load a tool from a file.
'''
"""
return [PathToolBit.Factory.CreateFrom(foo) for foo in GetToolFiles(parent)]
# Set the factory so all tools are created with UI
PathToolBit.Factory = ToolBitGuiFactory()
PathIconViewProvider.RegisterViewProvider('ToolBit', ViewProvider)
PathIconViewProvider.RegisterViewProvider("ToolBit", ViewProvider)

View File

@@ -20,65 +20,83 @@
# * *
# ***************************************************************************
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import PySide.QtCore as QtCore
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathLog as PathLog
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 CommandToolBitSelectorOpen:
'''
"""
Command to toggle the ToolBitSelector Dock
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_ToolTable',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Dock"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Toggle the Toolbit Dock"),
'Accel': "P, T",
'CmdType': "ForEdit"}
return {
"Pixmap": "Path_ToolTable",
"MenuText": QT_TRANSLATE_NOOP("Path_ToolBitDock", "ToolBit Dock"),
"ToolTip": QT_TRANSLATE_NOOP("Path_ToolBitDock", "Toggle the Toolbit Dock"),
"Accel": "P, T",
"CmdType": "ForEdit",
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def Activated(self):
import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui
dock = PathToolBitLibraryGui.ToolBitSelector()
dock.open()
class CommandToolBitLibraryOpen:
'''
"""
Command to open ToolBitLibrary editor.
'''
"""
def __init__(self):
pass
def GetResources(self):
return {'Pixmap': 'Path_ToolTable',
'MenuText': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "ToolBit Library editor"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("PathToolBitLibrary", "Open an editor to manage ToolBit libraries"),
'CmdType': "ForEdit"}
return {
"Pixmap": "Path_ToolTable",
"MenuText": QT_TRANSLATE_NOOP(
"Path_ToolBitLibraryOpen", "ToolBit Library editor"
),
"ToolTip": QT_TRANSLATE_NOOP(
"Path_ToolBitLibraryOpen", "Open an editor to manage ToolBit libraries"
),
"CmdType": "ForEdit",
}
def IsActive(self):
return FreeCAD.ActiveDocument is not None
def Activated(self):
import PathScripts.PathToolBitLibraryGui as PathToolBitLibraryGui
library = PathToolBitLibraryGui.ToolBitLibrary()
library.open()
if FreeCAD.GuiUp:
FreeCADGui.addCommand('Path_ToolBitLibraryOpen', CommandToolBitLibraryOpen())
FreeCADGui.addCommand('Path_ToolBitDock', CommandToolBitSelectorOpen())
FreeCADGui.addCommand("Path_ToolBitLibraryOpen", CommandToolBitLibraryOpen())
FreeCADGui.addCommand("Path_ToolBitDock", CommandToolBitSelectorOpen())
BarList = ['Path_ToolBitDock']
MenuList = ['Path_ToolBitLibraryOpen', 'Path_ToolBitDock']
BarList = ["Path_ToolBitDock"]
MenuList = ["Path_ToolBitLibraryOpen", "Path_ToolBitDock"]
FreeCAD.Console.PrintLog("Loading PathToolBitLibraryCmd... done\n")

View File

@@ -24,7 +24,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.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathToolBit as PathToolBit
@@ -42,15 +42,19 @@ import uuid as UUID
from functools import partial
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())
_UuidRole = PySide.QtCore.Qt.UserRole + 1
_PathRole = PySide.QtCore.Qt.UserRole + 2
def translate(context, text, disambig=None):
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
def checkWorkingDir():
# users shouldn't use the example toolbits and libraries.
@@ -60,30 +64,37 @@ def checkWorkingDir():
workingdir = os.path.dirname(PathPreferences.lastPathToolLibrary())
defaultdir = os.path.dirname(PathPreferences.pathDefaultToolsPath())
PathLog.debug('workingdir: {} defaultdir: {}'.format(workingdir, defaultdir))
PathLog.debug("workingdir: {} defaultdir: {}".format(workingdir, defaultdir))
dirOK = lambda : workingdir != defaultdir and (os.access(workingdir, os.W_OK))
dirOK = lambda: workingdir != defaultdir and (os.access(workingdir, os.W_OK))
if dirOK():
return True
qm = PySide.QtGui.QMessageBox
ret = qm.question(None,'', "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No)
ret = qm.question(
None, "", "Toolbit working directory not set up. Do that now?", qm.Yes | qm.No
)
if ret == qm.No:
return False
msg = translate("Path", "Choose a writable location for your toolbits", None)
msg = translate(
"Path_ToolBit", "Choose a writable location for your toolbits", None
)
while not dirOK():
workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(None, msg,
PathPreferences.filePath())
workingdir = PySide.QtGui.QFileDialog.getExistingDirectory(
None, msg, PathPreferences.filePath()
)
if workingdir[-8:] == os.path.sep + 'Library':
if workingdir[-8:] == os.path.sep + "Library":
workingdir = workingdir[:-8] # trim off trailing /Library if user chose it
PathPreferences.setLastPathToolLibrary("{}{}Library".format(workingdir, os.path.sep))
PathPreferences.setLastPathToolLibrary(
"{}{}Library".format(workingdir, os.path.sep)
)
PathPreferences.setLastPathToolBit("{}{}Bit".format(workingdir, os.path.sep))
PathLog.debug('setting workingdir to: {}'.format(workingdir))
PathLog.debug("setting workingdir to: {}".format(workingdir))
# Copy only files of default Path\Tools folder to working directory (targeting the README.md help file)
src_toolfiles = os.listdir(defaultdir)
@@ -94,7 +105,7 @@ def checkWorkingDir():
shutil.copy(full_file_name, workingdir)
# Determine which subdirectories are missing
subdirlist = ['Bit', 'Library', 'Shape']
subdirlist = ["Bit", "Library", "Shape"]
mode = 0o777
for dir in subdirlist.copy():
subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
@@ -103,9 +114,16 @@ def checkWorkingDir():
# Query user for creation permission of any missing subdirectories
if len(subdirlist) >= 1:
needed = ', '.join([str(d) for d in subdirlist])
needed = ", ".join([str(d) for d in subdirlist])
qm = PySide.QtGui.QMessageBox
ret = qm.question(None,'', "Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(workingdir, needed), qm.Yes | qm.No)
ret = qm.question(
None,
"",
"Toolbit Working directory {} needs these sudirectories:\n {} \n Create them?".format(
workingdir, needed
),
qm.Yes | qm.No,
)
if ret == qm.No:
return False
@@ -115,27 +133,37 @@ def checkWorkingDir():
subdir = "{}{}{}".format(workingdir, os.path.sep, dir)
os.mkdir(subdir, mode)
# Query user to copy example files into subdirectories created
if dir != 'Shape':
if dir != "Shape":
qm = PySide.QtGui.QMessageBox
ret = qm.question(None,'', "Copy example files to new {} directory?".format(dir), qm.Yes | qm.No)
ret = qm.question(
None,
"",
"Copy example files to new {} directory?".format(dir),
qm.Yes | qm.No,
)
if ret == qm.Yes:
src="{}{}{}".format(defaultdir, os.path.sep, dir)
src = "{}{}{}".format(defaultdir, os.path.sep, dir)
src_files = os.listdir(src)
for file_name in src_files:
full_file_name = os.path.join(src, file_name)
if os.path.isfile(full_file_name):
shutil.copy(full_file_name, subdir)
# if no library is set, choose the first one in the Library directory
if PathPreferences.lastFileToolLibrary() is None:
libFiles = [f for f in glob.glob(PathPreferences.lastPathToolLibrary() + os.path.sep + '*.fctl')]
libFiles = [
f
for f in glob.glob(
PathPreferences.lastPathToolLibrary() + os.path.sep + "*.fctl"
)
]
PathPreferences.setLastFileToolLibrary(libFiles[0])
return True
class _TableView(PySide.QtGui.QTableView):
'''Subclass of QTableView to support rearrange and copying of ToolBits'''
"""Subclass of QTableView to support rearrange and copying of ToolBits"""
def __init__(self, parent):
PySide.QtGui.QTableView.__init__(self, parent)
@@ -169,9 +197,15 @@ class _TableView(PySide.QtGui.QTableView):
for col in range(model.columnCount()):
srcItem = model.item(srcRow, col)
model.setData(model.index(dstRow, col), srcItem.data(PySide.QtCore.Qt.EditRole), PySide.QtCore.Qt.EditRole)
model.setData(
model.index(dstRow, col),
srcItem.data(PySide.QtCore.Qt.EditRole),
PySide.QtCore.Qt.EditRole,
)
if col == 0:
model.setData(model.index(dstRow, col), srcItem.data(_PathRole), _PathRole)
model.setData(
model.index(dstRow, col), srcItem.data(_PathRole), _PathRole
)
# Even a clone of a tool gets its own uuid so it can be identified when
# rearranging the order or inserting/deleting rows
model.setData(model.index(dstRow, col), UUID.uuid4(), _UuidRole)
@@ -185,7 +219,7 @@ class _TableView(PySide.QtGui.QTableView):
def dropEvent(self, event):
PathLog.track()
mime = event.mimeData()
data = mime.data('application/x-qstandarditemmodeldatalist')
data = mime.data("application/x-qstandarditemmodeldatalist")
stream = PySide.QtCore.QDataStream(data)
srcRows = []
while not stream.atEnd():
@@ -205,8 +239,7 @@ class _TableView(PySide.QtGui.QTableView):
class ModelFactory(object):
''' Helper class to generate qtdata models for toolbit libraries
'''
"""Helper class to generate qtdata models for toolbit libraries"""
def __init__(self, path=None):
PathLog.track()
@@ -221,35 +254,37 @@ class ModelFactory(object):
with open(path) as fp:
library = json.load(fp)
for toolBit in library['tools']:
for toolBit in library["tools"]:
try:
nr = toolBit['nr']
bit = PathToolBit.findToolBit(toolBit['path'], path)
nr = toolBit["nr"]
bit = PathToolBit.findToolBit(toolBit["path"], path)
if bit:
PathLog.track(bit)
tool = PathToolBit.Declaration(bit)
datamodel.appendRow(self._toolAdd(nr, tool, bit))
else:
PathLog.error("Could not find tool #{}: {}".format(nr, toolBit['path']))
PathLog.error(
"Could not find tool #{}: {}".format(nr, toolBit["path"])
)
except Exception as e:
msg = "Error loading tool: {} : {}".format(toolBit['path'], e)
msg = "Error loading tool: {} : {}".format(toolBit["path"], e)
FreeCAD.Console.PrintError(msg)
def _toolAdd(self, nr, tool, path):
strShape = os.path.splitext(os.path.basename(tool['shape']))[0]
strShape = os.path.splitext(os.path.basename(tool["shape"]))[0]
# strDiam = tool['parameter']['Diameter']
tooltip = "{}".format(strShape)
toolNr = PySide.QtGui.QStandardItem()
toolNr.setData(nr, PySide.QtCore.Qt.EditRole)
toolNr.setToolTip(tool['shape'])
toolNr.setToolTip(tool["shape"])
toolNr.setData(path, _PathRole)
toolNr.setData(UUID.uuid4(), _UuidRole)
toolNr.setToolTip(tooltip)
toolName = PySide.QtGui.QStandardItem()
toolName.setData(tool['name'], PySide.QtCore.Qt.EditRole)
toolName.setData(tool["name"], PySide.QtCore.Qt.EditRole)
toolName.setEditable(False)
toolName.setToolTip(tooltip)
@@ -260,9 +295,9 @@ class ModelFactory(object):
return [toolNr, toolName, toolShape]
def newTool(self, datamodel, path):
'''
"""
Adds a toolbit item to a model
'''
"""
PathLog.track()
try:
@@ -278,15 +313,15 @@ class ModelFactory(object):
datamodel.appendRow(self._toolAdd(nr, tool, path))
def findLibraries(self, model):
'''
"""
Finds all the fctl files in a location
Returns a QStandardItemModel
'''
"""
PathLog.track()
path = PathPreferences.lastPathToolLibrary()
if os.path.isdir(path): # opening all tables in a directory
libFiles = [f for f in glob.glob(path + os.path.sep + '*.fctl')]
libFiles = [f for f in glob.glob(path + os.path.sep + "*.fctl")]
libFiles.sort()
for libFile in libFiles:
loc, fnlong = os.path.split(libFile)
@@ -294,17 +329,17 @@ class ModelFactory(object):
libItem = PySide.QtGui.QStandardItem(fn)
libItem.setToolTip(loc)
libItem.setData(libFile, _PathRole)
libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg'))
libItem.setIcon(PySide.QtGui.QPixmap(":/icons/Path_ToolTable.svg"))
model.appendRow(libItem)
PathLog.debug('model rows: {}'.format(model.rowCount()))
PathLog.debug("model rows: {}".format(model.rowCount()))
return model
def libraryOpen(self, model, lib=""):
'''
"""
opens the tools in library
Returns a QStandardItemModel
'''
"""
PathLog.track(lib)
if lib == "":
@@ -316,23 +351,23 @@ class ModelFactory(object):
if os.path.isfile(lib): # An individual library is wanted
self.__libraryLoad(lib, model)
PathLog.debug('model rows: {}'.format(model.rowCount()))
PathLog.debug("model rows: {}".format(model.rowCount()))
return model
class ToolBitSelector(object):
'''Controller for displaying a library and creating ToolControllers'''
"""Controller for displaying a library and creating ToolControllers"""
def __init__(self):
checkWorkingDir()
self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitSelector.ui')
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitSelector.ui")
self.factory = ModelFactory()
self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
self.setupUI()
self.title = self.form.windowTitle()
def columnNames(self):
return ['#', 'Tool']
return ["#", "Tool"]
def currentLibrary(self, shortNameOnly):
libfile = PathPreferences.lastFileToolLibrary()
@@ -357,14 +392,19 @@ class ToolBitSelector(object):
self.loadData()
self.form.tools.setModel(self.toolModel)
self.form.tools.selectionModel().selectionChanged.connect(self.enableButtons)
self.form.tools.doubleClicked.connect(partial(self.selectedOrAllToolControllers))
self.form.tools.doubleClicked.connect(
partial(self.selectedOrAllToolControllers)
)
self.form.libraryEditorOpen.clicked.connect(self.libraryEditorOpen)
self.form.addToolController.clicked.connect(self.selectedOrAllToolControllers)
def enableButtons(self):
selected = (len(self.form.tools.selectedIndexes()) >= 1)
selected = len(self.form.tools.selectedIndexes()) >= 1
if selected:
jobs = len([1 for j in FreeCAD.ActiveDocument.Objects if j.Name[:3] == "Job"]) >= 1
jobs = (
len([1 for j in FreeCAD.ActiveDocument.Objects if j.Name[:3] == "Job"])
>= 1
)
self.form.addToolController.setEnabled(selected and jobs)
def libraryEditorOpen(self):
@@ -373,17 +413,17 @@ class ToolBitSelector(object):
self.loadData()
def selectedOrAllTools(self):
'''
"""
Iterate the selection and add individual tools
If a group is selected, iterate and add children
'''
"""
itemsToProcess = []
for index in self.form.tools.selectedIndexes():
item = index.model().itemFromIndex(index)
if item.hasChildren():
for i in range(item.rowCount()-1):
for i in range(item.rowCount() - 1):
if item.child(i).column() == 0:
itemsToProcess.append(item.child(i))
@@ -398,10 +438,10 @@ class ToolBitSelector(object):
return tools
def selectedOrAllToolControllers(self, index=None):
'''
"""
if no jobs, don't do anything, otherwise all TCs for all
selected toolbits
'''
"""
jobs = PathUtilsGui.PathUtils.GetJobs()
if len(jobs) == 0:
return
@@ -417,12 +457,14 @@ class ToolBitSelector(object):
tools = self.selectedOrAllTools()
for tool in tools:
tc = PathToolControllerGui.Create("TC: {}".format(tool[1].Label), tool[1], tool[0])
tc = PathToolControllerGui.Create(
"TC: {}".format(tool[1].Label), tool[1], tool[0]
)
job.Proxy.addToolController(tc)
FreeCAD.ActiveDocument.recompute()
def open(self, path=None):
''' load library stored in path and bring up ui'''
"""load library stored in path and bring up ui"""
docs = FreeCADGui.getMainWindow().findChildren(PySide.QtGui.QDockWidget)
for doc in docs:
if doc.objectName() == "ToolSelector":
@@ -434,13 +476,16 @@ class ToolBitSelector(object):
return
mw = FreeCADGui.getMainWindow()
mw.addDockWidget(PySide.QtCore.Qt.RightDockWidgetArea, self.form,
PySide.QtCore.Qt.Orientation.Vertical)
mw.addDockWidget(
PySide.QtCore.Qt.RightDockWidgetArea,
self.form,
PySide.QtCore.Qt.Orientation.Vertical,
)
class ToolBitLibrary(object):
'''ToolBitLibrary is the controller for
displaying/selecting/creating/editing a collection of ToolBits.'''
"""ToolBitLibrary is the controller for
displaying/selecting/creating/editing a collection of ToolBits."""
def __init__(self):
PathLog.track()
@@ -449,9 +494,11 @@ class ToolBitLibrary(object):
self.temptool = None
self.toolModel = PySide.QtGui.QStandardItemModel(0, len(self.columnNames()))
self.listModel = PySide.QtGui.QStandardItemModel()
self.form = FreeCADGui.PySideUic.loadUi(':/panels/ToolBitLibraryEdit.ui')
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitLibraryEdit.ui")
self.toolTableView = _TableView(self.form.toolTableGroup)
self.form.toolTableGroup.layout().replaceWidget(self.form.toolTable, self.toolTableView)
self.form.toolTableGroup.layout().replaceWidget(
self.form.toolTable, self.toolTableView
)
self.form.toolTable.hide()
self.setupUI()
self.title = self.form.windowTitle()
@@ -503,7 +550,9 @@ class ToolBitLibrary(object):
def toolDelete(self):
PathLog.track()
selectedRows = set([index.row() for index in self.toolTableView.selectedIndexes()])
selectedRows = set(
[index.row() for index in self.toolTableView.selectedIndexes()]
)
for row in sorted(list(selectedRows), key=lambda r: -r):
self.toolModel.removeRows(row, 1)
@@ -512,7 +561,7 @@ class ToolBitLibrary(object):
self.form.toolDelete.setEnabled(sel)
def tableSelected(self, index):
''' loads the tools for the selected tool table '''
"""loads the tools for the selected tool table"""
PathLog.track()
item = index.model().itemFromIndex(index)
libpath = item.data(_PathRole)
@@ -525,7 +574,9 @@ class ToolBitLibrary(object):
def libraryPath(self):
PathLog.track()
path = PySide.QtGui.QFileDialog.getExistingDirectory(self.form, 'Tool Library Path', PathPreferences.lastPathToolLibrary())
path = PySide.QtGui.QFileDialog.getExistingDirectory(
self.form, "Tool Library Path", PathPreferences.lastPathToolLibrary()
)
if len(path) == 0:
return
@@ -588,10 +639,14 @@ class ToolBitLibrary(object):
pass
else:
tbpath = item.data(_PathRole)
self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, 'temptool')
self.editor = PathToolBitEdit.ToolBitEditor(self.temptool, self.form.toolTableGroup, loadBitBody=False)
self.temptool = PathToolBit.ToolBitFactory().CreateFrom(tbpath, "temptool")
self.editor = PathToolBitEdit.ToolBitEditor(
self.temptool, self.form.toolTableGroup, loadBitBody=False
)
QBtn = PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel
QBtn = (
PySide.QtGui.QDialogButtonBox.Ok | PySide.QtGui.QDialogButtonBox.Cancel
)
buttonBox = PySide.QtGui.QDialogButtonBox(QBtn)
buttonBox.accepted.connect(self.accept)
buttonBox.rejected.connect(self.reject)
@@ -603,24 +658,31 @@ class ToolBitLibrary(object):
def toolEditDone(self, success=True):
FreeCAD.ActiveDocument.removeObject("temptool")
print('all done')
print("all done")
def libraryNew(self):
TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)")
TooltableTypeJSON = translate("Path_ToolBit", "Tooltable JSON (*.fctl)")
filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form,
translate("TooltableEditor", "Save toolbit library", None),
PathPreferences.lastPathToolLibrary(), "{}".format(TooltableTypeJSON))
filename = PySide.QtGui.QFileDialog.getSaveFileName(
self.form,
translate("Path_ToolBit", "Save toolbit library", None),
PathPreferences.lastPathToolLibrary(),
"{}".format(TooltableTypeJSON),
)
if not (filename and filename[0]):
self.loadData()
path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0])
path = (
filename[0]
if filename[0].endswith(".fctl")
else "{}.fctl".format(filename[0])
)
library = {}
tools = []
library['version'] = 1
library['tools'] = tools
with open(path, 'w') as fp:
library["version"] = 1
library["tools"] = tools
with open(path, "w") as fp:
json.dump(library, fp, sort_keys=True, indent=2)
self.loadData()
@@ -628,22 +690,26 @@ class ToolBitLibrary(object):
def librarySave(self):
library = {}
tools = []
library['version'] = 1
library['tools'] = tools
library["version"] = 1
library["tools"] = tools
for row in range(self.toolModel.rowCount()):
toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole)
toolNr = self.toolModel.data(
self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole
)
toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole)
if PathPreferences.toolsStoreAbsolutePaths():
bitPath = toolPath
else:
# bitPath = PathToolBit.findRelativePathTool(toolPath)
# Extract the name of the shape file
__, filShp = os.path.split(toolPath) # __ is an ignored placeholder acknowledged by LGTM
__, filShp = os.path.split(
toolPath
) # __ is an ignored placeholder acknowledged by LGTM
bitPath = str(filShp)
tools.append({'nr': toolNr, 'path': bitPath})
tools.append({"nr": toolNr, "path": bitPath})
if self.path is not None:
with open(self.path, 'w') as fp:
with open(self.path, "w") as fp:
json.dump(library, fp, sort_keys=True, indent=2)
def libraryOk(self):
@@ -658,7 +724,7 @@ class ToolBitLibrary(object):
return lib, loc
def columnNames(self):
return ['Nr', 'Tool', 'Shape']
return ["Nr", "Tool", "Shape"]
def loadData(self, path=None):
PathLog.track(path)
@@ -680,7 +746,7 @@ class ToolBitLibrary(object):
self.path = path
self.form.setWindowTitle("{}".format(PathPreferences.lastPathToolLibrary()))
self.toolModel.setHorizontalHeaderLabels(self.columnNames())
self.listModel.setHorizontalHeaderLabels(['Library'])
self.listModel.setHorizontalHeaderLabels(["Library"])
# Select the current library in the list of tables
curIndex = None
@@ -723,19 +789,29 @@ class ToolBitLibrary(object):
def librarySaveAs(self, path):
TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.fctl)")
TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)")
TooltableTypeJSON = translate("Path_ToolBit", "Tooltable JSON (*.fctl)")
TooltableTypeLinuxCNC = translate("Path_ToolBit", "LinuxCNC tooltable (*.tbl)")
filename = PySide.QtGui.QFileDialog.getSaveFileName(self.form,
translate("TooltableEditor", "Save toolbit library", None),
PathPreferences.lastPathToolLibrary(), "{};;{}".format(TooltableTypeJSON,
TooltableTypeLinuxCNC))
filename = PySide.QtGui.QFileDialog.getSaveFileName(
self.form,
translate("Path_ToolBit", "Save toolbit library", None),
PathPreferences.lastPathToolLibrary(),
"{};;{}".format(TooltableTypeJSON, TooltableTypeLinuxCNC),
)
if filename and filename[0]:
if filename[1] == TooltableTypeLinuxCNC:
path = filename[0] if filename[0].endswith('.tbl') else "{}.tbl".format(filename[0])
path = (
filename[0]
if filename[0].endswith(".tbl")
else "{}.tbl".format(filename[0])
)
self.libararySaveLinuxCNC(path)
else:
path = filename[0] if filename[0].endswith('.fctl') else "{}.fctl".format(filename[0])
path = (
filename[0]
if filename[0].endswith(".fctl")
else "{}.fctl".format(filename[0])
)
self.path = path
self.librarySave()
self.updateToolbar()
@@ -743,11 +819,13 @@ class ToolBitLibrary(object):
def libararySaveLinuxCNC(self, path):
# linuxcnc line template
LIN = "T{} P{} X{} Y{} Z{} A{} B{} C{} U{} V{} W{} D{} I{} J{} Q{}; {}"
with open(path, 'w') as fp:
with open(path, "w") as fp:
fp.write(";\n")
for row in range(self.toolModel.rowCount()):
toolNr = self.toolModel.data(self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole)
toolNr = self.toolModel.data(
self.toolModel.index(row, 0), PySide.QtCore.Qt.EditRole
)
toolPath = self.toolModel.data(self.toolModel.index(row, 0), _PathRole)
bit = PathToolBit.Factory.CreateFrom(toolPath)
@@ -765,16 +843,39 @@ class ToolBitLibrary(object):
voffset = bit.Voffset if hasattr(bit, "Voffset") else "0"
woffset = bit.Woffset if hasattr(bit, "Woffset") else "0"
diameter = bit.Diameter.getUserPreferred()[0].split()[0] if hasattr(bit, "Diameter") else "0"
diameter = (
bit.Diameter.getUserPreferred()[0].split()[0]
if hasattr(bit, "Diameter")
else "0"
)
frontangle = bit.FrontAngle if hasattr(bit, "FrontAngle") else "0"
backangle = bit.BackAngle if hasattr(bit, "BackAngle") else "0"
orientation = bit.Orientation if hasattr(bit, "Orientation") else "0"
orientation = (
bit.Orientation if hasattr(bit, "Orientation") else "0"
)
remark = bit.Label
fp.write(LIN.format(toolNr, pocket, xoffset, yoffset,
zoffset, aoffset, boffset, coffset, uoffset,
voffset, woffset, diameter, frontangle, backangle,
orientation, remark) + "\n")
fp.write(
LIN.format(
toolNr,
pocket,
xoffset,
yoffset,
zoffset,
aoffset,
boffset,
coffset,
uoffset,
voffset,
woffset,
diameter,
frontangle,
backangle,
orientation,
remark,
)
+ "\n"
)
FreeCAD.ActiveDocument.removeObject(bit.Name)

View File

@@ -20,78 +20,149 @@
# * *
# ***************************************************************************
'''Tool Controller defines tool, spindle speed and feed rates for Path Operations'''
"""Tool Controller defines tool, spindle speed and feed rates for Path Operations"""
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathToolBit as PathToolBit
from PySide import QtCore
# PathLog.setLevel(PathLog.Level.DEBUG, 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
class ToolControllerTemplate:
'''Attribute and sub element strings for template export/import.'''
"""Attribute and sub element strings for template export/import."""
Expressions = 'xengine'
ExprExpr = 'expr'
ExprProp = 'prop'
HorizFeed = 'hfeed'
HorizRapid = 'hrapid'
Label = 'label'
Name = 'name'
SpindleDir = 'dir'
SpindleSpeed = 'speed'
ToolNumber = 'nr'
Tool = 'tool'
Version = 'version'
VertFeed = 'vfeed'
VertRapid = 'vrapid'
Expressions = "xengine"
ExprExpr = "expr"
ExprProp = "prop"
HorizFeed = "hfeed"
HorizRapid = "hrapid"
Label = "label"
Name = "name"
SpindleDir = "dir"
SpindleSpeed = "speed"
ToolNumber = "nr"
Tool = "tool"
Version = "version"
VertFeed = "vfeed"
VertRapid = "vrapid"
class ToolController:
def __init__(self, obj, legacyTool=False, createTool=True):
PathLog.track('tool: {}'.format(legacyTool))
PathLog.track("tool: {}".format(legacyTool))
obj.addProperty("App::PropertyIntegerConstraint", "ToolNumber", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The active tool"))
obj.addProperty(
"App::PropertyIntegerConstraint",
"ToolNumber",
"Tool",
QT_TRANSLATE_NOOP("App::Property", "The active tool"),
)
obj.ToolNumber = (0, 0, 10000, 1)
obj.addProperty("App::PropertyFloat", "SpindleSpeed", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The speed of the cutting spindle in RPM"))
obj.addProperty("App::PropertyEnumeration", "SpindleDir", "Tool", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Direction of spindle rotation"))
obj.SpindleDir = ['Forward', 'Reverse']
obj.addProperty("App::PropertySpeed", "VertFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for vertical moves in Z"))
obj.addProperty("App::PropertySpeed", "HorizFeed", "Feed", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Feed rate for horizontal moves"))
obj.addProperty("App::PropertySpeed", "VertRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for vertical moves in Z"))
obj.addProperty("App::PropertySpeed", "HorizRapid", "Rapid", QtCore.QT_TRANSLATE_NOOP("PathToolController", "Rapid rate for horizontal moves"))
obj.setEditorMode('Placement', 2)
obj.addProperty(
"App::PropertyFloat",
"SpindleSpeed",
"Tool",
QT_TRANSLATE_NOOP(
"App::Property", "The speed of the cutting spindle in RPM"
),
)
obj.addProperty(
"App::PropertyEnumeration",
"SpindleDir",
"Tool",
QT_TRANSLATE_NOOP("App::Property", "Direction of spindle rotation"),
)
obj.addProperty(
"App::PropertySpeed",
"VertFeed",
"Feed",
QT_TRANSLATE_NOOP("App::Property", "Feed rate for vertical moves in Z"),
)
obj.addProperty(
"App::PropertySpeed",
"HorizFeed",
"Feed",
QT_TRANSLATE_NOOP("App::Property", "Feed rate for horizontal moves"),
)
obj.addProperty(
"App::PropertySpeed",
"VertRapid",
"Rapid",
QT_TRANSLATE_NOOP("App::Property", "Rapid rate for vertical moves in Z"),
)
obj.addProperty(
"App::PropertySpeed",
"HorizRapid",
"Rapid",
QT_TRANSLATE_NOOP("App::Property", "Rapid rate for horizontal moves"),
)
obj.setEditorMode("Placement", 2)
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
if createTool:
self.ensureUseLegacyTool(obj, legacyTool)
@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 = {
"SpindleDir": [
(translate("Path_ToolController", "Forward"), "Forward"),
(translate("Path_ToolController", "Reverse"), "Reverse"),
], # 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.append((v, [tup[idx] for tup in enums[v]]))
PathLog.debug(data)
return data
def onDocumentRestored(self, obj):
obj.setEditorMode('Placement', 2)
obj.setEditorMode("Placement", 2)
def onDelete(self, obj, arg2=None):
# pylint: disable=unused-argument
if not self.usesLegacyTool(obj):
if hasattr(obj.Tool, 'InList') and len(obj.Tool.InList) == 1:
if hasattr(obj.Tool.Proxy, 'onDelete'):
if hasattr(obj.Tool, "InList") and len(obj.Tool.InList) == 1:
if hasattr(obj.Tool.Proxy, "onDelete"):
obj.Tool.Proxy.onDelete(obj.Tool)
def setFromTemplate(self, obj, template):
'''
"""
setFromTemplate(obj, xmlItem) ... extract properties from xmlItem
and assign to receiver.
'''
"""
PathLog.track(obj.Name, template)
version = 0
if template.get(ToolControllerTemplate.Version):
@@ -108,51 +179,79 @@ class ToolController:
if template.get(ToolControllerTemplate.HorizRapid):
obj.HorizRapid = template.get(ToolControllerTemplate.HorizRapid)
if template.get(ToolControllerTemplate.SpindleSpeed):
obj.SpindleSpeed = float(template.get(ToolControllerTemplate.SpindleSpeed))
obj.SpindleSpeed = float(
template.get(ToolControllerTemplate.SpindleSpeed)
)
if template.get(ToolControllerTemplate.SpindleDir):
obj.SpindleDir = template.get(ToolControllerTemplate.SpindleDir)
if template.get(ToolControllerTemplate.ToolNumber):
obj.ToolNumber = int(template.get(ToolControllerTemplate.ToolNumber))
obj.ToolNumber = int(
template.get(ToolControllerTemplate.ToolNumber)
)
if template.get(ToolControllerTemplate.Tool):
toolVersion = template.get(ToolControllerTemplate.Tool).get(ToolControllerTemplate.Version)
toolVersion = template.get(ToolControllerTemplate.Tool).get(
ToolControllerTemplate.Version
)
if toolVersion == 1:
self.ensureUseLegacyTool(obj, True)
obj.Tool.setFromTemplate(template.get(ToolControllerTemplate.Tool))
obj.Tool.setFromTemplate(
template.get(ToolControllerTemplate.Tool)
)
else:
self.ensureUseLegacyTool(obj, False)
obj.Tool = PathToolBit.Factory.CreateFromAttrs(template.get(ToolControllerTemplate.Tool))
if obj.Tool and obj.Tool.ViewObject and obj.Tool.ViewObject.Visibility:
obj.Tool = PathToolBit.Factory.CreateFromAttrs(
template.get(ToolControllerTemplate.Tool)
)
if (
obj.Tool
and obj.Tool.ViewObject
and obj.Tool.ViewObject.Visibility
):
obj.Tool.ViewObject.Visibility = False
if template.get(ToolControllerTemplate.Expressions):
for exprDef in template.get(ToolControllerTemplate.Expressions):
if exprDef[ToolControllerTemplate.ExprExpr]:
obj.setExpression(exprDef[ToolControllerTemplate.ExprProp], exprDef[ToolControllerTemplate.ExprExpr])
obj.setExpression(
exprDef[ToolControllerTemplate.ExprProp],
exprDef[ToolControllerTemplate.ExprExpr],
)
else:
PathLog.error(translate('PathToolController', "Unsupported PathToolController template version %s") % template.get(ToolControllerTemplate.Version))
PathLog.error(
"Unsupported PathToolController template version {}".format(
template.get(ToolControllerTemplate.Version)
)
)
else:
PathLog.error(translate('PathToolController', 'PathToolController template has no version - corrupted template file?'))
PathLog.error(
"PathToolController template has no version - corrupted template file?"
)
def templateAttrs(self, obj):
'''templateAttrs(obj) ... answer a dictionary with all properties that should be stored for a template.'''
"""templateAttrs(obj) ... answer a dictionary with all properties that should be stored for a template."""
attrs = {}
attrs[ToolControllerTemplate.Version] = 1
attrs[ToolControllerTemplate.Name] = obj.Name
attrs[ToolControllerTemplate.Label] = obj.Label
attrs[ToolControllerTemplate.ToolNumber] = obj.ToolNumber
attrs[ToolControllerTemplate.VertFeed] = ("%s" % (obj.VertFeed))
attrs[ToolControllerTemplate.HorizFeed] = ("%s" % (obj.HorizFeed))
attrs[ToolControllerTemplate.VertRapid] = ("%s" % (obj.VertRapid))
attrs[ToolControllerTemplate.HorizRapid] = ("%s" % (obj.HorizRapid))
attrs[ToolControllerTemplate.Version] = 1
attrs[ToolControllerTemplate.Name] = obj.Name
attrs[ToolControllerTemplate.Label] = obj.Label
attrs[ToolControllerTemplate.ToolNumber] = obj.ToolNumber
attrs[ToolControllerTemplate.VertFeed] = "%s" % (obj.VertFeed)
attrs[ToolControllerTemplate.HorizFeed] = "%s" % (obj.HorizFeed)
attrs[ToolControllerTemplate.VertRapid] = "%s" % (obj.VertRapid)
attrs[ToolControllerTemplate.HorizRapid] = "%s" % (obj.HorizRapid)
attrs[ToolControllerTemplate.SpindleSpeed] = obj.SpindleSpeed
attrs[ToolControllerTemplate.SpindleDir] = obj.SpindleDir
attrs[ToolControllerTemplate.SpindleDir] = obj.SpindleDir
if self.usesLegacyTool(obj):
attrs[ToolControllerTemplate.Tool] = obj.Tool.templateAttrs()
attrs[ToolControllerTemplate.Tool] = obj.Tool.templateAttrs()
else:
attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool)
attrs[ToolControllerTemplate.Tool] = obj.Tool.Proxy.templateAttrs(obj.Tool)
expressions = []
for expr in obj.ExpressionEngine:
PathLog.debug('%s: %s' % (expr[0], expr[1]))
expressions.append({ToolControllerTemplate.ExprProp: expr[0], ToolControllerTemplate.ExprExpr: expr[1]})
PathLog.debug("%s: %s" % (expr[0], expr[1]))
expressions.append(
{
ToolControllerTemplate.ExprProp: expr[0],
ToolControllerTemplate.ExprExpr: expr[1],
}
)
if expressions:
attrs[ToolControllerTemplate.Expressions] = expressions
return attrs
@@ -161,24 +260,23 @@ class ToolController:
PathLog.track()
commands = ""
commands += "(" + obj.Label + ")"+'\n'
commands += 'M6 T'+str(obj.ToolNumber)+'\n'
commands += "(" + obj.Label + ")" + "\n"
commands += "M6 T" + str(obj.ToolNumber) + "\n"
# If a toolbit is used, check to see if spindlepower is allowed.
# This is to prevent accidentally spinning the spindle with an
# unpowered tool like probe or dragknife
allowSpindlePower = True
if (not isinstance(obj.Tool, Path.Tool) and
hasattr(obj.Tool, "SpindlePower")):
allowSpindlePower = obj.Tool.SpindlePower
if not isinstance(obj.Tool, Path.Tool) and hasattr(obj.Tool, "SpindlePower"):
allowSpindlePower = obj.Tool.SpindlePower
if allowSpindlePower:
PathLog.debug('selected tool preventing spindle power')
if obj.SpindleDir == 'Forward':
commands += 'M3 S' + str(obj.SpindleSpeed) + '\n'
PathLog.debug("selected tool preventing spindle power")
if obj.SpindleDir == "Forward":
commands += "M3 S" + str(obj.SpindleSpeed) + "\n"
else:
commands += 'M4 S' + str(obj.SpindleSpeed) + '\n'
commands += "M4 S" + str(obj.SpindleSpeed) + "\n"
if commands == "":
commands += "(No commands processed)"
@@ -189,32 +287,56 @@ class ToolController:
obj.ViewObject.Visibility = True
def getTool(self, obj):
'''returns the tool associated with this tool controller'''
"""returns the tool associated with this tool controller"""
PathLog.track()
return obj.Tool
def usesLegacyTool(self, obj):
'''returns True if the tool being controlled is a legacy tool'''
"""returns True if the tool being controlled is a legacy tool"""
return isinstance(obj.Tool, Path.Tool)
def ensureUseLegacyTool(self, obj, legacy):
if not hasattr(obj, 'Tool') or (legacy != self.usesLegacyTool(obj)):
if legacy and hasattr(obj, 'Tool') and len(obj.Tool.InList) == 1:
if hasattr(obj.Tool.Proxy, 'onDelete'):
if not hasattr(obj, "Tool") or (legacy != self.usesLegacyTool(obj)):
if legacy and hasattr(obj, "Tool") and len(obj.Tool.InList) == 1:
if hasattr(obj.Tool.Proxy, "onDelete"):
obj.Tool.Proxy.onDelete(obj.Tool)
obj.Document.removeObject(obj.Tool.Name)
if hasattr(obj, 'Tool'):
obj.removeProperty('Tool')
if hasattr(obj, "Tool"):
obj.removeProperty("Tool")
if legacy:
obj.addProperty("Path::PropertyTool", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller"))
obj.addProperty(
"Path::PropertyTool",
"Tool",
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "The tool used by this controller"
),
)
else:
obj.addProperty("App::PropertyLink", "Tool", "Base", QtCore.QT_TRANSLATE_NOOP("PathToolController", "The tool used by this controller"))
obj.addProperty(
"App::PropertyLink",
"Tool",
"Base",
QT_TRANSLATE_NOOP(
"App::Property", "The tool used by this controller"
),
)
def Create(name='TC: Default Tool', tool=None, toolNumber=1, assignViewProvider=True, assignTool=True):
legacyTool = PathPreferences.toolsUseLegacyTools() if tool is None else isinstance(tool, Path.Tool)
def Create(
name="TC: Default Tool",
tool=None,
toolNumber=1,
assignViewProvider=True,
assignTool=True,
):
legacyTool = (
PathPreferences.toolsUseLegacyTools()
if tool is None
else isinstance(tool, Path.Tool)
)
PathLog.track(tool, toolNumber, legacyTool)

View File

@@ -20,45 +20,50 @@
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
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
import PathScripts.PathGui as PathGui
import PathScripts.PathLog as PathLog
import PathScripts.PathToolBitGui as PathToolBitGui
import PathScripts.PathToolEdit as PathToolEdit
import PathScripts.PathUtil as PathUtil
from PySide import QtCore, QtGui
import PathScripts.PathToolController as PathToolController
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Part = LazyLoader('Part', globals(), 'Part')
Part = LazyLoader("Part", globals(), "Part")
# 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 ViewProvider:
def __init__(self, vobj):
vobj.Proxy = self
self.vobj = vobj
def attach(self, vobj):
mode = 2
vobj.setEditorMode('LineWidth', mode)
vobj.setEditorMode('MarkerColor', mode)
vobj.setEditorMode('NormalColor', mode)
vobj.setEditorMode('DisplayMode', mode)
vobj.setEditorMode('BoundingBox', mode)
vobj.setEditorMode('Selectable', mode)
vobj.setEditorMode('ShapeColor', mode)
vobj.setEditorMode('Transparency', mode)
vobj.setEditorMode('Visibility', mode)
vobj.setEditorMode("LineWidth", mode)
vobj.setEditorMode("MarkerColor", mode)
vobj.setEditorMode("NormalColor", mode)
vobj.setEditorMode("DisplayMode", mode)
vobj.setEditorMode("BoundingBox", mode)
vobj.setEditorMode("Selectable", mode)
vobj.setEditorMode("ShapeColor", mode)
vobj.setEditorMode("Transparency", mode)
vobj.setEditorMode("Visibility", mode)
self.vobj = vobj
def __getstate__(self):
@@ -73,12 +78,12 @@ class ViewProvider:
def onChanged(self, vobj, prop):
# pylint: disable=unused-argument
mode = 2
vobj.setEditorMode('LineWidth', mode)
vobj.setEditorMode('MarkerColor', mode)
vobj.setEditorMode('NormalColor', mode)
vobj.setEditorMode('DisplayMode', mode)
vobj.setEditorMode('BoundingBox', mode)
vobj.setEditorMode('Selectable', mode)
vobj.setEditorMode("LineWidth", mode)
vobj.setEditorMode("MarkerColor", mode)
vobj.setEditorMode("NormalColor", mode)
vobj.setEditorMode("DisplayMode", mode)
vobj.setEditorMode("BoundingBox", mode)
vobj.setEditorMode("Selectable", mode)
def onDelete(self, vobj, args=None):
# pylint: disable=unused-argument
@@ -115,7 +120,7 @@ class ViewProvider:
PathLog.track()
for action in menu.actions():
menu.removeAction(action)
action = QtGui.QAction(translate('Path', 'Edit'), menu)
action = QtGui.QAction(translate("Path", "Edit"), menu)
action.triggered.connect(self.setEdit)
menu.addAction(action)
@@ -126,7 +131,7 @@ class ViewProvider:
return []
def Create(name='Default Tool', tool=None, toolNumber=1):
def Create(name="Default Tool", tool=None, toolNumber=1):
PathLog.track(tool, toolNumber)
obj = PathScripts.PathToolController.Create(name, tool, toolNumber)
@@ -142,16 +147,20 @@ class CommandPathToolController(object):
# pylint: disable=no-init
def GetResources(self):
return {'Pixmap': 'Path_LengthOffset',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller to the Job"),
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller")}
return {
"Pixmap": "Path_LengthOffset",
"MenuText": QT_TRANSLATE_NOOP(
"Path_ToolController", "Add Tool Controller to the Job"
),
"ToolTip": QT_TRANSLATE_NOOP("Path_ToolController", "Add Tool Controller"),
}
def selectedJob(self):
if FreeCAD.ActiveDocument:
sel = FreeCADGui.Selection.getSelectionEx()
if sel and sel[0].Object.Name[:3] == 'Job':
if sel and sel[0].Object.Name[:3] == "Job":
return sel[0].Object
jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == 'Job']
jobs = [o for o in FreeCAD.ActiveDocument.Objects if o.Name[:3] == "Job"]
if 1 == len(jobs):
return jobs[0]
return None
@@ -178,30 +187,69 @@ class CommandPathToolController(object):
class ToolControllerEditor(object):
def __init__(self, obj, asDialog):
self.form = FreeCADGui.PySideUic.loadUi(":/panels/DlgToolControllerEdit.ui")
if not asDialog:
self.form.buttonBox.hide()
self.obj = obj
self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj,
'VertFeed')
self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj,
'HorizFeed')
self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj,
'VertRapid')
self.horizRapid = PathGui.QuantitySpinBox(self.form.horizRapid, obj,
'HorizRapid')
comboToPropertyMap = [("spindleDirection", "SpindleDir")]
enumTups = PathToolController.ToolController.propertyEnumerations(
dataType="raw"
)
self.populateCombobox(self.form, enumTups, comboToPropertyMap)
self.vertFeed = PathGui.QuantitySpinBox(self.form.vertFeed, obj, "VertFeed")
self.horizFeed = PathGui.QuantitySpinBox(self.form.horizFeed, obj, "HorizFeed")
self.vertRapid = PathGui.QuantitySpinBox(self.form.vertRapid, obj, "VertRapid")
self.horizRapid = PathGui.QuantitySpinBox(
self.form.horizRapid, obj, "HorizRapid"
)
if obj.Proxy.usesLegacyTool(obj):
self.editor = PathToolEdit.ToolEditor(obj.Tool,
self.form.toolEditor)
self.editor = PathToolEdit.ToolEditor(obj.Tool, self.form.toolEditor)
else:
self.editor = None
self.form.toolBox.widget(1).hide()
self.form.toolBox.removeItem(1)
def selectInComboBox(self, name, combo):
"""selectInComboBox(name, combo) ...
helper function to select a specific value in a combo box."""
blocker = QtCore.QSignalBlocker(combo)
index = combo.currentIndex() # Save initial index
# Search using currentData and return if found
newindex = combo.findData(name)
if newindex >= 0:
combo.setCurrentIndex(newindex)
return
# if not found, search using current text
newindex = combo.findText(name, QtCore.Qt.MatchFixedString)
if newindex >= 0:
combo.setCurrentIndex(newindex)
return
# not found, return unchanged
combo.setCurrentIndex(index)
return
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 updateUi(self):
tc = self.obj
self.form.tcName.setText(tc.Label)
@@ -211,10 +259,14 @@ class ToolControllerEditor(object):
self.vertFeed.updateSpinBox()
self.vertRapid.updateSpinBox()
self.form.spindleSpeed.setValue(tc.SpindleSpeed)
index = self.form.spindleDirection.findText(tc.SpindleDir,
QtCore.Qt.MatchFixedString)
if index >= 0:
self.form.spindleDirection.setCurrentIndex(index)
self.selectInComboBox(tc.SpindleDir, self.form.spindleDirection)
# index = self.form.spindleDirection.findText(
# tc.SpindleDir, QtCore.Qt.MatchFixedString
# )
# if index >= 0:
# self.form.spindleDirection.setCurrentIndex(index)
if self.editor:
self.editor.updateUI()
@@ -229,15 +281,14 @@ class ToolControllerEditor(object):
self.horizRapid.updateProperty()
self.vertRapid.updateProperty()
tc.SpindleSpeed = self.form.spindleSpeed.value()
tc.SpindleDir = self.form.spindleDirection.currentText()
tc.SpindleDir = self.form.spindleDirection.currentData()
if self.editor:
self.editor.updateTool()
tc.Tool = self.editor.tool
except Exception as e:
PathLog.error(translate("PathToolController",
"Error updating TC: %s") % e)
PathLog.error("Error updating TC: {}".format(e))
def refresh(self):
self.form.blockSignals(True)
@@ -259,7 +310,6 @@ class ToolControllerEditor(object):
class TaskPanel:
def __init__(self, obj):
self.editor = ToolControllerEditor(obj, False)
self.form = self.editor.form
@@ -309,8 +359,7 @@ class TaskPanel:
def setupUi(self):
if self.editor.editor:
t = Part.makeCylinder(1, 1)
self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature",
"tool")
self.toolrep = FreeCAD.ActiveDocument.addObject("Part::Feature", "tool")
self.toolrep.Shape = t
self.setFields()
@@ -337,6 +386,6 @@ class DlgToolControllerEdit:
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_ToolController', CommandPathToolController())
FreeCADGui.addCommand("Path_ToolController", CommandPathToolController())
FreeCAD.Console.PrintLog("Loading PathToolControllerGui... done\n")

View File

@@ -21,7 +21,8 @@
# ***************************************************************************
from __future__ import print_function
from PySide import QtCore, QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import FreeCADGui
import Path
@@ -30,19 +31,20 @@ import PathScripts.PathLog as PathLog
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathToolBitLibraryCmd as PathToolBitLibraryCmd
import PathScripts.PathToolEdit as PathToolEdit
import PathScripts.PathUtils as PathUtils
import PathScripts.PathToolLibraryManager as ToolLibraryManager
import PathScripts.PathUtils as PathUtils
from PySide import QtCore, QtGui
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 EditorPanel():
class EditorPanel:
def __init__(self, job, cb):
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolLibraryEditor.ui")
self.TLM = ToolLibraryManager.ToolLibraryManager()
@@ -88,7 +90,7 @@ class EditorPanel():
return toolslist[tooltype]
def getMaterial(self, material):
'''gets a combobox index number for a given material or vice versa'''
"""gets a combobox index number for a given material or vice versa"""
matslist = Path.Tool.getToolMaterials(Path.Tool())
if isinstance(material, str):
if material in matslist:
@@ -99,7 +101,7 @@ class EditorPanel():
return matslist[material]
def addTool(self):
'''adds new tool to the current tool table'''
"""adds new tool to the current tool table"""
tool = Path.Tool()
editor = self.toolEditor(tool)
@@ -111,19 +113,19 @@ class EditorPanel():
self.loadTable(listname)
def delete(self):
'''deletes the selected tool'''
"""deletes the selected tool"""
listname = self.TLM.getCurrentTableName()
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
item = model.item(i, 0)
if item.checkState():
t = model.index(i, 1)
self.TLM.delete(int(t.data()) ,listname)
self.TLM.delete(int(t.data()), listname)
self.loadTable(listname)
self.toolSelectionChanged()
def editTool(self, currItem):
'''load the tool edit dialog'''
"""load the tool edit dialog"""
if not currItem:
currItem = self.form.ToolsList.selectedIndexes()[1]
@@ -141,7 +143,7 @@ class EditorPanel():
self.loadTable(listname)
def moveUp(self):
'''moves a tool to a lower number, if possible'''
"""moves a tool to a lower number, if possible"""
item = self.form.ToolsList.selectedIndexes()[1].data()
if item:
number = int(item)
@@ -152,7 +154,7 @@ class EditorPanel():
self.updateSelection(newNum)
def moveDown(self):
'''moves a tool to a higher number, if possible'''
"""moves a tool to a higher number, if possible"""
item = self.form.ToolsList.selectedIndexes()[1].data()
if item:
number = int(item)
@@ -163,7 +165,7 @@ class EditorPanel():
self.updateSelection(newNum)
def duplicate(self):
'''duplicated the selected tool in the current tool table'''
"""duplicated the selected tool in the current tool table"""
item = self.form.ToolsList.selectedIndexes()[1].data()
if item:
number = int(item)
@@ -174,7 +176,7 @@ class EditorPanel():
self.updateSelection(newNum)
def updateSelection(self, number):
'''update the tool list selection to track moves'''
"""update the tool list selection to track moves"""
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
if int(model.index(i, 1).data()) == number:
@@ -182,24 +184,41 @@ class EditorPanel():
self.form.ToolsList.model().item(i, 0).setCheckState(QtCore.Qt.Checked)
return
def importFile(self):
'''imports a tooltable from a file'''
filename = QtGui.QFileDialog.getOpenFileName(self.form, translate( "TooltableEditor", "Open tooltable", None), None, "{};;{};;{}".format(self.TLM.TooltableTypeJSON, self.TLM.TooltableTypeXML, self.TLM.TooltableTypeHeekscad))
"""imports a tooltable from a file"""
filename = QtGui.QFileDialog.getOpenFileName(
self.form,
translate("Path_ToolTable", "Open tooltable", None),
None,
"{};;{};;{}".format(
self.TLM.TooltableTypeJSON,
self.TLM.TooltableTypeXML,
self.TLM.TooltableTypeHeekscad,
),
)
if filename[0]:
listname = self.TLM.getNextToolTableName()
if self.TLM.read(filename, listname):
self.loadToolTables()
def exportFile(self):
'''export a tooltable to a file'''
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("TooltableEditor", "Save tooltable", None), None, "{};;{};;{}".format(self.TLM.TooltableTypeJSON, self.TLM.TooltableTypeXML, self.TLM.TooltableTypeLinuxCNC))
"""export a tooltable to a file"""
filename = QtGui.QFileDialog.getSaveFileName(
self.form,
translate("Path_ToolTable", "Save tooltable", None),
None,
"{};;{};;{}".format(
self.TLM.TooltableTypeJSON,
self.TLM.TooltableTypeXML,
self.TLM.TooltableTypeLinuxCNC,
),
)
if filename[0]:
listname = self.TLM.getCurrentTableName()
self.TLM.write(filename, listname)
def toolSelectionChanged(self, index=None):
''' updates the ui when tools are selected'''
"""updates the ui when tools are selected"""
if index:
self.form.ToolsList.selectRow(index.row())
@@ -222,7 +241,7 @@ class EditorPanel():
# only allow moving or deleting a single tool at a time.
if checkCount == 1:
#make sure the row is highlighted when the check box gets ticked
# make sure the row is highlighted when the check box gets ticked
self.form.ToolsList.selectRow(checkList[0])
self.form.ButtonDelete.setEnabled(True)
self.form.ButtonUp.setEnabled(True)
@@ -234,7 +253,7 @@ class EditorPanel():
self.form.btnCopyTools.setEnabled(False)
def copyTools(self):
''' copy selected tool '''
"""copy selected tool"""
tools = []
model = self.form.ToolsList.model()
for i in range(model.rowCount()):
@@ -268,43 +287,54 @@ class EditorPanel():
for toolnum in tools:
tool = self.TLM.getTool(currList, int(toolnum))
PathLog.debug('tool: {}, toolnum: {}'.format(tool, toolnum))
PathLog.debug("tool: {}, toolnum: {}".format(tool, toolnum))
if self.job:
label = "T{}: {}".format(toolnum, tool.Name)
tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum))
tc = PathScripts.PathToolController.Create(
label, tool=tool, toolNumber=int(toolnum)
)
self.job.Proxy.addToolController(tc)
else:
for job in FreeCAD.ActiveDocument.findObjects("Path::Feature"):
if isinstance(job.Proxy, PathScripts.PathJob.ObjectJob) and job.Label == targetlist:
if (
isinstance(job.Proxy, PathScripts.PathJob.ObjectJob)
and job.Label == targetlist
):
label = "T{}: {}".format(toolnum, tool.Name)
tc = PathScripts.PathToolController.Create(label, tool=tool, toolNumber=int(toolnum))
tc = PathScripts.PathToolController.Create(
label, tool=tool, toolNumber=int(toolnum)
)
job.Proxy.addToolController(tc)
if self.cb:
self.cb()
FreeCAD.ActiveDocument.recompute()
def tableSelected(self, index):
''' loads the tools for the selected tool table '''
name = self.form.TableList.itemWidget(self.form.TableList.itemFromIndex(index)).getTableName()
"""loads the tools for the selected tool table"""
name = self.form.TableList.itemWidget(
self.form.TableList.itemFromIndex(index)
).getTableName()
self.loadTable(name)
def loadTable(self, name):
''' loads the tools for the selected tool table '''
"""loads the tools for the selected tool table"""
tooldata = self.TLM.getTools(name)
if tooldata:
self.form.ToolsList.setModel(tooldata)
self.form.ToolsList.resizeColumnsToContents()
self.form.ToolsList.horizontalHeader().setResizeMode(self.form.ToolsList.model().columnCount() - 1, QtGui.QHeaderView.Stretch)
self.form.ToolsList.horizontalHeader().setResizeMode(
self.form.ToolsList.model().columnCount() - 1, QtGui.QHeaderView.Stretch
)
self.setCurrentToolTableByName(name)
def addNewToolTable(self):
''' adds new tool to selected tool table '''
"""adds new tool to selected tool table"""
name = self.TLM.addNewToolTable()
self.loadToolTables()
self.loadTable(name)
def loadToolTables(self):
''' Load list of available tool tables '''
"""Load list of available tool tables"""
self.form.TableList.clear()
model = self.form.ToolsList.model()
if model:
@@ -314,44 +344,54 @@ class EditorPanel():
listWidgetItem = QtGui.QListWidgetItem()
listItem = ToolTableListWidgetItem(self.TLM)
listItem.setTableName(table.Name)
listItem.setIcon(QtGui.QPixmap(':/icons/Path_ToolTable.svg'))
listItem.setIcon(QtGui.QPixmap(":/icons/Path_ToolTable.svg"))
listItem.toolMoved.connect(self.reloadReset)
listWidgetItem.setSizeHint(QtCore.QSize(0,40))
listWidgetItem.setSizeHint(QtCore.QSize(0, 40))
self.form.TableList.addItem(listWidgetItem)
self.form.TableList.setItemWidget(listWidgetItem, listItem)
#Load the first tooltable
# Load the first tooltable
self.loadTable(self.TLM.getCurrentTableName())
def reloadReset(self):
''' reloads the current tooltable'''
"""reloads the current tooltable"""
name = self.TLM.getCurrentTableName()
self.loadTable(name)
def setCurrentToolTableByName(self, name):
''' get the current tool table '''
"""get the current tool table"""
item = self.getToolTableByName(name)
if item:
self.form.TableList.setCurrentItem(item)
def getToolTableByName(self, name):
''' returns the listWidgetItem for the selected name'''
"""returns the listWidgetItem for the selected name"""
for i in range(self.form.TableList.count()):
tableName = self.form.TableList.itemWidget(self.form.TableList.item(i)).getTableName()
tableName = self.form.TableList.itemWidget(
self.form.TableList.item(i)
).getTableName()
if tableName == name:
return self.form.TableList.item(i)
return False
def removeToolTable(self):
''' delete the selected tool table '''
"""delete the selected tool table"""
self.TLM.deleteToolTable()
self.loadToolTables()
def renameTable(self):
''' provides dialog for new tablename and renames the selected tool table'''
"""provides dialog for new tablename and renames the selected tool table"""
name = self.TLM.getCurrentTableName()
newName, ok = QtGui.QInputDialog.getText(None, translate("TooltableEditor","Rename Tooltable"),translate("TooltableEditor","Enter Name:"),QtGui.QLineEdit.Normal,name)
newName, ok = QtGui.QInputDialog.getText(
None,
translate("Path_ToolTable", "Rename Tooltable"),
translate("Path_ToolTable", "Enter Name:"),
QtGui.QLineEdit.Normal,
name,
)
if ok and newName:
index = self.form.TableList.indexFromItem(self.getToolTableByName(name)).row()
index = self.form.TableList.indexFromItem(
self.getToolTableByName(name)
).row()
reloadTables = self.TLM.renameToolTable(newName, index)
if reloadTables:
self.loadToolTables()
@@ -380,14 +420,21 @@ class EditorPanel():
self.form.TableList.itemChanged.connect(self.renameTable)
self.form.ButtonAddToolTable.clicked.connect(self.addNewToolTable)
self.form.ButtonAddToolTable.setToolTip(translate("TooltableEditor","Add New Tool Table"))
self.form.ButtonAddToolTable.setToolTip(
translate("Path_ToolTable", "Add New Tool Table")
)
self.form.ButtonRemoveToolTable.clicked.connect(self.removeToolTable)
self.form.ButtonRemoveToolTable.setToolTip(translate("TooltableEditor","Delete Selected Tool Table"))
self.form.ButtonRemoveToolTable.setToolTip(
translate("Path_ToolTable", "Delete Selected Tool Table")
)
self.form.ButtonRenameToolTable.clicked.connect(self.renameTable)
self.form.ButtonRenameToolTable.setToolTip(translate("TooltableEditor","Rename Selected Tool Table"))
self.form.ButtonRenameToolTable.setToolTip(
translate("Path_ToolTable", "Rename Selected Tool Table")
)
self.setFields()
class ToolTableListWidgetItem(QtGui.QWidget):
toolMoved = QtCore.Signal()
@@ -405,13 +452,13 @@ class ToolTableListWidgetItem(QtGui.QWidget):
self.mainLayout.addWidget(self.tableNameLabel, 1)
self.setLayout(self.mainLayout)
def setTableName (self, text):
def setTableName(self, text):
self.tableNameLabel.setText(text)
def getTableName(self):
return self.tableNameLabel.text()
def setIcon (self, icon):
def setIcon(self, icon):
icon = icon.scaled(24, 24)
self.iconQLabel.setPixmap(icon)
@@ -434,7 +481,7 @@ class ToolTableListWidgetItem(QtGui.QWidget):
self.toolMoved.emit()
class CommandToolLibraryEdit():
class CommandToolLibraryEdit:
def __init__(self):
pass
@@ -449,10 +496,12 @@ class CommandToolLibraryEdit():
cb()
def GetResources(self):
return {'Pixmap' : 'Path_ToolTable',
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager"),
'Accel': "P, T",
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_ToolTable","Tool Manager")}
return {
"Pixmap": "Path_ToolTable",
"MenuText": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"),
"Accel": "P, T",
"ToolTip": QT_TRANSLATE_NOOP("Path_ToolTable", "Tool Manager"),
}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
@@ -460,6 +509,7 @@ class CommandToolLibraryEdit():
def Activated(self):
self.edit()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_ToolLibraryEdit',CommandToolLibraryEdit())
FreeCADGui.addCommand("Path_ToolLibraryEdit", CommandToolLibraryEdit())

View File

@@ -23,7 +23,6 @@
from __future__ import print_function
import FreeCAD
import FreeCADGui
import Path
import PathScripts
import PathScripts.PathLog as PathLog
@@ -31,14 +30,15 @@ import PathScripts.PathUtil as PathUtil
import json
import os
import xml.sax
from PySide import QtGui
from PySide import QtCore, QtGui
if False:
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
PathLog.trackModule(PathLog.thisModule())
else:
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
translate = FreeCAD.Qt.translate
# Tooltable XML readers
class FreeCADTooltableHandler(xml.sax.ContentHandler):
@@ -62,7 +62,7 @@ class FreeCADTooltableHandler(xml.sax.ContentHandler):
self.tool.ToolType = str(attrs["type"])
self.tool.Material = str(attrs["mat"])
# for some reason without the following line I get an error
#print attrs["diameter"]
# print attrs["diameter"]
self.tool.Diameter = float(attrs["diameter"])
self.tool.LengthOffset = float(attrs["length"])
self.tool.FlatRadius = float(attrs["flat"])
@@ -80,7 +80,6 @@ class FreeCADTooltableHandler(xml.sax.ContentHandler):
class HeeksTooltableHandler(xml.sax.ContentHandler):
def __init__(self):
xml.sax.ContentHandler.__init__(self)
self.tooltable = Path.Tooltable()
@@ -115,15 +114,13 @@ class HeeksTooltableHandler(xml.sax.ContentHandler):
elif m == "1":
self.tool.Material = "Carbide"
# for some reason without the following line I get an error
#print attrs["diameter"]
# print attrs["diameter"]
self.tool.Diameter = float(attrs["diameter"])
self.tool.LengthOffset = float(attrs["tool_length_offset"])
self.tool.FlatRadius = float(attrs["flat_radius"])
self.tool.CornerRadius = float(attrs["corner_radius"])
self.tool.CuttingEdgeAngle = float(
attrs["cutting_edge_angle"])
self.tool.CuttingEdgeHeight = float(
attrs["cutting_edge_height"])
self.tool.CuttingEdgeAngle = float(attrs["cutting_edge_angle"])
self.tool.CuttingEdgeHeight = float(attrs["cutting_edge_height"])
# Call when an elements ends
def endElement(self, name):
@@ -134,17 +131,21 @@ class HeeksTooltableHandler(xml.sax.ContentHandler):
self.tool = None
class ToolLibraryManager():
'''
class ToolLibraryManager:
"""
The Tool Library is a list of individual tool tables. Each
Tool Table can contain n tools. The tool library will be persisted to user
preferences and all or part of the library can be exported to other formats
'''
"""
TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.json)")
TooltableTypeXML = translate("PathToolLibraryManager", "Tooltable XML (*.xml)")
TooltableTypeHeekscad = translate("PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)")
TooltableTypeLinuxCNC = translate("PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)")
TooltableTypeJSON = translate("PathToolLibraryManager", "Tooltable JSON (*.json)")
TooltableTypeXML = translate("PathToolLibraryManager", "Tooltable XML (*.xml)")
TooltableTypeHeekscad = translate(
"PathToolLibraryManager", "HeeksCAD tooltable (*.tooltable)"
)
TooltableTypeLinuxCNC = translate(
"PathToolLibraryManager", "LinuxCNC tooltable (*.tbl)"
)
PreferenceMainLibraryXML = "ToolLibrary"
PreferenceMainLibraryJSON = "ToolLibrary-Main"
@@ -156,38 +157,40 @@ class ToolLibraryManager():
self.loadToolTables()
def getToolTables(self):
''' Return tool table list '''
"""Return tool table list"""
return self.toolTables
def getCurrentTableName(self):
''' return the name of the currently loaded tool table '''
"""return the name of the currently loaded tool table"""
return self.currentTableName
def getCurrentTable(self):
''' returns an object of the current tool table '''
"""returns an object of the current tool table"""
return self.getTableFromName(self.currentTableName)
def getTableFromName(self, name):
''' get the tool table object from the name '''
"""get the tool table object from the name"""
for table in self.toolTables:
if table.Name == name:
return table
def getNextToolTableName(self, tableName='Tool Table'):
''' get a unique name for a new tool table '''
def getNextToolTableName(self, tableName="Tool Table"):
"""get a unique name for a new tool table"""
iter = 1
tempName = tableName[-2:]
if tempName[0] == '-' and tempName[-1].isdigit():
if tempName[0] == "-" and tempName[-1].isdigit():
tableName = tableName[:-2]
while any(table.Name == tableName + '-' + str(iter) for table in self.toolTables):
while any(
table.Name == tableName + "-" + str(iter) for table in self.toolTables
):
iter += 1
return tableName + '-' + str(iter)
return tableName + "-" + str(iter)
def addNewToolTable(self):
''' creates a new tool table '''
"""creates a new tool table"""
tt = Path.Tooltable()
tt.Version = 1
name = self.getNextToolTableName()
@@ -197,20 +200,27 @@ class ToolLibraryManager():
return name
def deleteToolTable(self):
''' deletes the selected tool table '''
"""deletes the selected tool table"""
if len(self.toolTables):
index = next((index for (index, d) in enumerate(self.toolTables) if d.Name == self.currentTableName), None)
index = next(
(
index
for (index, d) in enumerate(self.toolTables)
if d.Name == self.currentTableName
),
None,
)
self.toolTables.pop(index)
self.saveMainLibrary()
def renameToolTable(self, newName, index):
''' renames a tool table with the new name'''
"""renames a tool table with the new name"""
currentTableName = self.toolTables[index].Name
if newName == currentTableName:
PathLog.error(translate('PathToolLibraryManager', "Tool Table Same Name"))
PathLog.error(translate("PathToolLibraryManager", "Tool Table Same Name"))
return False
if newName in self.toolTables:
PathLog.error(translate('PathToolLibraryManager', "Tool Table Name Exists"))
PathLog.error(translate("PathToolLibraryManager", "Tool Table Name Exists"))
return False
tt = self.getTableFromName(currentTableName)
if tt:
@@ -218,67 +228,74 @@ class ToolLibraryManager():
self.saveMainLibrary()
return True
def templateAttrs(self):
''' gets the tool table arributes '''
"""gets the tool table arributes"""
toolTables = []
for tt in self.toolTables:
tableData = {}
tableData['Version'] = 1
tableData['TableName'] = tt.Name
tableData["Version"] = 1
tableData["TableName"] = tt.Name
toolData = {}
for tool in tt.Tools:
toolData[tool] = tt.Tools[tool].templateAttrs()
tableData['Tools'] = toolData
tableData["Tools"] = toolData
toolTables.append(tableData)
return toolTables
def tooltableFromAttrs(self, stringattrs):
if stringattrs.get('Version') and 1 == int(stringattrs['Version']):
if stringattrs.get("Version") and 1 == int(stringattrs["Version"]):
tt = Path.Tooltable()
tt.Version = 1
tt.Name = self.getNextToolTableName()
if stringattrs.get('Version'):
tt.Version = stringattrs.get('Version')
if stringattrs.get("Version"):
tt.Version = stringattrs.get("Version")
if stringattrs.get('TableName'):
tt.Name = stringattrs.get('TableName')
if stringattrs.get("TableName"):
tt.Name = stringattrs.get("TableName")
if any(table.Name == tt.Name for table in self.toolTables):
tt.Name = self.getNextToolTableName(tt.Name)
for key, attrs in PathUtil.keyValueIter(stringattrs['Tools']):
tool = Path.Tool()
tool.Name = str(attrs["name"])
tool.ToolType = str(attrs["tooltype"])
tool.Material = str(attrs["material"])
tool.Diameter = float(attrs["diameter"])
tool.LengthOffset = float(attrs["lengthOffset"])
tool.FlatRadius = float(attrs["flatRadius"])
tool.CornerRadius = float(attrs["cornerRadius"])
tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"])
tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"])
tt.setTool(int(key), tool)
for key, attrs in PathUtil.keyValueIter(stringattrs["Tools"]):
tool = Path.Tool()
tool.Name = str(attrs["name"])
tool.ToolType = str(attrs["tooltype"])
tool.Material = str(attrs["material"])
tool.Diameter = float(attrs["diameter"])
tool.LengthOffset = float(attrs["lengthOffset"])
tool.FlatRadius = float(attrs["flatRadius"])
tool.CornerRadius = float(attrs["cornerRadius"])
tool.CuttingEdgeAngle = float(attrs["cuttingEdgeAngle"])
tool.CuttingEdgeHeight = float(attrs["cuttingEdgeHeight"])
tt.setTool(int(key), tool)
return tt
else:
PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable template version %s") % stringattrs.get('Version'))
PathLog.error(
translate(
"PathToolLibraryManager",
"Unsupported Path tooltable template version %s",
)
% stringattrs.get("Version")
)
return None
def loadToolTables(self):
''' loads the tool tables from the stored data '''
"""loads the tool tables from the stored data"""
self.toolTables = []
self.currentTableName = ''
self.currentTableName = ""
def addTable(tt):
if tt:
self.toolTables.append(tt)
else:
PathLog.error(translate('PathToolLibraryManager', "Unsupported Path tooltable"))
PathLog.error(
translate("PathToolLibraryManager", "Unsupported Path tooltable")
)
prefString = self.prefs.GetString(self.PreferenceMainLibraryJSON, "")
@@ -300,14 +317,14 @@ class ToolLibraryManager():
self.currentTableName = self.toolTables[0].Name
def saveMainLibrary(self):
'''Persists the permanent library to FreeCAD user preferences'''
"""Persists the permanent library to FreeCAD user preferences"""
tmpstring = json.dumps(self.templateAttrs())
self.prefs.SetString(self.PreferenceMainLibraryJSON, tmpstring)
self.loadToolTables()
return True
def getJobList(self):
'''Builds the list of all Tool Table lists'''
"""Builds the list of all Tool Table lists"""
tablelist = []
for o in FreeCAD.ActiveDocument.Objects:
@@ -318,27 +335,29 @@ class ToolLibraryManager():
return tablelist
def getTool(self, listname, toolnum):
''' gets the tool object '''
"""gets the tool object"""
tt = self.getTableFromName(listname)
return tt.getTool(toolnum)
def getTools(self, tablename):
'''returns the tool data for a given table'''
"""returns the tool data for a given table"""
tooldata = []
tableExists = any(table.Name == tablename for table in self.toolTables)
tableExists = any(table.Name == tablename for table in self.toolTables)
if tableExists:
self.currentTableName = tablename
else:
return None
tt = self.getTableFromName(tablename)
headers = ["","Tool Num.","Name","Tool Type","Diameter"]
headers = ["", "Tool Num.", "Name", "Tool Type", "Diameter"]
model = QtGui.QStandardItemModel()
model.setHorizontalHeaderLabels(headers)
def unitconv(ivalue):
val = FreeCAD.Units.Quantity(ivalue, FreeCAD.Units.Length)
displayed_val = val.UserString #just the displayed value-not the internal one
displayed_val = (
val.UserString
) # just the displayed value-not the internal one
return displayed_val
if tt:
@@ -348,10 +367,10 @@ class ToolLibraryManager():
itemcheck = QtGui.QStandardItem()
itemcheck.setCheckable(True)
itemNumber = QtGui.QStandardItem(str(number))
itemName = QtGui.QStandardItem(t.Name)
itemToolType = QtGui.QStandardItem(t.ToolType)
itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter))
itemNumber = QtGui.QStandardItem(str(number))
itemName = QtGui.QStandardItem(t.Name)
itemToolType = QtGui.QStandardItem(t.ToolType)
itemDiameter = QtGui.QStandardItem(unitconv(t.Diameter))
row = [itemcheck, itemNumber, itemName, itemToolType, itemDiameter]
model.appendRow(row)
@@ -367,9 +386,9 @@ class ToolLibraryManager():
try:
fileExtension = os.path.splitext(filename[0])[1].lower()
xmlHandler = None
if fileExtension == '.tooltable':
if fileExtension == ".tooltable":
xmlHandler = HeeksTooltableHandler()
if fileExtension == '.xml':
if fileExtension == ".xml":
xmlHandler = FreeCADTooltableHandler()
if xmlHandler:
@@ -405,41 +424,58 @@ class ToolLibraryManager():
else:
return False
except Exception as e: # pylint: disable=broad-except
except Exception as e: # pylint: disable=broad-except
print("could not parse file", e)
def write(self, filename, listname):
"exports the tooltable to a file"
tt = self.getTableFromName(listname)
if tt:
try:
def openFileWithExtension(name, ext):
fext = os.path.splitext(name)[1].lower()
if fext != ext:
name = "{}{}".format(name, ext)
return (open(PathUtil.toUnicode(name), 'w'), name)
return (open(PathUtil.toUnicode(name), "w"), name)
if filename[1] == self.TooltableTypeXML:
fp,fname = openFileWithExtension(filename[0], '.xml')
fp, fname = openFileWithExtension(filename[0], ".xml")
fp.write('<?xml version="1.0" encoding="UTF-8"?>\n')
fp.write(tt.Content)
elif filename[1] == self.TooltableTypeLinuxCNC:
fp,fname = openFileWithExtension(filename[0], '.tbl')
fp, fname = openFileWithExtension(filename[0], ".tbl")
for key in tt.Tools:
t = tt.Tools[key]
fp.write("T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format(key,0,t.LengthOffset,0,0,0,0,0,0,t.Diameter,0,0,0,t.Name))
fp.write(
"T{0} P{0} Y{1} Z{2} A{3} B{4} C{5} U{6} V{7} W{8} D{9} I{10} J{11} Q{12} ;{13}\n".format(
key,
0,
t.LengthOffset,
0,
0,
0,
0,
0,
0,
t.Diameter,
0,
0,
0,
t.Name,
)
)
else:
fp,fname = openFileWithExtension(filename[0], '.json')
fp, fname = openFileWithExtension(filename[0], ".json")
json.dump(self.templateAttrs(), fp, sort_keys=True, indent=2)
fp.close()
print("Written ", PathUtil.toUnicode(fname))
except Exception as e: # pylint: disable=broad-except
except Exception as e: # pylint: disable=broad-except
print("Could not write file:", e)
def addnew(self, listname, tool, position = None):
def addnew(self, listname, tool, position=None):
"adds a new tool at the end of the table"
tt = self.getTableFromName(listname)
if not tt:
@@ -456,7 +492,7 @@ class ToolLibraryManager():
return newID
def updateTool(self, listname, toolnum, tool):
'''updates tool data'''
"""updates tool data"""
tt = self.getTableFromName(listname)
tt.deleteTool(toolnum)
tt.setTool(toolnum, tool)
@@ -498,7 +534,7 @@ class ToolLibraryManager():
return True, target
def duplicate(self, number, listname):
''' duplicates the selected tool in the selected tool table '''
"""duplicates the selected tool in the selected tool table"""
tt = self.getTableFromName(listname)
tool = tt.getTool(number).copy()
tt.addTools(tool)
@@ -510,7 +546,7 @@ class ToolLibraryManager():
return True, newID
def moveToTable(self, number, listname):
''' Moves the tool to selected tool table '''
"""Moves the tool to selected tool table"""
fromTable = self.getTableFromName(self.getCurrentTableName())
toTable = self.getTableFromName(listname)
tool = fromTable.getTool(number).copy()
@@ -518,9 +554,9 @@ class ToolLibraryManager():
fromTable.deleteTool(number)
def delete(self, number, listname):
'''deletes a tool from the current list'''
"""deletes a tool from the current list"""
tt = self.getTableFromName(listname)
tt.deleteTool(number)
if listname == self.getCurrentTableName():
self.saveMainLibrary()
return True
return True