Move Path.Base and Path.Base.Gui refactoring

This commit is contained in:
Markus Lampert
2022-08-13 19:53:53 -07:00
parent f1596599ee
commit 5ca5a94f5e
63 changed files with 274 additions and 298 deletions

View File

@@ -25,9 +25,9 @@ from pivy import coin
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Gui.Util as PathGuiUtil
import Path.Op.Gui.Base as PathOpGui
import PathScripts.PathFeatureExtensions as FeatureExtensions
import PathScripts.PathGui as PathGui
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
@@ -197,7 +197,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
self.extensions = list()
self.defaultLength = PathGui.QuantitySpinBox(
self.defaultLength = PathGuiUtil.QuantitySpinBox(
self.form.defaultLength, obj, "ExtensionLengthDefault"
)

View File

@@ -1,262 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import Path
# lazily loaded modules
from lazy_loader.lazy_loader import LazyLoader
Draft = LazyLoader("Draft", globals(), "Draft")
from PySide import QtCore, QtGui
from pivy import coin
__title__ = "Path GetPoint UI"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Helper class to use FreeCADGUi.Snapper to let the user enter arbitrary points while the task panel is active."
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
# Path.Log.track(Path.Log.thisModule())
class TaskPanel:
"""Use an instance of this class in another TaskPanel to invoke the snapper.
Create the instance in the TaskPanel's constructors and invoke getPoint(whenDone, start) whenever a new point is
required or an existing point needs to be edited. The receiver is expected to have the same lifespan as the form
provided in the constructor.
The (only) public API function other than the constructor is getPoint(whenDone, start).
"""
def __init__(self, form, onPath=False):
"""__init___(form) ... form will be replaced by PointEdit.ui while the Snapper is active."""
self.formOrig = form
self.formPoint = FreeCADGui.PySideUic.loadUi(":/panels/PointEdit.ui")
self.formPoint.setParent(form.parent())
form.parent().layout().addWidget(self.formPoint)
self.formPoint.hide()
self.setupUi()
self.buttonBox = None
self.onPath = onPath
self.obj = None
self.pt = None
self.point = None
self.pointCbClick = None
self.pointCbMove = None
self.pointWhenDone = None
self.escape = None
self.view = None
def setupUi(self):
"""setupUi() ... internal function - do not call."""
self.formPoint.buttonBox.accepted.connect(self.pointAccept)
self.formPoint.buttonBox.rejected.connect(self.pointReject)
self.formPoint.globalX.editingFinished.connect(self.updatePoint)
self.formPoint.globalY.editingFinished.connect(self.updatePoint)
self.formPoint.globalZ.editingFinished.connect(self.updatePoint)
self.formPoint.globalX.setProperty(
"unit", FreeCAD.Units.MilliMetre.getUserPreferred()[2]
)
self.formPoint.globalY.setProperty(
"unit", FreeCAD.Units.MilliMetre.getUserPreferred()[2]
)
self.formPoint.globalZ.setProperty(
"unit", FreeCAD.Units.MilliMetre.getUserPreferred()[2]
)
def addEscapeShortcut(self):
"""addEscapeShortcut() ... internal function - do not call."""
# The only way I could get to intercept the escape key, or really any key was
# by creating an action with a shortcut .....
self.escape = QtGui.QAction(self.formPoint)
self.escape.setText("Done")
self.escape.setShortcut(QtGui.QKeySequence.fromString("Esc"))
QtCore.QObject.connect(
self.escape, QtCore.SIGNAL("triggered()"), self.pointDone
)
self.formPoint.addAction(self.escape)
def removeEscapeShortcut(self):
"""removeEscapeShortcut() ... internal function - do not call."""
if self.escape:
self.formPoint.removeAction(self.escape)
self.escape = None
def getPoint(self, whenDone, start=None):
"""getPoint(whenDone, start=None) ... invoke Snapper and call whenDone when a point is entered or the user cancels the operation.
whenDone(point, obj) is called either with a point and the object on which the point lies if the user set the point,
or None and None if the user cancelled the operation.
start is an optional Vector indicating from where to start Snapper. This is mostly used when editing existing points. Snapper also
creates a dotted line indicating from where the original point started from.
If start is specified the Snapper UI is closed on the first point the user enters. If start remains None, then Snapper is kept open
until the user explicitly closes Snapper. This lets the user enter multiple points in quick succession."""
# there's no get point without Snapper, if it's not loaded, need to do that explicitly
if not hasattr(FreeCADGui, "Snapper"):
import DraftTools
def displayPoint(p):
self.point = p
self.formPoint.globalX.setProperty("rawValue", p.x)
self.formPoint.globalY.setProperty("rawValue", p.y)
self.formPoint.globalZ.setProperty("rawValue", p.z)
self.formPoint.globalX.setFocus()
self.formPoint.globalX.selectAll()
def mouseMove(cb):
p = None
event = cb.getEvent()
pos = event.getPosition()
if self.onPath:
# There should not be a dependency from Draft->Path, so handle Path "snapping"
# directly, at least for now. Simple enough because there isn't really any
# "snapping" going on other than what getObjectInfo() provides.
screenpos = tuple(pos.getValue())
snapInfo = Draft.get3DView().getObjectInfo(screenpos)
if snapInfo:
obj = FreeCAD.ActiveDocument.getObject(snapInfo["Object"])
if hasattr(obj, "Path"):
self.obj = obj
p = FreeCAD.Vector(snapInfo["x"], snapInfo["y"], snapInfo["z"])
self.pt = p
else:
self.obj = None
else:
# Snapper handles regular objects just fine
cntrl = event.wasCtrlDown()
shift = event.wasShiftDown()
self.pt = FreeCADGui.Snapper.snap(
pos, lastpoint=start, active=cntrl, constrain=shift
)
plane = FreeCAD.DraftWorkingPlane
p = plane.getLocalCoords(self.pt)
self.obj = FreeCADGui.Snapper.lastSnappedObject
if p:
displayPoint(p)
def click(cb):
event = cb.getEvent()
if (
event.getButton() == 1
and event.getState() == coin.SoMouseButtonEvent.DOWN
):
if self.obj:
accept()
def accept():
if start:
self.pointAccept()
else:
self.pointAcceptAndContinue()
def cancel():
self.pointReject()
self.pointWhenDone = whenDone
self.formOrig.hide()
self.formPoint.show()
self.addEscapeShortcut()
if start:
displayPoint(start)
else:
displayPoint(FreeCAD.Vector(0, 0, 0))
self.view = Draft.get3DView()
self.pointCbClick = self.view.addEventCallbackPivy(
coin.SoMouseButtonEvent.getClassTypeId(), click
)
self.pointCbMove = self.view.addEventCallbackPivy(
coin.SoLocation2Event.getClassTypeId(), mouseMove
)
if self.buttonBox:
self.buttonBox.setEnabled(False)
FreeCADGui.Snapper.forceGridOff = True
def pointFinish(self, ok, cleanup=True):
"""pointFinish(ok, cleanup=True) ... internal function - do not call."""
if cleanup:
self.removeGlobalCallbacks()
FreeCADGui.Snapper.off(True)
if self.buttonBox:
self.buttonBox.setEnabled(True)
self.removeEscapeShortcut()
self.formPoint.hide()
self.formOrig.show()
self.formOrig.setFocus()
if ok:
self.updatePoint(False)
self.pointWhenDone(self.pt, self.obj)
else:
self.pointWhenDone(None, None)
def pointDone(self):
"""pointDone() ... internal function - do not call."""
self.pointFinish(False)
def pointReject(self):
"""pointReject() ... internal function - do not call."""
self.pointFinish(False)
def pointAccept(self):
"""pointAccept() ... internal function - do not call."""
self.pointFinish(True)
def pointAcceptAndContinue(self):
"""pointAcceptAndContinue() ... internal function - do not call."""
self.pointFinish(True, False)
def removeGlobalCallbacks(self):
"""removeGlobalCallbacks() ... internal function - do not call."""
if hasattr(self, "view") and self.view:
if self.pointCbClick:
self.view.removeEventCallbackPivy(
coin.SoMouseButtonEvent.getClassTypeId(), self.pointCbClick
)
self.pointCbClick = None
if self.pointCbMove:
self.view.removeEventCallbackPivy(
coin.SoLocation2Event.getClassTypeId(), self.pointCbMove
)
self.pointCbMove = None
self.view = None
def updatePoint(self, usePoint=True):
"""updatePoint() ... internal function - do not call."""
if usePoint and self.point:
self.pt = self.point
else:
x = FreeCAD.Units.Quantity(self.formPoint.globalX.text()).Value
y = FreeCAD.Units.Quantity(self.formPoint.globalY.text()).Value
z = FreeCAD.Units.Quantity(self.formPoint.globalZ.text()).Value
self.pt = FreeCAD.Vector(x, y, z)

View File

@@ -1,253 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
from PySide import QtGui, QtCore
from PySide import QtCore, QtGui
__title__ = "Path UI helper and utility functions"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "A collection of helper and utility functions for the Path GUI."
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
def populateCombobox(form, enumTups, comboBoxesPropertyMap):
"""populateCombobox(form, enumTups, 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
"""
Path.Log.track(enumTups)
# 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 updateInputField(obj, prop, widget, onBeforeChange=None):
"""updateInputField(obj, prop, widget) ... update obj's property prop with the value of widget.
The property's value is only assigned if the new value differs from the current value.
This prevents onChanged notifications where the value didn't actually change.
Gui::InputField and Gui::QuantitySpinBox widgets are supported - and the property can
be of type Quantity or Float.
If onBeforeChange is specified it is called before a new value is assigned to the property.
Returns True if a new value was assigned, False otherwise (new value is the same as the current).
"""
value = widget.property("rawValue")
Path.Log.track("value: {}".format(value))
attr = PathUtil.getProperty(obj, prop)
attrValue = attr.Value if hasattr(attr, "Value") else attr
isDiff = False
if not Path.Geom.isRoughly(attrValue, value):
isDiff = True
else:
if hasattr(obj, "ExpressionEngine"):
exprSet = False
for (prp, expr) in obj.ExpressionEngine:
if prp == prop:
exprSet = True
Path.Log.debug('prop = "expression": {} = "{}"'.format(prp, expr))
value = FreeCAD.Units.Quantity(obj.evalExpression(expr)).Value
if not Path.Geom.isRoughly(attrValue, value):
isDiff = True
break
if exprSet:
widget.setReadOnly(True)
widget.setStyleSheet("color: gray")
else:
widget.setReadOnly(False)
widget.setStyleSheet("color: black")
widget.update()
if isDiff:
Path.Log.debug(
"updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value)
)
if onBeforeChange:
onBeforeChange(obj)
PathUtil.setProperty(obj, prop, value)
return True
return False
class QuantitySpinBox(QtCore.QObject):
"""Controller class to interface a Gui::QuantitySpinBox.
The spin box gets bound to a given property and supports update in both directions.
QuatitySpinBox(widget, obj, prop, onBeforeChange=None)
widget ... expected to be reference to a Gui::QuantitySpinBox
obj ... document object
prop ... canonical name of the (sub-) property
onBeforeChange ... an optional callback being executed before the value of the property is changed
"""
def __init__(self, widget, obj, prop, onBeforeChange=None):
super().__init__()
Path.Log.track(widget)
self.widget = widget
self.onBeforeChange = onBeforeChange
self.prop = None
self.obj = obj
self.lastWidgetText = self.widget.text()
self.attachTo(obj, prop)
self.widget.installEventFilter(self)
# Connect local class method as slot
self.widget.textChanged.connect(self.onWidgetValueChanged)
def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.Type.FocusIn:
self.updateSpinBox()
return False
def onWidgetValueChanged(self):
"""onWidgetValueChanged()... Slot method for determining if a change
in widget value is a result of an expression edit, or a simple spinbox change.
If the former, emit a manual `editingFinished` signal because the Formula Editor
window returned a value to the base widget, leaving it in read-only mode,
and finishing the editing of the value. Otherwise, due nothing if the value
has not changed, or there is no active expression for the property.
If the user closes the Formula Editor to cancel the edit, the value will not
be changed, and this manual signal will not be emitted."""
if self._hasExpression() and self.widget.text() != self.lastWidgetText:
self.widget.editingFinished.emit()
def attachTo(self, obj, prop=None):
"""attachTo(obj, prop=None) ... use an existing editor for the given object and property"""
Path.Log.track(self.prop, prop)
self.obj = obj
self.prop = prop
if obj and prop:
attr = PathUtil.getProperty(obj, prop)
if attr is not None:
if hasattr(attr, "Value"):
self.widget.setProperty("unit", attr.getUserPreferred()[2])
self.widget.setProperty("binding", "%s.%s" % (obj.Name, prop))
self.valid = True
else:
Path.Log.warning("Cannot find property {} of {}".format(prop, obj.Label))
self.valid = False
else:
self.valid = False
def expression(self):
"""expression() ... returns the expression if one is bound to the property"""
Path.Log.track(self.prop, self.valid)
if self.valid:
return self.widget.property("expression")
return ""
def setMinimum(self, quantity):
"""setMinimum(quantity) ... set the minimum"""
Path.Log.track(self.prop, self.valid)
if self.valid:
value = quantity.Value if hasattr(quantity, "Value") else quantity
self.widget.setProperty("setMinimum", value)
def updateSpinBox(self, quantity=None):
"""updateSpinBox(quantity=None) ... update the display value of the spin box.
If no value is provided the value of the bound property is used.
quantity can be of type Quantity or Float."""
Path.Log.track(self.prop, self.valid, quantity)
if self.valid:
expr = self._hasExpression()
if quantity is None:
if expr:
quantity = FreeCAD.Units.Quantity(self.obj.evalExpression(expr))
else:
quantity = PathUtil.getProperty(self.obj, self.prop)
value = quantity.Value if hasattr(quantity, "Value") else quantity
self.widget.setProperty("rawValue", value)
self.lastWidgetText = self.widget.text() # update last widget value
if expr:
self.widget.setReadOnly(True)
self.widget.setStyleSheet("color: gray")
else:
self.widget.setReadOnly(False)
self.widget.setStyleSheet("color: black")
def updateProperty(self):
"""updateProperty() ... update the bound property with the value from the spin box"""
Path.Log.track(self.prop, self.valid)
if self.valid:
return updateInputField(
self.obj, self.prop, self.widget, self.onBeforeChange
)
return None
def _hasExpression(self):
for (prop, exp) in self.obj.ExpressionEngine:
if prop == self.prop:
return exp
return None
def getDocNode():
doc = FreeCADGui.ActiveDocument.Document.Name
tws = FreeCADGui.getMainWindow().findChildren(QtGui.QTreeWidget)
for tw in tws:
if tw.topLevelItemCount() != 1 or tw.topLevelItem(0).text(0) != "Application":
continue
toptree = tw.topLevelItem(0)
for i in range(0, toptree.childCount()):
docitem = toptree.child(i)
if docitem.text(0) == doc:
return docitem
return None
def disableItem(item):
Dragflag = QtCore.Qt.ItemFlag.ItemIsDragEnabled
Dropflag = QtCore.Qt.ItemFlag.ItemIsDropEnabled
item.setFlags(item.flags() & ~Dragflag)
item.setFlags(item.flags() & ~Dropflag)
for idx in range(0, item.childCount()):
disableItem(item.child(idx))
def findItem(docitem, objname):
print(docitem.text(0))
for i in range(0, docitem.childCount()):
if docitem.child(i).text(0) == objname:
return docitem.child(i)
res = findItem(docitem.child(i), objname)
if res:
return res
return None

View File

@@ -1,99 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import Path
import subprocess
LOGLEVEL = False
if LOGLEVEL:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
Processed = False
def Startup():
global Processed
if not Processed:
Path.Log.debug("Initializing PathGui")
from Path.Op.Gui import Adaptive
from Path.Dressup.Gui import AxisMap
from Path.Dressup.Gui import Dogbone
from Path.Dressup.Gui import Dragknife
from Path.Dressup.Gui import LeadInOut
from Path.Dressup.Gui import PathBoundary
from Path.Dressup.Gui import RampEntry
from Path.Dressup.Gui import Tags
from Path.Dressup.Gui import ZCorrect
from Path.Op.Gui import Custom
from Path.Op.Gui import Deburr
from Path.Op.Gui import Drilling
from Path.Op.Gui import Engrave
from Path.Op.Gui import Helix
from Path.Op.Gui import MillFace
from Path.Op.Gui import Pocket
from Path.Op.Gui import PocketShape
from Path.Op.Gui import Probe
from Path.Op.Gui import Profile
from Path.Op.Gui import Slot
from Path.Op.Gui import ThreadMilling
from Path.Op.Gui import Vcarve
from Path.Post import Command
from Path.Tool import Controller
from Path.Tool.Gui import Controller
from PathScripts import PathArray
from PathScripts import PathComment
from PathScripts import PathFixture
from PathScripts import PathHop
from PathScripts import PathInspect
from PathScripts import PathPropertyBagGui
from PathScripts import PathSanity
from PathScripts import PathSetupSheetGui
from PathScripts import PathSimpleCopy
from PathScripts import PathSimulatorGui
from PathScripts import PathStop
from PathScripts import PathUtilsGui
from packaging.version import Version, parse
# If camotics is installed and current enough, import the GUI
try:
import camotics
r = subprocess.run(
["camotics", "--version"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
).stderr.strip()
v = parse(r.decode("utf-8"))
if v >= Version("1.2.2"):
from PathScripts import PathCamoticsGui
except (FileNotFoundError, ModuleNotFoundError):
pass
Processed = True
else:
Path.Log.debug("Skipping PathGui initialisation")

View File

@@ -1,125 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
import Path.Base.Util as PathUtil
import PathGui
import importlib
__title__ = "Path Icon ViewProvider"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "ViewProvider who's main and only task is to assign an icon."
translate = FreeCAD.Qt.translate
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class ViewProvider(object):
"""Generic view provider to assign an icon."""
def __init__(self, vobj, icon):
self.icon = icon
self.attach(vobj)
self.editModule = None
self.editCallback = None
vobj.Proxy = self
def attach(self, vobj):
self.vobj = vobj
self.obj = vobj.Object
def __getstate__(self):
attrs = {"icon": self.icon}
if hasattr(self, "editModule"):
attrs["editModule"] = self.editModule
attrs["editCallback"] = self.editCallback
return attrs
def __setstate__(self, state):
self.icon = state["icon"]
if state.get("editModule", None):
self.editModule = state["editModule"]
self.editCallback = state["editCallback"]
def getIcon(self):
return ":/icons/Path_{}.svg".format(self.icon)
def onEdit(self, callback):
self.editModule = callback.__module__
self.editCallback = callback.__name__
def _onEditCallback(self, edit):
if hasattr(self, "editModule"):
mod = importlib.import_module(self.editModule)
callback = getattr(mod, self.editCallback)
callback(self.obj, self.vobj, edit)
def setEdit(self, vobj=None, mode=0):
if 0 == mode:
self._onEditCallback(True)
return False
def unsetEdit(self, arg1, arg2):
self._onEditCallback(False)
def setupContextMenu(self, vobj, menu):
Path.Log.track()
from PySide import QtGui
edit = translate("Path", "Edit")
action = QtGui.QAction(edit, menu)
action.triggered.connect(self.setEdit)
menu.addAction(action)
_factory = {}
def Attach(vobj, name):
"""Attach(vobj, name) ... attach the appropriate view provider to the view object.
If no view provider was registered for the given name a default IconViewProvider is created."""
Path.Log.track(vobj.Object.Label, name)
global _factory
for key, value in PathUtil.keyValueIter(_factory):
if key == name:
return value(vobj, name)
Path.Log.track(vobj.Object.Label, name, "PathIconViewProvider")
return ViewProvider(vobj, name)
def RegisterViewProvider(name, provider):
"""RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name
an instance of provider is used instead."""
Path.Log.track(name)
global _factory
_factory[name] = provider

View File

@@ -24,11 +24,10 @@ from PySide import QtCore
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCAD
import Path
import Path.Base.SetupSheet as PathSetupSheet
import Path.Base.Util as PathUtil
from Path.Post.Processor import PostProcessor
import Path.Tool.Controller as PathToolController
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathStock as PathStock
import json
import time
@@ -83,9 +82,9 @@ def createResourceClone(obj, orig, name, icon):
clone.addProperty("App::PropertyString", "PathResource")
clone.PathResource = name
if clone.ViewObject:
import PathScripts.PathIconViewProvider
import Path.Base.Gui.IconViewProvider
PathScripts.PathIconViewProvider.Attach(clone.ViewObject, icon)
Path.Base.Gui.IconViewProvider.Attach(clone.ViewObject, icon)
clone.ViewObject.Visibility = False
clone.ViewObject.Transparency = 80
obj.Document.recompute() # necessary to create the clone shape
@@ -223,16 +222,16 @@ class ObjectJob:
for n in self.propertyEnumerations():
setattr(obj, n[0], n[1])
obj.PostProcessorOutputFile = PathPreferences.defaultOutputFile()
obj.PostProcessor = postProcessors = PathPreferences.allEnabledPostProcessors()
defaultPostProcessor = PathPreferences.defaultPostProcessor()
obj.PostProcessorOutputFile = Path.Preferences.defaultOutputFile()
obj.PostProcessor = postProcessors = Path.Preferences.allEnabledPostProcessors()
defaultPostProcessor = Path.Preferences.defaultPostProcessor()
# Check to see if default post processor hasn't been 'lost' (This can happen when Macro dir has changed)
if defaultPostProcessor in postProcessors:
obj.PostProcessor = defaultPostProcessor
else:
obj.PostProcessor = postProcessors[0]
obj.PostProcessorArgs = PathPreferences.defaultPostProcessorArgs()
obj.GeometryTolerance = PathPreferences.defaultGeometryTolerance()
obj.PostProcessorArgs = Path.Preferences.defaultPostProcessorArgs()
obj.GeometryTolerance = Path.Preferences.defaultGeometryTolerance()
self.setupOperations(obj)
self.setupSetupSheet(obj)
@@ -307,9 +306,9 @@ class ObjectJob:
)
obj.SetupSheet = PathSetupSheet.Create()
if obj.SetupSheet.ViewObject:
import PathScripts.PathIconViewProvider
import Path.Base.Gui.IconViewProvider
PathScripts.PathIconViewProvider.Attach(
Path.Base.Gui.IconViewProvider.Attach(
obj.SetupSheet.ViewObject, "SetupSheet"
)
obj.SetupSheet.Label = "SetupSheet"
@@ -383,7 +382,7 @@ class ObjectJob:
def setupStock(self, obj):
"""setupStock(obj)... setup the Stock for the Job object."""
if not obj.Stock:
stockTemplate = PathPreferences.defaultStockTemplate()
stockTemplate = Path.Preferences.defaultStockTemplate()
if stockTemplate:
obj.Stock = PathStock.CreateFromTemplate(obj, json.loads(stockTemplate))
if not obj.Stock:

View File

@@ -28,7 +28,6 @@ import Path
import Path.Base.Util as PathUtil
import PathScripts.PathJob as PathJob
import PathScripts.PathJobDlg as PathJobDlg
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathStock as PathStock
import json
import os
@@ -136,7 +135,7 @@ class CommandJobTemplateExport:
foo = QtGui.QFileDialog.getSaveFileName(
QtGui.QApplication.activeWindow(),
"Path - Job Template",
PathPreferences.filePath(),
Path.Preferences.filePath(),
"job_*.json",
)[0]
if foo:

View File

@@ -27,7 +27,6 @@ import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathScripts.PathJob as PathJob
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathStock as PathStock
import glob
import os
@@ -223,7 +222,7 @@ class JobCreate:
def setupTemplate(self):
templateFiles = []
for path in PathPreferences.searchPaths():
for path in Path.Preferences.searchPaths():
cleanPaths = [
f.replace("\\", "/") for f in self.templateFilesIn(path)
] # Standardize slashes used across os platforms
@@ -240,7 +239,7 @@ class JobCreate:
name = basename + " (%s)" % i
Path.Log.track(name, tFile)
template[name] = tFile
selectTemplate = PathPreferences.defaultJobTemplate()
selectTemplate = Path.Preferences.defaultJobTemplate()
index = 0
self.dialog.jobTemplate.addItem("<none>", "")
for name in sorted(template.keys()):

View File

@@ -28,15 +28,14 @@ from pivy import coin
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Gui.SetupSheetGui as PathSetupSheetGui
import Path.Base.Util as PathUtil
import Path.GuiInit as PathGuiInit
import Path.Tool.Gui.Bit as PathToolBitGui
import Path.Tool.Gui.Controller as PathToolControllerGui
import PathScripts.PathGuiInit as PathGuiInit
import PathScripts.PathJob as PathJob
import PathScripts.PathJobCmd as PathJobCmd
import PathScripts.PathJobDlg as PathJobDlg
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathSetupSheetGui as PathSetupSheetGui
import PathScripts.PathStock as PathStock
import PathScripts.PathUtils as PathUtils
import json
@@ -635,7 +634,7 @@ class TaskPanel:
self.form.toolControllerList.resizeColumnsToContents()
currentPostProcessor = self.obj.PostProcessor
postProcessors = PathPreferences.allEnabledPostProcessors(
postProcessors = Path.Preferences.allEnabledPostProcessors(
["", currentPostProcessor]
)
for post in postProcessors:
@@ -999,7 +998,7 @@ class TaskPanel:
tools = PathToolBitGui.LoadTools()
curLib = PathPreferences.lastFileToolLibrary()
curLib = Path.Preferences.lastFileToolLibrary()
library = None
if curLib is not None:

View File

@@ -1,356 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2014 Yorik van Havre <yorik@uncreated.net> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
import glob
import os
from PySide.QtGui import QMessageBox
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
translate = FreeCAD.Qt.translate
DefaultFilePath = "DefaultFilePath"
DefaultJobTemplate = "DefaultJobTemplate"
DefaultStockTemplate = "DefaultStockTemplate"
DefaultTaskPanelLayout = "DefaultTaskPanelLayout"
PostProcessorDefault = "PostProcessorDefault"
PostProcessorDefaultArgs = "PostProcessorDefaultArgs"
PostProcessorBlacklist = "PostProcessorBlacklist"
PostProcessorOutputFile = "PostProcessorOutputFile"
PostProcessorOutputPolicy = "PostProcessorOutputPolicy"
LastPathToolBit = "LastPathToolBit"
LastPathToolLibrary = "LastPathToolLibrary"
LastPathToolShape = "LastPathToolShape"
LastPathToolTable = "LastPathToolTable"
LastFileToolBit = "LastFileToolBit"
LastFileToolLibrary = "LastFileToolLibrary"
LastFileToolShape = "LastFileToolShape"
UseAbsoluteToolPaths = "UseAbsoluteToolPaths"
# OpenLastLibrary = "OpenLastLibrary"
# Linear tolerance to use when generating Paths, eg when tessellating geometry
GeometryTolerance = "GeometryTolerance"
LibAreaCurveAccuracy = "LibAreaCurveAccuracy"
WarningSuppressRapidSpeeds = "WarningSuppressRapidSpeeds"
WarningSuppressAllSpeeds = "WarningSuppressAllSpeeds"
WarningSuppressSelectionMode = "WarningSuppressSelectionMode"
WarningSuppressOpenCamLib = "WarningSuppressOpenCamLib"
WarningSuppressVelocity = "WarningSuppressVelocity"
EnableExperimentalFeatures = "EnableExperimentalFeatures"
EnableAdvancedOCLFeatures = "EnableAdvancedOCLFeatures"
def preferences():
return FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Path")
def pathPostSourcePath():
return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Path/Post/")
def pathDefaultToolsPath(sub=None):
if sub:
return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Tools/", sub)
return os.path.join(FreeCAD.getHomePath(), "Mod/Path/Tools/")
def allAvailablePostProcessors():
allposts = []
for path in searchPathsPost():
posts = [
str(os.path.split(os.path.splitext(p)[0])[1][:-5])
for p in glob.glob(path + "/*_post.py")
]
allposts.extend(posts)
allposts.sort()
return allposts
def allEnabledPostProcessors(include=None):
blacklist = postProcessorBlacklist()
enabled = [
processor
for processor in allAvailablePostProcessors()
if processor not in blacklist
]
if include:
postlist = list(set(include + enabled))
postlist.sort()
return postlist
return enabled
def defaultPostProcessor():
pref = preferences()
return pref.GetString(PostProcessorDefault, "")
def defaultPostProcessorArgs():
pref = preferences()
return pref.GetString(PostProcessorDefaultArgs, "")
def defaultGeometryTolerance():
return preferences().GetFloat(GeometryTolerance, 0.01)
def defaultLibAreaCurveAccuracy():
return preferences().GetFloat(LibAreaCurveAccuracy, 0.01)
def defaultFilePath():
return preferences().GetString(DefaultFilePath)
def filePath():
path = defaultFilePath()
if not path:
path = macroFilePath()
return path
def macroFilePath():
grp = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
return grp.GetString("MacroPath", FreeCAD.getUserMacroDir())
def searchPaths():
paths = []
p = defaultFilePath()
if p:
paths.append(p)
paths.append(macroFilePath())
return paths
def searchPathsPost():
paths = []
p = defaultFilePath()
if p:
paths.append(p)
paths.append(macroFilePath())
paths.append(os.path.join(pathPostSourcePath(), "scripts/"))
paths.append(pathPostSourcePath())
return paths
def searchPathsTool(sub):
paths = []
paths.append(os.path.join(FreeCAD.getHomePath(), "Mod", "Path", "Tools", sub))
return paths
def toolsStoreAbsolutePaths():
return preferences().GetBool(UseAbsoluteToolPaths, False)
# def toolsOpenLastLibrary():
# return preferences().GetBool(OpenLastLibrary, False)
def setToolsSettings(relative):
pref = preferences()
pref.SetBool(UseAbsoluteToolPaths, relative)
# pref.SetBool(OpenLastLibrary, lastlibrary)
def defaultJobTemplate():
template = preferences().GetString(DefaultJobTemplate)
if "xml" not in template:
return template
return ""
def setJobDefaults(fileName, jobTemplate, geometryTolerance, curveAccuracy):
Path.Log.track(
"(%s='%s', %s, %s, %s)"
% (DefaultFilePath, fileName, jobTemplate, geometryTolerance, curveAccuracy)
)
pref = preferences()
pref.SetString(DefaultFilePath, fileName)
pref.SetString(DefaultJobTemplate, jobTemplate)
pref.SetFloat(GeometryTolerance, geometryTolerance)
pref.SetFloat(LibAreaCurveAccuracy, curveAccuracy)
def postProcessorBlacklist():
pref = preferences()
blacklist = pref.GetString(PostProcessorBlacklist, "")
if not blacklist:
return []
return eval(blacklist)
def setPostProcessorDefaults(processor, args, blacklist):
pref = preferences()
pref.SetString(PostProcessorDefault, processor)
pref.SetString(PostProcessorDefaultArgs, args)
pref.SetString(PostProcessorBlacklist, "%s" % (blacklist))
def setOutputFileDefaults(fileName, policy):
pref = preferences()
pref.SetString(PostProcessorOutputFile, fileName)
pref.SetString(PostProcessorOutputPolicy, policy)
def defaultOutputFile():
pref = preferences()
return pref.GetString(PostProcessorOutputFile, "")
def defaultOutputPolicy():
pref = preferences()
return pref.GetString(PostProcessorOutputPolicy, "")
def defaultStockTemplate():
return preferences().GetString(DefaultStockTemplate, "")
def setDefaultStockTemplate(template):
preferences().SetString(DefaultStockTemplate, template)
def defaultTaskPanelLayout():
return preferences().GetInt(DefaultTaskPanelLayout, 0)
def setDefaultTaskPanelLayout(style):
preferences().SetInt(DefaultTaskPanelLayout, style)
def advancedOCLFeaturesEnabled():
return preferences().GetBool(EnableAdvancedOCLFeatures, False)
def experimentalFeaturesEnabled():
return preferences().GetBool(EnableExperimentalFeatures, False)
def suppressAllSpeedsWarning():
return preferences().GetBool(WarningSuppressAllSpeeds, True)
def suppressRapidSpeedsWarning(user=True):
return (user and suppressAllSpeedsWarning()) or preferences().GetBool(
WarningSuppressRapidSpeeds, True
)
def suppressSelectionModeWarning():
return preferences().GetBool(WarningSuppressSelectionMode, True)
def suppressOpenCamLibWarning():
return preferences().GetBool(WarningSuppressOpenCamLib, True)
def suppressVelocity():
return preferences().GetBool(WarningSuppressVelocity, False)
def setPreferencesAdvanced(
ocl, warnSpeeds, warnRapids, warnModes, warnOCL, warnVelocity
):
preferences().SetBool(EnableAdvancedOCLFeatures, ocl)
preferences().SetBool(WarningSuppressAllSpeeds, warnSpeeds)
preferences().SetBool(WarningSuppressRapidSpeeds, warnRapids)
preferences().SetBool(WarningSuppressSelectionMode, warnModes)
preferences().SetBool(WarningSuppressOpenCamLib, warnOCL)
preferences().SetBool(WarningSuppressVelocity, warnVelocity)
def lastFileToolLibrary():
filename = preferences().GetString(LastFileToolLibrary)
if filename.endswith(".fctl") and os.path.isfile(filename):
return filename
libpath = preferences().GetString(
LastPathToolLibrary, pathDefaultToolsPath("Library")
)
libFiles = [f for f in glob.glob(libpath + "/*.fctl")]
libFiles.sort()
if len(libFiles) >= 1:
filename = libFiles[0]
setLastFileToolLibrary(filename)
Path.Log.track(filename)
return filename
else:
return None
def setLastFileToolLibrary(path):
Path.Log.track(path)
if os.path.isfile(path): # keep the path and file in sync
preferences().SetString(LastPathToolLibrary, os.path.split(path)[0])
return preferences().SetString(LastFileToolLibrary, path)
def lastPathToolBit():
return preferences().GetString(LastPathToolBit, pathDefaultToolsPath("Bit"))
def setLastPathToolBit(path):
return preferences().SetString(LastPathToolBit, path)
def lastPathToolLibrary():
Path.Log.track()
return preferences().GetString(LastPathToolLibrary, pathDefaultToolsPath("Library"))
def setLastPathToolLibrary(path):
Path.Log.track(path)
curLib = lastFileToolLibrary()
Path.Log.debug("curLib: {}".format(curLib))
if curLib and os.path.split(curLib)[0] != path:
setLastFileToolLibrary("") # a path is known but not specific file
return preferences().SetString(LastPathToolLibrary, path)
def lastPathToolShape():
return preferences().GetString(LastPathToolShape, pathDefaultToolsPath("Shape"))
def setLastPathToolShape(path):
return preferences().SetString(LastPathToolShape, path)
def lastPathToolTable():
return preferences().GetString(LastPathToolTable, "")
def setLastPathToolTable(table):
return preferences().SetString(LastPathToolTable, table)

View File

@@ -1,79 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2021 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCADGui
import Path
import PathScripts.PathPreferences as PathPreferences
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class AdvancedPreferencesPage:
def __init__(self, parent=None):
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.WarningSuppressVelocity.isChecked(),
)
def loadSettings(self):
Path.Log.track()
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.WarningSuppressVelocity.setChecked(PathPreferences.suppressVelocity())
self.updateSelection()
def updateSelection(self, state=None):
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

@@ -23,7 +23,6 @@
import FreeCAD
import Path
import Path.Post.Processor as PostProcessor
import PathScripts.PathPreferences as PathPreferences
import PathScripts.PathStock as PathStock
import json
@@ -52,7 +51,7 @@ class JobPreferencesPage:
jobTemplate = self.form.leDefaultJobTemplate.text()
geometryTolerance = Units.Quantity(self.form.geometryTolerance.text())
curveAccuracy = Units.Quantity(self.form.curveAccuracy.text())
PathPreferences.setJobDefaults(
Path.Preferences.setJobDefaults(
filePath, jobTemplate, geometryTolerance, curveAccuracy
)
@@ -66,11 +65,11 @@ class JobPreferencesPage:
item = self.form.postProcessorList.item(i)
if item.checkState() == QtCore.Qt.CheckState.Unchecked:
blacklist.append(item.text())
PathPreferences.setPostProcessorDefaults(processor, args, blacklist)
Path.Preferences.setPostProcessorDefaults(processor, args, blacklist)
path = str(self.form.leOutputFile.text())
policy = str(self.form.cboOutputPolicy.currentText())
PathPreferences.setOutputFileDefaults(path, policy)
Path.Preferences.setOutputFileDefaults(path, policy)
self.saveStockSettings()
self.saveToolsSettings()
@@ -141,12 +140,12 @@ class JobPreferencesPage:
attrs["posZ"] = FreeCAD.Units.Quantity(
self.form.stockPositionZ.text()
).Value
PathPreferences.setDefaultStockTemplate(json.dumps(attrs))
Path.Preferences.setDefaultStockTemplate(json.dumps(attrs))
else:
PathPreferences.setDefaultStockTemplate("")
Path.Preferences.setDefaultStockTemplate("")
def saveToolsSettings(self):
PathPreferences.setToolsSettings(
Path.Preferences.setToolsSettings(
self.form.toolsAbsolutePaths.isChecked()
)
@@ -181,11 +180,11 @@ class JobPreferencesPage:
)
def loadSettings(self):
self.form.leDefaultFilePath.setText(PathPreferences.defaultFilePath())
self.form.leDefaultJobTemplate.setText(PathPreferences.defaultJobTemplate())
self.form.leDefaultFilePath.setText(Path.Preferences.defaultFilePath())
self.form.leDefaultJobTemplate.setText(Path.Preferences.defaultJobTemplate())
blacklist = PathPreferences.postProcessorBlacklist()
for processor in PathPreferences.allAvailablePostProcessors():
blacklist = Path.Preferences.postProcessorBlacklist()
for processor in Path.Preferences.allAvailablePostProcessors():
item = QtGui.QListWidgetItem(processor)
if processor in blacklist:
item.setCheckState(QtCore.Qt.CheckState.Unchecked)
@@ -198,26 +197,26 @@ class JobPreferencesPage:
)
self.form.postProcessorList.addItem(item)
self.verifyAndUpdateDefaultPostProcessorWith(
PathPreferences.defaultPostProcessor()
Path.Preferences.defaultPostProcessor()
)
self.form.defaultPostProcessorArgs.setText(
PathPreferences.defaultPostProcessorArgs()
Path.Preferences.defaultPostProcessorArgs()
)
geomTol = Units.Quantity(
PathPreferences.defaultGeometryTolerance(), Units.Length
Path.Preferences.defaultGeometryTolerance(), Units.Length
)
self.form.geometryTolerance.setText(geomTol.UserString)
self.form.curveAccuracy.setText(
Units.Quantity(
PathPreferences.defaultLibAreaCurveAccuracy(), Units.Length
Path.Preferences.defaultLibAreaCurveAccuracy(), Units.Length
).UserString
)
self.form.leOutputFile.setText(PathPreferences.defaultOutputFile())
self.form.leOutputFile.setText(Path.Preferences.defaultOutputFile())
self.selectComboEntry(
self.form.cboOutputPolicy, PathPreferences.defaultOutputPolicy()
self.form.cboOutputPolicy, Path.Preferences.defaultOutputPolicy()
)
self.form.tbDefaultFilePath.clicked.connect(self.browseDefaultFilePath)
@@ -235,7 +234,7 @@ class JobPreferencesPage:
self.loadToolSettings()
def loadStockSettings(self):
stock = PathPreferences.defaultStockTemplate()
stock = Path.Preferences.defaultStockTemplate()
index = -1
if stock:
attrs = json.loads(stock)
@@ -328,7 +327,7 @@ class JobPreferencesPage:
def loadToolSettings(self):
self.form.toolsAbsolutePaths.setChecked(
PathPreferences.toolsStoreAbsolutePaths()
Path.Preferences.toolsStoreAbsolutePaths()
)
def getPostProcessor(self, name):
@@ -370,7 +369,7 @@ class JobPreferencesPage:
def bestGuessForFilePath(self):
path = self.form.leDefaultFilePath.text()
if not path:
path = PathPreferences.filePath()
path = Path.Preferences.filePath()
return path
def browseDefaultJobTemplate(self):

View File

@@ -1,463 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
from PySide import QtCore, QtGui
import FreeCAD
import FreeCADGui
import Path
import Path.Base.PropertyBag as PathPropertyBag
import Path.Base.Util as PathUtil
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathPropertyEditor as PathPropertyEditor
import re
__title__ = "Property Bag Editor"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for a PropertyBag"
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
translate = FreeCAD.Qt.translate
class ViewProvider(object):
"""ViewProvider for a PropertyBag.
It's sole job is to provide an icon and invoke the TaskPanel on edit."""
def __init__(self, vobj, name):
Path.Log.track(name)
vobj.Proxy = self
self.icon = name
# mode = 2
self.obj = None
self.vobj = None
def attach(self, vobj):
Path.Log.track()
self.vobj = vobj
self.obj = vobj.Object
def getIcon(self):
return ":/icons/Path-SetupSheet.svg"
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def getDisplayMode(self, mode):
return "Default"
def setEdit(self, vobj, mode=0):
Path.Log.track()
taskPanel = TaskPanel(vobj)
FreeCADGui.Control.closeDialog()
FreeCADGui.Control.showDialog(taskPanel)
taskPanel.setupUi()
return True
def unsetEdit(self, vobj, mode):
FreeCADGui.Control.closeDialog()
return
def claimChildren(self):
return []
def doubleClicked(self, vobj):
self.setEdit(vobj)
class Delegate(QtGui.QStyledItemDelegate):
RoleObject = QtCore.Qt.UserRole + 1
RoleProperty = QtCore.Qt.UserRole + 2
RoleEditor = QtCore.Qt.UserRole + 3
# def paint(self, painter, option, index):
# #Path.Log.track(index.column(), type(option))
def createEditor(self, parent, option, index):
editor = PathPropertyEditor.Editor(
index.data(self.RoleObject), index.data(self.RoleProperty)
)
index.model().setData(index, editor, self.RoleEditor)
return editor.widget(parent)
def setEditorData(self, widget, index):
Path.Log.track(index.row(), index.column())
index.data(self.RoleEditor).setEditorData(widget)
def setModelData(self, widget, model, index):
Path.Log.track(index.row(), index.column())
editor = index.data(self.RoleEditor)
editor.setModelData(widget)
index.model().setData(index, editor.displayString(), QtCore.Qt.DisplayRole)
def updateEditorGeometry(self, widget, option, index):
widget.setGeometry(option.rect)
class PropertyCreate(object):
def __init__(self, obj, grp, typ, another):
self.obj = obj
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyCreate.ui")
obj.Proxy.refreshCustomPropertyGroups()
for g in sorted(obj.CustomPropertyGroups):
self.form.propertyGroup.addItem(g)
if grp:
self.form.propertyGroup.setCurrentText(grp)
for t in sorted(PathPropertyBag.SupportedPropertyType):
self.form.propertyType.addItem(t)
if PathPropertyBag.SupportedPropertyType[t] == typ:
typ = t
if typ:
self.form.propertyType.setCurrentText(typ)
else:
self.form.propertyType.setCurrentText("String")
self.form.createAnother.setChecked(another)
self.form.propertyGroup.currentTextChanged.connect(self.updateUI)
self.form.propertyGroup.currentIndexChanged.connect(self.updateUI)
self.form.propertyName.textChanged.connect(self.updateUI)
self.form.propertyType.currentIndexChanged.connect(self.updateUI)
self.form.propertyEnum.textChanged.connect(self.updateUI)
def updateUI(self):
typeSet = True
if self.propertyIsEnumeration():
self.form.labelEnum.setEnabled(True)
self.form.propertyEnum.setEnabled(True)
typeSet = self.form.propertyEnum.text().strip() != ""
else:
self.form.labelEnum.setEnabled(False)
self.form.propertyEnum.setEnabled(False)
if self.form.propertyEnum.text().strip():
self.form.propertyEnum.setText("")
ok = self.form.buttonBox.button(QtGui.QDialogButtonBox.Ok)
if not re.match("^[A-Za-z0-9_]*$", self.form.propertyName.text()):
typeSet = False
if typeSet and self.propertyGroup():
ok.setEnabled(True)
else:
ok.setEnabled(False)
def propertyName(self):
return self.form.propertyName.text().strip()
def propertyGroup(self):
return self.form.propertyGroup.currentText().strip()
def propertyType(self):
return PathPropertyBag.SupportedPropertyType[
self.form.propertyType.currentText()
].strip()
def propertyInfo(self):
return self.form.propertyInfo.toPlainText().strip()
def createAnother(self):
return self.form.createAnother.isChecked()
def propertyEnumerations(self):
return [s.strip() for s in self.form.propertyEnum.text().strip().split(",")]
def propertyIsEnumeration(self):
return self.propertyType() == "App::PropertyEnumeration"
def exec_(self, name):
if name:
# property exists - this is an edit operation
self.form.propertyName.setText(name)
if self.propertyIsEnumeration():
self.form.propertyEnum.setText(
",".join(self.obj.getEnumerationsOfProperty(name))
)
self.form.propertyInfo.setText(self.obj.getDocumentationOfProperty(name))
self.form.labelName.setEnabled(False)
self.form.propertyName.setEnabled(False)
self.form.labelType.setEnabled(False)
self.form.propertyType.setEnabled(False)
self.form.createAnother.setEnabled(False)
else:
self.form.propertyName.setText("")
self.form.propertyInfo.setText("")
self.form.propertyEnum.setText("")
# self.form.propertyName.setFocus()
self.updateUI()
return self.form.exec_()
class TaskPanel(object):
ColumnName = 0
# ColumnType = 1
ColumnVal = 1
# TableHeaders = ['Property', 'Type', 'Value']
TableHeaders = ["Property", "Value"]
def __init__(self, vobj):
self.obj = vobj.Object
self.props = sorted(self.obj.Proxy.getCustomProperties())
self.form = FreeCADGui.PySideUic.loadUi(":panels/PropertyBag.ui")
# initialized later
self.model = None
self.delegate = None
FreeCAD.ActiveDocument.openTransaction("Edit PropertyBag")
def updateData(self, topLeft, bottomRight):
pass
def _setupProperty(self, i, name):
typ = PathPropertyBag.getPropertyTypeName(self.obj.getTypeIdOfProperty(name))
val = PathUtil.getPropertyValueString(self.obj, name)
info = self.obj.getDocumentationOfProperty(name)
self.model.setData(
self.model.index(i, self.ColumnName), name, QtCore.Qt.EditRole
)
# self.model.setData(self.model.index(i, self.ColumnType), typ, QtCore.Qt.EditRole)
self.model.setData(
self.model.index(i, self.ColumnVal), self.obj, Delegate.RoleObject
)
self.model.setData(
self.model.index(i, self.ColumnVal), name, Delegate.RoleProperty
)
self.model.setData(
self.model.index(i, self.ColumnVal), val, QtCore.Qt.DisplayRole
)
self.model.setData(
self.model.index(i, self.ColumnName), typ, QtCore.Qt.ToolTipRole
)
# self.model.setData(self.model.index(i, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(
self.model.index(i, self.ColumnVal), info, QtCore.Qt.ToolTipRole
)
self.model.item(i, self.ColumnName).setEditable(False)
# self.model.item(i, self.ColumnType).setEditable(False)
def setupUi(self):
Path.Log.track()
self.delegate = Delegate(self.form)
self.model = QtGui.QStandardItemModel(
len(self.props), len(self.TableHeaders), self.form
)
self.model.setHorizontalHeaderLabels(self.TableHeaders)
for i, name in enumerate(self.props):
self._setupProperty(i, name)
self.form.table.setModel(self.model)
self.form.table.setItemDelegateForColumn(self.ColumnVal, self.delegate)
self.form.table.resizeColumnsToContents()
self.model.dataChanged.connect(self.updateData)
self.form.table.selectionModel().selectionChanged.connect(self.propertySelected)
self.form.add.clicked.connect(self.propertyAdd)
self.form.remove.clicked.connect(self.propertyRemove)
self.form.modify.clicked.connect(self.propertyModify)
self.form.table.doubleClicked.connect(self.propertyModifyIndex)
self.propertySelected([])
def accept(self):
FreeCAD.ActiveDocument.commitTransaction()
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def reject(self):
FreeCAD.ActiveDocument.abortTransaction()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def propertySelected(self, selection):
Path.Log.track()
if selection:
self.form.modify.setEnabled(True)
self.form.remove.setEnabled(True)
else:
self.form.modify.setEnabled(False)
self.form.remove.setEnabled(False)
def addCustomProperty(self, obj, dialog):
name = dialog.propertyName()
typ = dialog.propertyType()
grp = dialog.propertyGroup()
info = dialog.propertyInfo()
propname = self.obj.Proxy.addCustomProperty(typ, name, grp, info)
if dialog.propertyIsEnumeration():
setattr(self.obj, name, dialog.propertyEnumerations())
return (propname, info)
def propertyAdd(self):
Path.Log.track()
more = False
grp = None
typ = None
while True:
dialog = PropertyCreate(self.obj, grp, typ, more)
if dialog.exec_(None):
# if we block signals the view doesn't get updated, surprise, surprise
# self.model.blockSignals(True)
name, info = self.addCustomProperty(self.obj, dialog)
index = 0
for i in range(self.model.rowCount()):
index = i
if (
self.model.item(i, self.ColumnName).data(QtCore.Qt.EditRole)
> dialog.propertyName()
):
break
self.model.insertRows(index, 1)
self._setupProperty(index, name)
self.form.table.selectionModel().setCurrentIndex(
self.model.index(index, 0), QtCore.QItemSelectionModel.Rows
)
# self.model.blockSignals(False)
more = dialog.createAnother()
else:
more = False
if not more:
break
def propertyModifyIndex(self, index):
Path.Log.track(index.row(), index.column())
row = index.row()
obj = self.model.item(row, self.ColumnVal).data(Delegate.RoleObject)
nam = self.model.item(row, self.ColumnVal).data(Delegate.RoleProperty)
grp = obj.getGroupOfProperty(nam)
typ = obj.getTypeIdOfProperty(nam)
dialog = PropertyCreate(self.obj, grp, typ, False)
if dialog.exec_(nam):
val = getattr(obj, nam)
obj.removeProperty(nam)
name, info = self.addCustomProperty(self.obj, dialog)
try:
setattr(obj, nam, val)
except Exception:
# this can happen if the old enumeration value doesn't exist anymore
pass
newVal = PathUtil.getPropertyValueString(obj, nam)
self.model.setData(
self.model.index(row, self.ColumnVal), newVal, QtCore.Qt.DisplayRole
)
# self.model.setData(self.model.index(row, self.ColumnType), info, QtCore.Qt.ToolTipRole)
self.model.setData(
self.model.index(row, self.ColumnVal), info, QtCore.Qt.ToolTipRole
)
def propertyModify(self):
Path.Log.track()
rows = []
for index in self.form.table.selectionModel().selectedIndexes():
row = index.row()
if row in rows:
continue
rows.append(row)
self.propertyModifyIndex(index)
def propertyRemove(self):
Path.Log.track()
# first find all rows which need to be removed
rows = []
for index in self.form.table.selectionModel().selectedIndexes():
if not index.row() in rows:
rows.append(index.row())
# then remove them in reverse order so the indexes of the remaining rows
# to delete are still valid
for row in reversed(sorted(rows)):
self.obj.removeProperty(self.model.item(row).data(QtCore.Qt.EditRole))
self.model.removeRow(row)
def Create(name="PropertyBag"):
"""Create(name = 'PropertyBag') ... creates a new setup sheet"""
FreeCAD.ActiveDocument.openTransaction("Create PropertyBag")
pcont = PathPropertyBag.Create(name)
PathIconViewProvider.Attach(pcont.ViewObject, name)
return pcont
PathIconViewProvider.RegisterViewProvider("PropertyBag", ViewProvider)
class PropertyBagCreateCommand(object):
"""Command to create a property container object"""
def __init__(self):
pass
def GetResources(self):
return {
"MenuText": translate("Path_PropertyBag", "PropertyBag"),
"ToolTip": translate(
"Path_PropertyBag",
"Creates an object which can be used to store reference properties.",
),
}
def IsActive(self):
return not FreeCAD.ActiveDocument is None
def Activated(self):
sel = FreeCADGui.Selection.getSelectionEx()
obj = Create()
body = None
if sel:
if "PartDesign::Body" == sel[0].Object.TypeId:
body = sel[0].Object
elif hasattr(sel[0].Object, "getParentGeoFeatureGroup"):
body = sel[0].Object.getParentGeoFeatureGroup()
if body:
obj.Label = "Attributes"
group = body.Group
group.append(obj)
body.Group = group
if FreeCAD.GuiUp:
FreeCADGui.addCommand("Path_PropertyBag", PropertyBagCreateCommand())
FreeCAD.Console.PrintLog("Loading PathPropertyBagGui ... done\n")

View File

@@ -1,239 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
from PySide import QtCore, QtGui
__title__ = "Path Property Editor"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for Properties"
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class _PropertyEditor(object):
"""Base class of all property editors - just outlines the TableView delegate interface."""
def __init__(self, obj, prop):
self.obj = obj
self.prop = prop
def widget(self, parent):
"""widget(parent) ... called by the delegate to get a new editor widget.
Must be implemented by subclasses and return the widget."""
pass
def setEditorData(self, widget):
"""setEditorData(widget) ... called by the delegate to initialize the editor.
The widget is the object returned by widget().
Must be implemented by subclasses."""
pass
def setModelData(self, widget):
"""setModelData(widget) ... called by the delegate to store new values.
Must be implemented by subclasses."""
pass
def propertyValue(self):
return self.obj.getPropertyByName(self.prop)
def setProperty(self, value):
setattr(self.obj, self.prop, value)
def displayString(self):
return self.propertyValue()
class _PropertyEditorBool(_PropertyEditor):
"""Editor for boolean values - uses a combo box."""
def widget(self, parent):
return QtGui.QComboBox(parent)
def setEditorData(self, widget):
widget.clear()
widget.addItems([str(False), str(True)])
index = 1 if self.propertyValue() else 0
widget.setCurrentIndex(index)
def setModelData(self, widget):
self.setProperty(widget.currentText() == str(True))
class _PropertyEditorString(_PropertyEditor):
"""Editor for string values - uses a line edit."""
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
text = "" if self.propertyValue() is None else self.propertyValue()
widget.setText(text)
def setModelData(self, widget):
self.setProperty(widget.text())
class _PropertyEditorQuantity(_PropertyEditor):
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
quantity = self.propertyValue()
if quantity is None:
quantity = self.defaultQuantity()
widget.setText(quantity.getUserPreferred()[0])
def defaultQuantity(self):
pass
def setModelData(self, widget):
self.setProperty(FreeCAD.Units.Quantity(widget.text()))
def displayString(self):
if self.propertyValue() is None:
return ""
return self.propertyValue().getUserPreferred()[0]
class _PropertyEditorAngle(_PropertyEditorQuantity):
"""Editor for angle values - uses a line edit"""
def defaultQuantity(self):
return FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle)
class _PropertyEditorLength(_PropertyEditorQuantity):
"""Editor for length values - uses a line edit."""
def defaultQuantity(self):
return FreeCAD.Units.Quantity(0, FreeCAD.Units.Length)
class _PropertyEditorPercent(_PropertyEditor):
"""Editor for percent values - uses a spin box."""
def widget(self, parent):
return QtGui.QSpinBox(parent)
def setEditorData(self, widget):
widget.setRange(0, 100)
value = self.propertyValue()
if value is None:
value = 0
widget.setValue(value)
def setModelData(self, widget):
self.setProperty(widget.value())
class _PropertyEditorInteger(_PropertyEditor):
"""Editor for integer values - uses a spin box."""
def widget(self, parent):
return QtGui.QSpinBox(parent)
def setEditorData(self, widget):
value = self.propertyValue()
if value is None:
value = 0
widget.setValue(value)
def setModelData(self, widget):
self.setProperty(widget.value())
class _PropertyEditorFloat(_PropertyEditor):
"""Editor for float values - uses a double spin box."""
def widget(self, parent):
return QtGui.QDoubleSpinBox(parent)
def setEditorData(self, widget):
value = self.propertyValue()
if value is None:
value = 0.0
widget.setValue(value)
def setModelData(self, widget):
self.setProperty(widget.value())
class _PropertyEditorFile(_PropertyEditor):
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
text = "" if self.propertyValue() is None else self.propertyValue()
widget.setText(text)
def setModelData(self, widget):
self.setProperty(widget.text())
class _PropertyEditorEnumeration(_PropertyEditor):
def widget(self, parent):
return QtGui.QComboBox(parent)
def setEditorData(self, widget):
widget.clear()
widget.addItems(self.obj.getEnumerationsOfProperty(self.prop))
widget.setCurrentText(self.propertyValue())
def setModelData(self, widget):
self.setProperty(widget.currentText())
_EditorFactory = {
"App::PropertyAngle": _PropertyEditorAngle,
"App::PropertyBool": _PropertyEditorBool,
"App::PropertyDistance": _PropertyEditorLength,
"App::PropertyEnumeration": _PropertyEditorEnumeration,
#'App::PropertyFile' : _PropertyEditorFile,
"App::PropertyFloat": _PropertyEditorFloat,
"App::PropertyInteger": _PropertyEditorInteger,
"App::PropertyLength": _PropertyEditorLength,
"App::PropertyPercent": _PropertyEditorPercent,
"App::PropertyString": _PropertyEditorString,
}
def Types():
"""Return the types of properties supported."""
return [t for t in _EditorFactory]
def Editor(obj, prop):
"""Returns an editor class to be used for the given property."""
factory = _EditorFactory[obj.getTypeIdOfProperty(prop)]
if factory:
return factory(obj, prop)
return None

View File

@@ -35,7 +35,6 @@ import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathScripts
import PathScripts.PathPreferences as PathPreferences
from collections import Counter
from datetime import datetime
import os
@@ -55,10 +54,10 @@ class CommandPathSanity:
def resolveOutputPath(self, job):
if job.PostProcessorOutputFile != "":
filepath = job.PostProcessorOutputFile
elif PathPreferences.defaultOutputFile() != "":
filepath = PathPreferences.defaultOutputFile()
elif Path.Preferences.defaultOutputFile() != "":
filepath = Path.Preferences.defaultOutputFile()
else:
filepath = PathPreferences.macroFilePath()
filepath = Path.Preferences.macroFilePath()
if "%D" in filepath:
D = FreeCAD.ActiveDocument.FileName

View File

@@ -26,7 +26,6 @@
import FreeCAD
import FreeCADGui
import Path
import PathScripts.PathPreferences as PathPreferences
import PathScripts.drillableLib as drillableLib
import math
@@ -255,61 +254,61 @@ class ALLGate(PathBaseGate):
def contourselect():
FreeCADGui.Selection.addSelectionGate(CONTOURGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Contour Select Mode\n")
def eselect():
FreeCADGui.Selection.addSelectionGate(EGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Edge Select Mode\n")
def drillselect():
FreeCADGui.Selection.addSelectionGate(DRILLGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Drilling Select Mode\n")
def engraveselect():
FreeCADGui.Selection.addSelectionGate(ENGRAVEGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Engraving Select Mode\n")
def fselect():
FreeCADGui.Selection.addSelectionGate(FACEGate()) # Was PROFILEGate()
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
def chamferselect():
FreeCADGui.Selection.addSelectionGate(CHAMFERGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Deburr Select Mode\n")
def profileselect():
FreeCADGui.Selection.addSelectionGate(PROFILEGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Profiling Select Mode\n")
def pocketselect():
FreeCADGui.Selection.addSelectionGate(POCKETGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Pocketing Select Mode\n")
def adaptiveselect():
FreeCADGui.Selection.addSelectionGate(ADAPTIVEGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Adaptive Select Mode\n")
def slotselect():
FreeCADGui.Selection.addSelectionGate(ALLGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Slot Cutter Select Mode\n")
@@ -318,30 +317,30 @@ def surfaceselect():
if MESHGate() or FACEGate():
gate = True
FreeCADGui.Selection.addSelectionGate(gate)
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Surfacing Select Mode\n")
def vcarveselect():
FreeCADGui.Selection.addSelectionGate(VCARVEGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Vcarve Select Mode\n")
def probeselect():
FreeCADGui.Selection.addSelectionGate(PROBEGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Probe Select Mode\n")
def customselect():
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Custom Select Mode\n")
def turnselect():
FreeCADGui.Selection.addSelectionGate(TURNGate())
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Turning Select Mode\n")
@@ -377,5 +376,5 @@ def select(op):
def clear():
FreeCADGui.Selection.removeSelectionGate()
if not PathPreferences.suppressSelectionModeWarning():
if not Path.Preferences.suppressSelectionModeWarning():
FreeCAD.Console.PrintWarning("Free Select\n")

View File

@@ -1,461 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
import Path.Base.Util as PathUtil
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
from PySide.QtCore import QT_TRANSLATE_NOOP
__title__ = "Setup Sheet for a Job."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "A container for all default values and job specific configuration values."
_RegisteredOps: dict = {}
if False:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class Template:
HorizRapid = "HorizRapid"
VertRapid = "VertRapid"
CoolantMode = "CoolantMode"
SafeHeightOffset = "SafeHeightOffset"
SafeHeightExpression = "SafeHeightExpression"
ClearanceHeightOffset = "ClearanceHeightOffset"
ClearanceHeightExpression = "ClearanceHeightExpression"
StartDepthExpression = "StartDepthExpression"
FinalDepthExpression = "FinalDepthExpression"
StepDownExpression = "StepDownExpression"
Fixtures = "Fixtures"
OrderOutputBy = "OrderOutputBy"
SplitOutput = "SplitOutput"
All = [
HorizRapid,
VertRapid,
CoolantMode,
SafeHeightOffset,
SafeHeightExpression,
ClearanceHeightOffset,
ClearanceHeightExpression,
StartDepthExpression,
FinalDepthExpression,
StepDownExpression,
]
def _traverseTemplateAttributes(attrs, codec):
Path.Log.debug(attrs)
coded = {}
for key, value in PathUtil.keyValueIter(attrs):
if type(value) == dict:
Path.Log.debug("%s is a dict" % key)
coded[key] = _traverseTemplateAttributes(value, codec)
elif type(value) == list:
Path.Log.debug("%s is a list" % key)
coded[key] = [_traverseTemplateAttributes(attr, codec) for attr in value]
elif PathUtil.isString(value):
Path.Log.debug("%s is a string" % key)
coded[key] = codec(value)
else:
Path.Log.debug("%s is %s" % (key, type(value)))
coded[key] = value
return coded
class SetupSheet:
"""Property container object used by a Job to hold global reference values."""
TemplateReference = "${SetupSheet}"
DefaultSafeHeightOffset = "3 mm"
DefaultClearanceHeightOffset = "5 mm"
DefaultSafeHeightExpression = "OpStockZMax+${SetupSheet}.SafeHeightOffset"
DefaultClearanceHeightExpression = "OpStockZMax+${SetupSheet}.ClearanceHeightOffset"
DefaultStartDepthExpression = "OpStartDepth"
DefaultFinalDepthExpression = "OpFinalDepth"
DefaultStepDownExpression = "OpToolDiameter"
DefaultCoolantModes = ["None", "Flood", "Mist"]
def __init__(self, obj):
self.obj = obj
obj.addProperty(
"App::PropertySpeed",
"VertRapid",
"ToolController",
QT_TRANSLATE_NOOP(
"App::Property", "Default speed for horizontal rapid moves."
),
)
obj.addProperty(
"App::PropertySpeed",
"HorizRapid",
"ToolController",
QT_TRANSLATE_NOOP(
"App::Property", "Default speed for vertical rapid moves."
),
)
obj.addProperty(
"App::PropertyStringList",
"CoolantModes",
"CoolantMode",
QT_TRANSLATE_NOOP("App::Property", "Coolant Modes"),
)
obj.addProperty(
"App::PropertyEnumeration",
"CoolantMode",
"CoolantMode",
QT_TRANSLATE_NOOP("App::Property", "Default coolant mode."),
)
obj.addProperty(
"App::PropertyLength",
"SafeHeightOffset",
"OperationHeights",
QT_TRANSLATE_NOOP(
"App::Property",
"The usage of this field depends on SafeHeightExpression - by default its value is added to StartDepth and used for SafeHeight of an operation.",
),
)
obj.addProperty(
"App::PropertyString",
"SafeHeightExpression",
"OperationHeights",
QT_TRANSLATE_NOOP(
"App::Property", "Expression set for the SafeHeight of new operations."
),
)
obj.addProperty(
"App::PropertyLength",
"ClearanceHeightOffset",
"OperationHeights",
QT_TRANSLATE_NOOP(
"App::Property",
"The usage of this field depends on ClearanceHeightExpression - by default is value is added to StartDepth and used for ClearanceHeight of an operation.",
),
)
obj.addProperty(
"App::PropertyString",
"ClearanceHeightExpression",
"OperationHeights",
QT_TRANSLATE_NOOP(
"App::Property",
"Expression set for the ClearanceHeight of new operations.",
),
)
obj.addProperty(
"App::PropertyString",
"StartDepthExpression",
"OperationDepths",
QT_TRANSLATE_NOOP(
"App::Property", "Expression used for StartDepth of new operations."
),
)
obj.addProperty(
"App::PropertyString",
"FinalDepthExpression",
"OperationDepths",
QT_TRANSLATE_NOOP(
"App::Property", "Expression used for FinalDepth of new operations."
),
)
obj.addProperty(
"App::PropertyString",
"StepDownExpression",
"OperationDepths",
QT_TRANSLATE_NOOP(
"App::Property", "Expression used for StepDown of new operations."
),
)
obj.SafeHeightOffset = self.decodeAttributeString(self.DefaultSafeHeightOffset)
obj.ClearanceHeightOffset = self.decodeAttributeString(
self.DefaultClearanceHeightOffset
)
obj.SafeHeightExpression = self.decodeAttributeString(
self.DefaultSafeHeightExpression
)
obj.ClearanceHeightExpression = self.decodeAttributeString(
self.DefaultClearanceHeightExpression
)
obj.StartDepthExpression = self.decodeAttributeString(
self.DefaultStartDepthExpression
)
obj.FinalDepthExpression = self.decodeAttributeString(
self.DefaultFinalDepthExpression
)
obj.StepDownExpression = self.decodeAttributeString(
self.DefaultStepDownExpression
)
obj.CoolantModes = self.DefaultCoolantModes
obj.CoolantMode = self.DefaultCoolantModes
obj.Proxy = self
def __getstate__(self):
return None
def __setstate__(self, state):
for obj in FreeCAD.ActiveDocument.Objects:
if hasattr(obj, "Proxy") and obj.Proxy == self:
self.obj = obj
break
return None
def hasDefaultToolRapids(self):
return Path.Geom.isRoughly(self.obj.VertRapid.Value, 0) and Path.Geom.isRoughly(
self.obj.HorizRapid.Value, 0
)
def hasDefaultOperationHeights(self):
if (
self.obj.SafeHeightOffset.UserString
!= FreeCAD.Units.Quantity(self.DefaultSafeHeightOffset).UserString
):
return False
if (
self.obj.ClearanceHeightOffset.UserString
!= FreeCAD.Units.Quantity(self.DefaultClearanceHeightOffset).UserString
):
return False
if self.obj.SafeHeightExpression != self.decodeAttributeString(
self.DefaultSafeHeightExpression
):
return False
if self.obj.ClearanceHeightExpression != self.decodeAttributeString(
self.DefaultClearanceHeightExpression
):
return False
return True
def hasDefaultOperationDepths(self):
if self.obj.StartDepthExpression != self.DefaultStartDepthExpression:
return False
if self.obj.FinalDepthExpression != self.DefaultFinalDepthExpression:
return False
if self.obj.StepDownExpression != self.DefaultStepDownExpression:
return False
return True
def hasDefaultCoolantMode(self):
return self.obj.CoolantMode == "None"
def setFromTemplate(self, attrs):
"""setFromTemplate(attrs) ... sets the default values from the given dictionary."""
for name in Template.All:
if attrs.get(name) is not None:
setattr(self.obj, name, attrs[name])
for opName, op in PathUtil.keyValueIter(_RegisteredOps):
opSetting = attrs.get(opName)
if opSetting is not None:
prototype = op.prototype(opName)
for propName in op.properties():
value = opSetting.get(propName)
if value is not None:
prop = prototype.getProperty(propName)
propertyName = OpPropertyName(opName, propName)
propertyGroup = OpPropertyGroup(opName)
prop.setupProperty(
self.obj,
propertyName,
propertyGroup,
prop.valueFromString(value),
)
def templateAttributes(
self,
includeRapids=True,
includeCoolantMode=True,
includeHeights=True,
includeDepths=True,
includeOps=None,
):
"""templateAttributes(includeRapids, includeHeights, includeDepths) ... answers a dictionary with the default values."""
attrs = {}
if includeRapids:
attrs[Template.VertRapid] = self.obj.VertRapid.UserString
attrs[Template.HorizRapid] = self.obj.HorizRapid.UserString
if includeCoolantMode:
attrs[Template.CoolantMode] = self.obj.CoolantMode
if includeHeights:
attrs[Template.SafeHeightOffset] = self.obj.SafeHeightOffset.UserString
attrs[Template.SafeHeightExpression] = self.obj.SafeHeightExpression
attrs[
Template.ClearanceHeightOffset
] = self.obj.ClearanceHeightOffset.UserString
attrs[
Template.ClearanceHeightExpression
] = self.obj.ClearanceHeightExpression
if includeDepths:
attrs[Template.StartDepthExpression] = self.obj.StartDepthExpression
attrs[Template.FinalDepthExpression] = self.obj.FinalDepthExpression
attrs[Template.StepDownExpression] = self.obj.StepDownExpression
if includeOps:
for opName in includeOps:
settings = {}
op = _RegisteredOps[opName]
for propName in op.properties():
prop = OpPropertyName(opName, propName)
if hasattr(self.obj, prop):
settings[propName] = PathUtil.getPropertyValueString(
self.obj, prop
)
attrs[opName] = settings
return attrs
def expressionReference(self):
"""expressionReference() ... returns the string to be used in expressions"""
# Using the Name here and not the Label (both would be valid) because the Name 'fails early'.
#
# If there is a Name/Label conflict and an expression is bound to the Name we'll get an error
# on creation (Property not found). Not good, but at least there's some indication that
# something's afoul.
#
# If the expression is based on the Label everything works out nicely - until the document is
# saved and loaded from disk. The Labels change in order to avoid the Name/Label conflict
# but the expression stays the same. If the user's lucky the expression is broken because the
# conflicting object doesn't have the properties reference by the expressions. If the user is
# not so lucky those properties also exist in the other object, there is no indication that
# anything is wrong but the expressions will substitute the values from the wrong object.
#
# I prefer the question: "why do I get this error when I create ..." over "my cnc machine just
# rammed it's tool head into the table ..." or even "I saved my file and now it's corrupt..."
#
# https://forum.freecadweb.org/viewtopic.php?f=10&t=24839
# https://forum.freecadweb.org/viewtopic.php?f=10&t=24845
return self.obj.Name
def encodeAttributeString(self, attr):
"""encodeAttributeString(attr) ... return the encoded string of a template attribute."""
return PathUtil.toUnicode(
attr.replace(self.expressionReference(), self.TemplateReference)
)
def decodeAttributeString(self, attr):
"""decodeAttributeString(attr) ... return the decoded string of a template attribute."""
return PathUtil.toUnicode(
attr.replace(self.TemplateReference, self.expressionReference())
)
def encodeTemplateAttributes(self, attrs):
"""encodeTemplateAttributes(attrs) ... return a dictionary with all values encoded."""
return _traverseTemplateAttributes(attrs, self.encodeAttributeString)
def decodeTemplateAttributes(self, attrs):
"""decodeTemplateAttributes(attrs) ... expand template attributes to reference the receiver where applicable."""
return _traverseTemplateAttributes(attrs, self.decodeAttributeString)
def operationsWithSettings(self):
"""operationsWithSettings() ... returns a list of operations which currently have some settings defined."""
ops = []
for name, value in PathUtil.keyValueIter(_RegisteredOps):
for prop in value.registeredPropertyNames(name):
if hasattr(self.obj, prop):
ops.append(name)
break
return list(sorted(ops))
def setOperationProperties(self, obj, opName):
Path.Log.track(obj.Label, opName)
try:
op = _RegisteredOps[opName]
for prop in op.properties():
propName = OpPropertyName(opName, prop)
if hasattr(self.obj, propName):
setattr(obj, prop, getattr(self.obj, propName))
except Exception:
Path.Log.info("SetupSheet has no support for {}".format(opName))
def onDocumentRestored(self, obj):
if not hasattr(obj, "CoolantModes"):
obj.addProperty(
"App::PropertyStringList",
"CoolantModes",
"CoolantMode",
QT_TRANSLATE_NOOP("App::Property", "Coolant Modes"),
)
obj.CoolantModes = self.DefaultCoolantModes
if not hasattr(obj, "CoolantMode"):
obj.addProperty(
"App::PropertyEnumeration",
"CoolantMode",
"CoolantMode",
QT_TRANSLATE_NOOP("App::Property", "Default coolant mode."),
)
obj.CoolantMode = self.DefaultCoolantModes
def Create(name="SetupSheet"):
obj = FreeCAD.ActiveDocument.addObject("App::FeaturePython", name)
obj.Proxy = SetupSheet(obj)
return obj
class _RegisteredOp(object):
def __init__(self, factory, properties):
self.factory = factory
self.properties = properties
def registeredPropertyNames(self, name):
return [OpPropertyName(name, prop) for prop in self.properties()]
def prototype(self, name):
ptt = PathSetupSheetOpPrototype.OpPrototype(name)
self.factory("OpPrototype.%s" % name, ptt)
return ptt
def RegisterOperation(name, objFactory, setupProperties):
global _RegisteredOps
_RegisteredOps[name] = _RegisteredOp(objFactory, setupProperties)
def OpNamePrefix(name):
return name.replace("Path", "").replace(" ", "").replace("_", "")
def OpPropertyName(opName, propName):
return "{}{}".format(OpNamePrefix(opName), propName)
def OpPropertyGroup(opName):
return "Op {}".format(opName)

View File

@@ -1,425 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import Path
import Path.Base.Util as PathUtil
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathScripts.PathGui as PathGui
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathSetupSheet as PathSetupSheet
import PathScripts.PathSetupSheetOpPrototypeGui as PathSetupSheetOpPrototypeGui
from PySide import QtCore, QtGui
__title__ = "Setup Sheet Editor"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for a SetupSheet"
LOGLEVEL = False
if LOGLEVEL:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class ViewProvider:
"""ViewProvider for a SetupSheet.
It's sole job is to provide an icon and invoke the TaskPanel on edit."""
def __init__(self, vobj, name):
Path.Log.track(name)
vobj.Proxy = self
self.icon = name
# mode = 2
self.obj = None
self.vobj = None
def attach(self, vobj):
Path.Log.track()
self.vobj = vobj
self.obj = vobj.Object
def getIcon(self):
return ":/icons/Path_SetupSheet.svg"
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def getDisplayMode(self, mode):
return "Default"
def setEdit(self, vobj, mode=0):
Path.Log.track()
taskPanel = TaskPanel(vobj)
FreeCADGui.Control.closeDialog()
FreeCADGui.Control.showDialog(taskPanel)
taskPanel.setupUi()
return True
def unsetEdit(self, vobj, mode):
FreeCADGui.Control.closeDialog()
return
def claimChildren(self):
return []
def doubleClicked(self, vobj):
self.setEdit(vobj)
class Delegate(QtGui.QStyledItemDelegate):
PropertyRole = QtCore.Qt.UserRole + 1
EditorRole = QtCore.Qt.UserRole + 2
def createEditor(self, parent, option, index):
if index.data(self.EditorRole) is None:
editor = PathSetupSheetOpPrototypeGui.Editor(index.data(self.PropertyRole))
index.model().setData(index, editor, self.EditorRole)
return index.data(self.EditorRole).widget(parent)
def setEditorData(self, widget, index):
Path.Log.track(index.row(), index.column())
index.data(self.EditorRole).setEditorData(widget)
def setModelData(self, widget, model, index):
Path.Log.track(index.row(), index.column())
editor = index.data(self.EditorRole)
editor.setModelData(widget)
index.model().setData(index, editor.prop.displayString(), QtCore.Qt.DisplayRole)
def updateEditorGeometry(self, widget, option, index):
widget.setGeometry(option.rect)
class OpTaskPanel:
"""Editor for an operation's property default values.
The implementation is a simplified generic property editor with basically 3 fields
- checkbox - if set a default value for the given property is set
- name - a non-editable string with the property name
- value - the actual editor for the property's default value
The specific editor classes for a given property type are implemented in
PathSetupSheetOpPrototypeGui which also provides a factory function. The properties
are displayed in a table, each field occypying a column and each row representing
a single property."""
def __init__(self, obj, name, op):
self.name = name
self.obj = obj
self.op = op
self.form = FreeCADGui.PySideUic.loadUi(":/panels/SetupOp.ui")
self.form.setWindowTitle("Op {}".format(self.name))
self.props = sorted(op.properties())
self.prototype = op.prototype(name)
# initialized later
self.delegate = None
self.model = None
def updateData(self, topLeft, bottomRight):
if 0 == topLeft.column():
isset = self.model.item(topLeft.row(), 0).checkState() == QtCore.Qt.Checked
self.model.item(topLeft.row(), 1).setEnabled(isset)
self.model.item(topLeft.row(), 2).setEnabled(isset)
def setupUi(self):
Path.Log.track()
self.delegate = Delegate(self.form)
self.model = QtGui.QStandardItemModel(len(self.props), 3, self.form)
self.model.setHorizontalHeaderLabels(["Set", "Property", "Value"])
for i, name in enumerate(self.props):
prop = self.prototype.getProperty(name)
isset = hasattr(self.obj, self.propertyName(name))
if isset:
prop.setValue(getattr(self.obj, self.propertyName(name)))
self.model.setData(self.model.index(i, 0), isset, QtCore.Qt.EditRole)
self.model.setData(self.model.index(i, 1), name, QtCore.Qt.EditRole)
self.model.setData(self.model.index(i, 2), prop, Delegate.PropertyRole)
self.model.setData(
self.model.index(i, 2), prop.displayString(), QtCore.Qt.DisplayRole
)
self.model.item(i, 0).setCheckable(True)
self.model.item(i, 0).setText("")
self.model.item(i, 1).setEditable(False)
self.model.item(i, 1).setToolTip(prop.info)
self.model.item(i, 2).setToolTip(prop.info)
if isset:
self.model.item(i, 0).setCheckState(QtCore.Qt.Checked)
else:
self.model.item(i, 0).setCheckState(QtCore.Qt.Unchecked)
self.model.item(i, 1).setEnabled(False)
self.model.item(i, 2).setEnabled(False)
self.form.table.setModel(self.model)
self.form.table.setItemDelegateForColumn(2, self.delegate)
self.form.table.resizeColumnsToContents()
self.model.dataChanged.connect(self.updateData)
def propertyName(self, prop):
return PathSetupSheet.OpPropertyName(self.name, prop)
def propertyGroup(self):
return PathSetupSheet.OpPropertyGroup(self.name)
def accept(self):
propertiesCreatedRemoved = False
for i, name in enumerate(self.props):
prop = self.prototype.getProperty(name)
propName = self.propertyName(name)
enabled = self.model.item(i, 0).checkState() == QtCore.Qt.Checked
if enabled and not prop.getValue() is None:
if prop.setupProperty(
self.obj, propName, self.propertyGroup(), prop.getValue()
):
propertiesCreatedRemoved = True
else:
if hasattr(self.obj, propName):
self.obj.removeProperty(propName)
propertiesCreatedRemoved = True
return propertiesCreatedRemoved
class OpsDefaultEditor:
"""Class to collect and display default property editors for all registered operations.
If a form is given at creation time it will integrate with that form and provide an interface to switch
between the editors of different operations. If no form is provided the class assumes that the UI is
taken care of somehow else and just serves as an interface to all operation editors."""
def __init__(self, obj, form):
self.form = form
self.obj = obj
self.ops = sorted(
[
OpTaskPanel(self.obj, name, op)
for name, op in PathUtil.keyValueIter(PathSetupSheet._RegisteredOps)
],
key=lambda op: op.name,
)
if form:
parent = form.tabOpDefaults
for op in self.ops:
form.opDefaultOp.addItem(op.form.windowTitle(), op)
op.form.setParent(parent)
parent.layout().addWidget(op.form)
op.form.hide()
self.currentOp = None
def reject(self):
pass
def accept(self):
if any([op.accept() for op in self.ops]):
Path.Log.track()
def getFields(self):
pass
def updateUI(self):
if self.currentOp:
self.currentOp.form.hide()
self.currentOp = None
if self.form:
current = self.form.opDefaultOp.currentIndex()
if current < 0:
current = 0
self.currentOp = self.form.opDefaultOp.itemData(current)
self.currentOp.form.show()
def updateModel(self, recomp=True):
Path.Log.track()
self.getFields()
self.updateUI()
if recomp:
FreeCAD.ActiveDocument.recompute()
def setFields(self):
self.updateUI()
def setupUi(self):
for op in self.ops:
op.setupUi()
self.updateUI()
if self.form:
self.form.opDefaultOp.currentIndexChanged.connect(self.updateUI)
class GlobalEditor(object):
"""Editor for the global properties which affect almost every operation."""
def __init__(self, obj, form):
self.form = form
self.obj = obj
# initialized later
self.clearanceHeightOffs = None
self.safeHeightOffs = None
self.rapidHorizontal = None
self.rapidVertical = None
def reject(self):
pass
def accept(self):
self.getFields()
def getFields(self):
def updateExpression(name, widget):
value = str(widget.text())
val = PathUtil.getProperty(self.obj, name)
if val != value:
PathUtil.setProperty(self.obj, name, value)
updateExpression("StartDepthExpression", self.form.setupStartDepthExpr)
updateExpression("FinalDepthExpression", self.form.setupFinalDepthExpr)
updateExpression("StepDownExpression", self.form.setupStepDownExpr)
updateExpression(
"ClearanceHeightExpression", self.form.setupClearanceHeightExpr
)
updateExpression("SafeHeightExpression", self.form.setupSafeHeightExpr)
self.clearanceHeightOffs.updateProperty()
self.safeHeightOffs.updateProperty()
self.rapidVertical.updateProperty()
self.rapidHorizontal.updateProperty()
self.obj.CoolantMode = self.form.setupCoolantMode.currentText()
def selectInComboBox(self, name, combo):
"""selectInComboBox(name, combo) ... helper function to select a specific value in a combo box."""
index = combo.findText(name, QtCore.Qt.MatchFixedString)
if index >= 0:
combo.blockSignals(True)
combo.setCurrentIndex(index)
combo.blockSignals(False)
def updateUI(self):
Path.Log.track()
self.form.setupStartDepthExpr.setText(self.obj.StartDepthExpression)
self.form.setupFinalDepthExpr.setText(self.obj.FinalDepthExpression)
self.form.setupStepDownExpr.setText(self.obj.StepDownExpression)
self.form.setupClearanceHeightExpr.setText(self.obj.ClearanceHeightExpression)
self.form.setupSafeHeightExpr.setText(self.obj.SafeHeightExpression)
self.clearanceHeightOffs.updateSpinBox()
self.safeHeightOffs.updateSpinBox()
self.rapidVertical.updateSpinBox()
self.rapidHorizontal.updateSpinBox()
self.selectInComboBox(self.obj.CoolantMode, self.form.setupCoolantMode)
def updateModel(self, recomp=True):
Path.Log.track()
self.getFields()
self.updateUI()
if recomp:
FreeCAD.ActiveDocument.recompute()
def setFields(self):
self.updateUI()
def setupUi(self):
self.clearanceHeightOffs = PathGui.QuantitySpinBox(
self.form.setupClearanceHeightOffs, self.obj, "ClearanceHeightOffset"
)
self.safeHeightOffs = PathGui.QuantitySpinBox(
self.form.setupSafeHeightOffs, self.obj, "SafeHeightOffset"
)
self.rapidHorizontal = PathGui.QuantitySpinBox(
self.form.setupRapidHorizontal, self.obj, "HorizRapid"
)
self.rapidVertical = PathGui.QuantitySpinBox(
self.form.setupRapidVertical, self.obj, "VertRapid"
)
self.form.setupCoolantMode.addItems(self.obj.CoolantModes)
self.setFields()
class TaskPanel:
"""TaskPanel for the SetupSheet - if it is being edited directly."""
def __init__(self, vobj):
self.vobj = vobj
self.obj = vobj.Object
Path.Log.track(self.obj.Label)
self.globalForm = FreeCADGui.PySideUic.loadUi(":/panels/SetupGlobal.ui")
self.globalEditor = GlobalEditor(self.obj, self.globalForm)
self.opsEditor = OpsDefaultEditor(self.obj, None)
self.form = [op.form for op in self.opsEditor.ops] + [self.globalForm]
FreeCAD.ActiveDocument.openTransaction("Edit SetupSheet")
def reject(self):
self.globalEditor.reject()
self.opsEditor.reject()
FreeCAD.ActiveDocument.abortTransaction()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def accept(self):
self.globalEditor.accept()
self.opsEditor.accept()
FreeCAD.ActiveDocument.commitTransaction()
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
def getFields(self):
self.globalEditor.getFields()
self.opsEditor.getFields()
def updateUI(self):
self.globalEditor.updateUI()
self.opsEditor.updateUI()
def updateModel(self):
self.globalEditor.updateModel(False)
self.opsEditor.updateModel(False)
FreeCAD.ActiveDocument.recompute()
def setFields(self):
self.globalEditor.setFields()
self.opsEditor.setFields()
def setupUi(self):
self.globalEditor.setupUi()
self.opsEditor.setupUi()
def Create(name="SetupSheet"):
"""Create(name='SetupSheet') ... creates a new setup sheet"""
FreeCAD.ActiveDocument.openTransaction("Create Job")
ssheet = PathSetupSheet.Create(name)
PathIconViewProvider.Attach(ssheet, name)
return ssheet
PathIconViewProvider.RegisterViewProvider("SetupSheet", ViewProvider)

View File

@@ -1,219 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import Path
__title__ = "Setup Sheet for a Job."
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Prototype objects to allow extraction of setup sheet values and editing."
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
# Path.Log.trackModule(Path.Log.thisModule())
class Property(object):
"""Base class for all prototype properties"""
def __init__(self, name, propType, category, info):
self.name = name
self.propType = propType
self.category = category
self.info = info
self.editorMode = 0
self.value = None
def setValue(self, value):
self.value = value
def getValue(self):
return self.value
def setEditorMode(self, mode):
self.editorMode = mode
def displayString(self):
if self.value is None:
t = self.typeString()
p = "an" if t[0] in ["A", "E", "I", "O", "U"] else "a"
return "%s %s" % (p, t)
return self.value
def typeString(self):
return "Property"
def setupProperty(self, obj, name, category, value):
created = False
if not hasattr(obj, name):
PathLog.track('add', obj.Name, name, self.propType)
obj.addProperty(self.propType, name, category, self.info)
self.initProperty(obj, name)
created = True
PathLog.track('set', obj.Name, name, value, type(value))
setattr(obj, name, value)
return created
def initProperty(self, obj, name):
pass
def setValueFromString(self, string):
self.setValue(self.valueFromString(string))
def valueFromString(self, string):
return string
class PropertyEnumeration(Property):
def typeString(self):
return "Enumeration"
def setValue(self, value):
if list == type(value):
self.enums = value
else:
super(PropertyEnumeration, self).setValue(value)
def getEnumValues(self):
return self.enums
def initProperty(self, obj, name):
setattr(obj, name, self.enums)
class PropertyQuantity(Property):
def displayString(self):
if self.value is None:
return Property.displayString(self)
return self.value.getUserPreferred()[0]
class PropertyAngle(PropertyQuantity):
def typeString(self):
return "Angle"
class PropertyDistance(PropertyQuantity):
def typeString(self):
return "Distance"
class PropertyLength(PropertyQuantity):
def typeString(self):
return "Length"
class PropertyFloat(Property):
def typeString(self):
return "Float"
def valueFromString(self, string):
return float(string)
class PropertyInteger(Property):
def typeString(self):
return "Integer"
def valueFromString(self, string):
return int(string)
class PropertyPercent(PropertyInteger):
def typeString(self):
return "Percent"
class PropertyBool(Property):
def typeString(self):
return "Bool"
def valueFromString(self, string):
return bool(string)
class PropertyString(Property):
def typeString(self):
return "String"
class PropertyMap(Property):
def typeString(self):
return "Map"
def displayString(self, value):
return str(value)
class OpPrototype(object):
PropertyType = {
"App::PropertyAngle": PropertyAngle,
"App::PropertyBool": PropertyBool,
"App::PropertyDistance": PropertyDistance,
"App::PropertyEnumeration": PropertyEnumeration,
"App::PropertyFile": PropertyString,
"App::PropertyFloat": PropertyFloat,
"App::PropertyFloatConstraint": Property,
"App::PropertyFloatList": Property,
"App::PropertyInteger": PropertyInteger,
"App::PropertyIntegerList": PropertyInteger,
"App::PropertyLength": PropertyLength,
"App::PropertyLink": Property,
"App::PropertyLinkList": Property,
"App::PropertyLinkSubListGlobal": Property,
"App::PropertyMap": PropertyMap,
"App::PropertyPercent": PropertyPercent,
"App::PropertyString": PropertyString,
"App::PropertyStringList": Property,
"App::PropertyVectorDistance": Property,
"App::PropertyVectorList": Property,
"Part::PropertyPartShape": Property,
"App::PropertyPythonObject": PropertyString,
}
def __init__(self, name):
self.Label = name
self.properties = {}
self.DoNotSetDefaultValues = True
self.Proxy = None
def __setattr__(self, name, val):
if name in ["Label", "DoNotSetDefaultValues", "properties", "Proxy"]:
if name == "Proxy":
val = None # make sure the proxy is never set
return super(OpPrototype, self).__setattr__(name, val)
self.properties[name].setValue(val)
def addProperty(self, typeString, name, category, info=None):
prop = self.PropertyType[typeString](name, typeString, category, info)
self.properties[name] = prop
return self
def setEditorMode(self, name, mode):
self.properties[name].setEditorMode(mode)
def getProperty(self, name):
return self.properties[name]
def setupProperties(self, setup):
return [p for p in self.properties if p.name in setup]

View File

@@ -1,229 +0,0 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * Copyright (c) 2018 sliptonic <shopinthewoods@gmail.com> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
import Path
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
from PySide import QtCore, QtGui
__title__ = "Setup Sheet Editor"
__author__ = "sliptonic (Brad Collette)"
__url__ = "https://www.freecadweb.org"
__doc__ = "Task panel editor for a SetupSheet"
LOGLEVEL = False
if LOGLEVEL:
Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule())
Path.Log.trackModule(Path.Log.thisModule())
else:
Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule())
class _PropertyEditor(object):
"""Base class of all property editors - just outlines the TableView delegate interface."""
def __init__(self, prop):
self.prop = prop
def widget(self, parent):
"""widget(parent) ... called by the delegate to get a new editor widget.
Must be implemented by subclasses and return the widget."""
pass
def setEditorData(self, widget):
"""setEditorData(widget) ... called by the delegate to initialize the editor.
The widget is the object returned by widget().
Must be implemented by subclasses."""
pass
def setModelData(self, widget):
"""setModelData(widget) ... called by the delegate to store new values.
Must be implemented by subclasses."""
pass
class _PropertyEnumEditor(_PropertyEditor):
"""Editor for enumeration values - uses a combo box."""
def widget(self, parent):
Path.Log.track(self.prop.name, self.prop.getEnumValues())
return QtGui.QComboBox(parent)
def setEditorData(self, widget):
widget.clear()
widget.addItems(self.prop.getEnumValues())
if self.prop.getValue():
index = widget.findText(self.prop.getValue(), QtCore.Qt.MatchFixedString)
if index >= 0:
widget.setCurrentIndex(index)
def setModelData(self, widget):
self.prop.setValue(widget.currentText())
class _PropertyBoolEditor(_PropertyEditor):
"""Editor for boolean values - uses a combo box."""
def widget(self, parent):
return QtGui.QComboBox(parent)
def setEditorData(self, widget):
widget.clear()
widget.addItems(["false", "true"])
if not self.prop.getValue() is None:
index = 1 if self.prop.getValue() else 0
widget.setCurrentIndex(index)
def setModelData(self, widget):
self.prop.setValue(widget.currentText() == "true")
class _PropertyStringEditor(_PropertyEditor):
"""Editor for string values - uses a line edit."""
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
text = "" if self.prop.getValue() is None else self.prop.getValue()
widget.setText(text)
def setModelData(self, widget):
self.prop.setValue(widget.text())
class _PropertyAngleEditor(_PropertyEditor):
"""Editor for angle values - uses a line edit"""
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
quantity = self.prop.getValue()
if quantity is None:
quantity = FreeCAD.Units.Quantity(0, FreeCAD.Units.Angle)
widget.setText(quantity.getUserPreferred()[0])
def setModelData(self, widget):
self.prop.setValue(FreeCAD.Units.Quantity(widget.text()))
class _PropertyLengthEditor(_PropertyEditor):
"""Editor for length values - uses a line edit."""
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
quantity = self.prop.getValue()
if quantity is None:
quantity = FreeCAD.Units.Quantity(0, FreeCAD.Units.Length)
widget.setText(quantity.getUserPreferred()[0])
def setModelData(self, widget):
self.prop.setValue(FreeCAD.Units.Quantity(widget.text()))
class _PropertyPercentEditor(_PropertyEditor):
"""Editor for percent values - uses a spin box."""
def widget(self, parent):
return QtGui.QSpinBox(parent)
def setEditorData(self, widget):
widget.setRange(0, 100)
value = self.prop.getValue()
if value is None:
value = 0
widget.setValue(value)
def setModelData(self, widget):
self.prop.setValue(widget.value())
class _PropertyIntegerEditor(_PropertyEditor):
"""Editor for integer values - uses a spin box."""
def widget(self, parent):
return QtGui.QSpinBox(parent)
def setEditorData(self, widget):
value = self.prop.getValue()
if value is None:
value = 0
widget.setValue(value)
def setModelData(self, widget):
self.prop.setValue(widget.value())
class _PropertyFloatEditor(_PropertyEditor):
"""Editor for float values - uses a double spin box."""
def widget(self, parent):
return QtGui.QDoubleSpinBox(parent)
def setEditorData(self, widget):
value = self.prop.getValue()
if value is None:
value = 0.0
widget.setValue(value)
def setModelData(self, widget):
self.prop.setValue(widget.value())
class _PropertyFileEditor(_PropertyEditor):
def widget(self, parent):
return QtGui.QLineEdit(parent)
def setEditorData(self, widget):
text = "" if self.prop.getValue() is None else self.prop.getValue()
widget.setText(text)
def setModelData(self, widget):
self.prop.setValue(widget.text())
_EditorFactory = {
PathSetupSheetOpPrototype.Property: None,
PathSetupSheetOpPrototype.PropertyAngle: _PropertyAngleEditor,
PathSetupSheetOpPrototype.PropertyBool: _PropertyBoolEditor,
PathSetupSheetOpPrototype.PropertyDistance: _PropertyLengthEditor,
PathSetupSheetOpPrototype.PropertyEnumeration: _PropertyEnumEditor,
PathSetupSheetOpPrototype.PropertyFloat: _PropertyFloatEditor,
PathSetupSheetOpPrototype.PropertyInteger: _PropertyIntegerEditor,
PathSetupSheetOpPrototype.PropertyLength: _PropertyLengthEditor,
PathSetupSheetOpPrototype.PropertyPercent: _PropertyPercentEditor,
PathSetupSheetOpPrototype.PropertyString: _PropertyStringEditor,
}
def Editor(prop):
"""Returns an editor class to be used for the given property."""
factory = _EditorFactory[prop.__class__]
if factory:
return factory(prop)
return None

View File

@@ -24,7 +24,7 @@ import FreeCAD
import Path
import Path.Base.Util as PathUtil
import Path.Dressup.Utils as PathDressup
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathGui
import PathScripts.PathJob as PathJob
import PathSimulator
import math

View File

@@ -338,9 +338,9 @@ def SetupStockObject(obj, stockType):
obj.StockType = stockType
obj.setEditorMode("StockType", 2) # hide
import PathScripts.PathIconViewProvider
import Path.Base.Gui.IconViewProvider
PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, "Stock")
Path.Base.Gui.IconViewProvider.ViewProvider(obj.ViewObject, "Stock")
obj.ViewObject.Transparency = 90
obj.ViewObject.DisplayMode = "Wireframe"

View File

@@ -24,7 +24,7 @@ import FreeCADGui
import FreeCAD
import Path
import Path.Tool.Controller as PathToolController
import PathGui as PGui # ensure Path/Gui/Resources are loaded
import PathGui
import PathScripts.PathJobCmd as PathJobCmd
import PathScripts.PathUtils as PathUtils
from PySide import QtGui