Basic editor and shape update.
This commit is contained in:
@@ -101,6 +101,7 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PathStop.py
|
||||
PathScripts/PathSurface.py
|
||||
PathScripts/PathSurfaceGui.py
|
||||
PathScripts/PathToolBitEdit.py
|
||||
PathScripts/PathToolController.py
|
||||
PathScripts/PathToolControllerGui.py
|
||||
PathScripts/PathToolEdit.py
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
<file>panels/PointEdit.ui</file>
|
||||
<file>panels/SetupGlobal.ui</file>
|
||||
<file>panels/SetupOp.ui</file>
|
||||
<file>panels/ToolBitEditor.ui</file>
|
||||
<file>panels/ToolEditor.ui</file>
|
||||
<file>panels/ToolLibraryEditor.ui</file>
|
||||
<file>panels/TaskPathSimulator.ui</file>
|
||||
|
||||
170
src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui
Normal file
170
src/Mod/Path/Gui/Resources/panels/ToolBitEditor.ui
Normal file
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Form</class>
|
||||
<widget class="QWidget" name="Form">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>411</width>
|
||||
<height>886</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Tool Bit</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="toolName">
|
||||
<property name="maxLength">
|
||||
<number>50</number>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Display Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Type</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QWidget" name="widget" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="templatePath"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="templateSet">
|
||||
<property name="text">
|
||||
<string>...</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="bitParams">
|
||||
<property name="title">
|
||||
<string>Bit Parameter</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout_2">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Point/Tip Angle</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="Gui::InputField" name="toolCuttingEdgeAngle">
|
||||
<property name="text">
|
||||
<string>180°</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string>°</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Cutting Edge Height</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::InputField" name="toolCuttingEdgeHeight">
|
||||
<property name="text">
|
||||
<string>0.00</string>
|
||||
</property>
|
||||
<property name="unit" stdset="0">
|
||||
<string>mm</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="widget_2" native="true">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="image">
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>210</width>
|
||||
<height>297</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Image</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::InputField</class>
|
||||
<extends>QLineEdit</extends>
|
||||
<header>Gui/InputField.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -25,6 +25,7 @@
|
||||
import FreeCAD
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PySide
|
||||
|
||||
|
||||
@@ -44,34 +45,6 @@ if LOGLEVEL:
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
def _getProperty(obj, prop):
|
||||
o = obj
|
||||
attr = obj
|
||||
name = None
|
||||
for name in prop.split('.'):
|
||||
o = attr
|
||||
if not hasattr(o, name):
|
||||
break
|
||||
attr = getattr(o, name)
|
||||
|
||||
if o == attr:
|
||||
PathLog.warning(translate('PathGui', "%s has no property %s (%s))") % (obj.Label, prop, name))
|
||||
return (None, None, None)
|
||||
|
||||
#PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr))
|
||||
return(o, attr, name)
|
||||
|
||||
def getProperty(obj, prop):
|
||||
'''getProperty(obj, prop) ... answer obj's property defined by its canonical name.'''
|
||||
o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable
|
||||
return attr
|
||||
|
||||
def setProperty(obj, prop, value):
|
||||
'''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.'''
|
||||
o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable
|
||||
if o and name:
|
||||
setattr(o, name, value)
|
||||
|
||||
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.
|
||||
@@ -82,13 +55,13 @@ If onBeforeChange is specified it is called before a new value is assigned to th
|
||||
Returns True if a new value was assigned, False otherwise (new value is the same as the current).
|
||||
'''
|
||||
value = FreeCAD.Units.Quantity(widget.text()).Value
|
||||
attr = getProperty(obj, prop)
|
||||
attr = PathUtil.getProperty(obj, prop)
|
||||
attrValue = attr.Value if hasattr(attr, 'Value') else attr
|
||||
if not PathGeom.isRoughly(attrValue, value):
|
||||
PathLog.debug("updateInputField(%s, %s): %.2f -> %.2f" % (obj.Label, prop, attr, value))
|
||||
if onBeforeChange:
|
||||
onBeforeChange(obj)
|
||||
setProperty(obj, prop, value)
|
||||
PathUtil.setProperty(obj, prop, value)
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -107,7 +80,7 @@ The spin box gets bound to a given property and supports update in both directio
|
||||
self.widget = widget
|
||||
self.prop = prop
|
||||
self.onBeforeChange = onBeforeChange
|
||||
attr = getProperty(self.obj, self.prop)
|
||||
attr = PathUtil.getProperty(self.obj, self.prop)
|
||||
if attr is not None:
|
||||
if hasattr(attr, 'Value'):
|
||||
widget.setProperty('unit', attr.getUserPreferred()[2])
|
||||
@@ -134,7 +107,7 @@ If no value is provided the value of the bound property is used.
|
||||
quantity can be of type Quantity or Float.'''
|
||||
if self.valid:
|
||||
if quantity is None:
|
||||
quantity = getProperty(self.obj, self.prop)
|
||||
quantity = PathUtil.getProperty(self.obj, self.prop)
|
||||
value = quantity.Value if hasattr(quantity, 'Value') else quantity
|
||||
self.widget.setProperty('rawValue', value)
|
||||
|
||||
|
||||
@@ -80,7 +80,8 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
# default depths calculation not correct for facing
|
||||
if prop == "Base":
|
||||
job = PathUtils.findParentJob(obj)
|
||||
obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax
|
||||
if job:
|
||||
obj.OpStartDepth = job.Stock.Shape.BoundBox.ZMax
|
||||
|
||||
if len(obj.Base) >= 1:
|
||||
print('processing')
|
||||
@@ -95,7 +96,7 @@ class ObjectFace(PathPocketBase.ObjectPocket):
|
||||
# Otherwise, top of part.
|
||||
|
||||
obj.OpFinalDepth = Part.makeCompound(sublist).BoundBox.ZMax
|
||||
else:
|
||||
elif job:
|
||||
obj.OpFinalDepth = job.Proxy.modelBoundBox(job).ZMax
|
||||
|
||||
def areaOpShapes(self, obj):
|
||||
|
||||
@@ -306,9 +306,9 @@ class GlobalEditor(object):
|
||||
def getFields(self):
|
||||
def updateExpression(name, widget):
|
||||
value = str(widget.text())
|
||||
val = PathGui.getProperty(self.obj, name)
|
||||
val = PathUtil.getProperty(self.obj, name)
|
||||
if val != value:
|
||||
PathGui.setProperty(self.obj, name, value)
|
||||
PathUtil.setProperty(self.obj, name, value)
|
||||
|
||||
updateExpression('StartDepthExpression', self.form.setupStartDepthExpr)
|
||||
updateExpression('FinalDepthExpression', self.form.setupFinalDepthExpr)
|
||||
|
||||
@@ -106,6 +106,10 @@ class PropertyQuantity(Property):
|
||||
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"
|
||||
@@ -137,24 +141,25 @@ class PropertyString(Property):
|
||||
class OpPrototype(object):
|
||||
|
||||
PropertyType = {
|
||||
'App::PropertyBool': PropertyBool,
|
||||
'App::PropertyDistance': PropertyDistance,
|
||||
'App::PropertyEnumeration': PropertyEnumeration,
|
||||
'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::PropertyPercent': PropertyPercent,
|
||||
'App::PropertyString': PropertyString,
|
||||
'App::PropertyStringList': Property,
|
||||
'App::PropertyVectorDistance': Property,
|
||||
'App::PropertyVectorList': Property,
|
||||
'Part::PropertyPartShape': Property,
|
||||
'App::PropertyAngle': PropertyAngle,
|
||||
'App::PropertyBool': PropertyBool,
|
||||
'App::PropertyDistance': PropertyDistance,
|
||||
'App::PropertyEnumeration': PropertyEnumeration,
|
||||
'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::PropertyPercent': PropertyPercent,
|
||||
'App::PropertyString': PropertyString,
|
||||
'App::PropertyStringList': Property,
|
||||
'App::PropertyVectorDistance': Property,
|
||||
'App::PropertyVectorList': Property,
|
||||
'Part::PropertyPartShape': Property,
|
||||
}
|
||||
|
||||
def __init__(self, name):
|
||||
|
||||
@@ -112,6 +112,21 @@ class _PropertyStringEditor(_PropertyEditor):
|
||||
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.'''
|
||||
|
||||
@@ -174,15 +189,16 @@ class _PropertyFloatEditor(_PropertyEditor):
|
||||
self.prop.setValue(widget.value())
|
||||
|
||||
_EditorFactory = {
|
||||
PathSetupSheetOpPrototype.Property: None,
|
||||
PathSetupSheetOpPrototype.PropertyBool: _PropertyBoolEditor,
|
||||
PathSetupSheetOpPrototype.PropertyDistance: _PropertyLengthEditor,
|
||||
PathSetupSheetOpPrototype.PropertyEnumeration: _PropertyEnumEditor,
|
||||
PathSetupSheetOpPrototype.PropertyFloat: _PropertyFloatEditor,
|
||||
PathSetupSheetOpPrototype.PropertyInteger: _PropertyIntegerEditor,
|
||||
PathSetupSheetOpPrototype.PropertyLength: _PropertyLengthEditor,
|
||||
PathSetupSheetOpPrototype.PropertyPercent: _PropertyPercentEditor,
|
||||
PathSetupSheetOpPrototype.PropertyString: _PropertyStringEditor,
|
||||
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):
|
||||
|
||||
202
src/Mod/Path/PathScripts/PathToolBit.py
Normal file
202
src/Mod/Path/PathScripts/PathToolBit.py
Normal file
@@ -0,0 +1,202 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2019 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 Part
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathSetupSheetOpPrototype as PathSetupSheetOpPrototype
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PySide
|
||||
import Sketcher
|
||||
import math
|
||||
import zipfile
|
||||
|
||||
__title__ = "Tool bits."
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Class to deal with and represent a tool bit."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule()
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
ParameterTypeConstraint = {
|
||||
'Angle': 'App::PropertyAngle',
|
||||
'Distance': 'App::PropertyLength',
|
||||
'DistanceX': 'App::PropertyLength',
|
||||
'DistanceY': 'App::PropertyLength',
|
||||
'Radius': 'App::PropertyLength'
|
||||
}
|
||||
|
||||
|
||||
def updateConstraint(sketch, name, value):
|
||||
for i, constraint in enumerate(sketch.Constraints):
|
||||
if constraint.Name.split(';')[0] == name:
|
||||
if constraint.Type in ['DistanceX', 'DistanceY', 'Distance', 'Radius']:
|
||||
if not PathGeom.isRoughly(constraint.Value, value.Value):
|
||||
PathLog.track(name, constraint.Type, 'update', i)
|
||||
constr = Sketcher.Constraint(constraint.Type, constraint.First, value)
|
||||
sketch.delConstraint(i)
|
||||
sketch.recompute()
|
||||
n = sketch.addConstraint(constr)
|
||||
sketch.renameConstraint(n, constraint.Name)
|
||||
else:
|
||||
PathLog.track(name, constraint.Type, 'unchanged')
|
||||
else:
|
||||
print(constraint.Name, constraint.Type)
|
||||
break
|
||||
|
||||
PropertyGroupBit = 'Bit'
|
||||
|
||||
class ToolBit(object):
|
||||
|
||||
def __init__(self, obj, templateFile):
|
||||
PathLog.track(obj.Label, templateFile)
|
||||
self.obj = obj
|
||||
obj.addProperty('App::PropertyFile', 'BitTemplate', 'Base', translate('PathToolBit', 'Template for bit shape'))
|
||||
obj.addProperty('App::PropertyLink', 'BitBody', 'Base', translate('PathToolBit', 'The parametrized body representing the tool bit'))
|
||||
if templateFile is not None:
|
||||
obj.BitTemplate = templateFile
|
||||
self._setupBitFromTemplate(obj)
|
||||
self.onDocumentRestored(obj)
|
||||
|
||||
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 bitPropertyNames(self, obj):
|
||||
return [prop for prop in obj.PropertiesList if obj.getGroupOfProperty(prop) == PropertyGroupBit]
|
||||
|
||||
def onDocumentRestored(self, obj):
|
||||
obj.setEditorMode('BitTemplate', 1)
|
||||
obj.setEditorMode('BitBody', 2)
|
||||
obj.setEditorMode('Shape', 2)
|
||||
|
||||
for prop in self.bitPropertyNames(obj):
|
||||
obj.setEditorMode(prop, 1)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
PathLog.track(obj.Label, prop)
|
||||
if prop == 'BitTemplate' and not 'Restore' in obj.State:
|
||||
self._setupBitFromTemplate(obj)
|
||||
#elif obj.getGroupOfProperty(prop) == PropertyGroupBit:
|
||||
# self._updateBitShape(obj, [prop])
|
||||
|
||||
def _updateBitShape(self, obj, properties=None):
|
||||
if not properties:
|
||||
properties = self.bitPropertyNames(obj)
|
||||
for prop in properties:
|
||||
for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']:
|
||||
PathLog.track(obj.Label, sketch.Label, prop)
|
||||
updateConstraint(sketch, prop, obj.getPropertyByName(prop))
|
||||
self._copyBitShape(obj)
|
||||
|
||||
def _copyBitShape(self, obj):
|
||||
obj.Document.recompute()
|
||||
if obj.BitBody and obj.BitBody.Shape:
|
||||
obj.Shape = obj.BitBody.Shape
|
||||
else:
|
||||
obj.Shape = Part.Shape()
|
||||
|
||||
def _loadBitBody(self, obj, path=None):
|
||||
if not path:
|
||||
path = obj.BitTemplate
|
||||
docOpened = False
|
||||
doc = None
|
||||
for d in FreeCAD.listDocuments():
|
||||
if FreeCAD.getDocument(d).FileName == path:
|
||||
doc = FreeCAD.getDocument(d)
|
||||
break
|
||||
if doc is None:
|
||||
doc = FreeCAD.open(path)
|
||||
docOpened = True
|
||||
return (doc, docOpened)
|
||||
|
||||
def _removeBitBody(self, obj):
|
||||
if obj.BitBody:
|
||||
obj.BitBody.removeObjectsFromDocument()
|
||||
obj.Document.removeObject(obj.BitBody.Name)
|
||||
obj.BitBody = None
|
||||
|
||||
def _deleteBitSetup(self, obj):
|
||||
PathLog.track(obj.Label)
|
||||
self._removeBitBody(obj)
|
||||
self._copyBitShape(obj)
|
||||
for prop in self.bitPropertyNames(obj):
|
||||
obj.removeProperty(prop)
|
||||
|
||||
def _setupBitFromTemplate(self, obj, path=None):
|
||||
(doc, docOpened) = self._loadBitBody(obj, path)
|
||||
|
||||
obj.Label = doc.RootObjects[0].Label
|
||||
self._deleteBitSetup(obj)
|
||||
obj.BitBody = obj.Document.copyObject(doc.RootObjects[0], True)
|
||||
if docOpened:
|
||||
FreeCAD.closeDocument(doc.Name)
|
||||
|
||||
if obj.BitBody.ViewObject:
|
||||
obj.BitBody.ViewObject.Visibility = False
|
||||
self._copyBitShape(obj)
|
||||
|
||||
for sketch in [o for o in obj.BitBody.Group if o.TypeId == 'Sketcher::SketchObject']:
|
||||
for constraint in [c for c in sketch.Constraints if c.Name != '']:
|
||||
typ = ParameterTypeConstraint.get(constraint.Type)
|
||||
PathLog.track(constraint, typ)
|
||||
if typ is not None:
|
||||
parts = [p.strip() for p in constraint.Name.split(';')]
|
||||
prop = parts[0]
|
||||
desc = ''
|
||||
if len(parts) > 1:
|
||||
desc = parts[1]
|
||||
obj.addProperty(typ, prop, PropertyGroupBit, desc)
|
||||
value = constraint.Value
|
||||
if constraint.Type == 'Angle':
|
||||
value = value * 180 / math.pi
|
||||
PathUtil.setProperty(obj, prop, constraint.Value)
|
||||
|
||||
def getBitThumbnail(self, obj):
|
||||
if obj.BitTemplate:
|
||||
with open(obj.BitTemplate, 'rb') as fd:
|
||||
zf = zipfile.ZipFile(fd)
|
||||
pf = zf.open('thumbnails/Thumbnail.png', 'r')
|
||||
data = pf.read()
|
||||
pf.close()
|
||||
return data
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def Create(name = 'ToolBit', templateFile=None):
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', name)
|
||||
obj.Proxy = ToolBit(obj, templateFile)
|
||||
return obj
|
||||
127
src/Mod/Path/PathScripts/PathToolBitEdit.py
Normal file
127
src/Mod/Path/PathScripts/PathToolBitEdit.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2019 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 PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
import copy
|
||||
import math
|
||||
import re
|
||||
|
||||
from PySide import QtGui
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
|
||||
ParameterTypeConstraint = {
|
||||
'Angle': 'Angle',
|
||||
'Distance': 'Length',
|
||||
'DistanceX': 'Length',
|
||||
'DistanceY': 'Length',
|
||||
'Radius': 'Length'
|
||||
}
|
||||
|
||||
ParameterTypeProperty = {
|
||||
'Length': 'App::PropertyLength',
|
||||
'Angle': 'App::PropertyAngle'
|
||||
}
|
||||
|
||||
class ToolBitEditor:
|
||||
'''UI and controller for editing a ToolBit.
|
||||
The controller embeds the UI to the parentWidget which has to have a layout attached to it.
|
||||
'''
|
||||
|
||||
def __init__(self, tool, parentWidget=None):
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/ToolBitEditor.ui")
|
||||
|
||||
if parentWidget:
|
||||
self.form.setParent(parentWidget)
|
||||
parentWidget.layout().addWidget(self.form)
|
||||
|
||||
self.tool = tool
|
||||
if not tool.BitTemplate:
|
||||
self.tool.BitTemplate = 'src/Mod/Path/Tools/Template/endmill-straight.fcstd'
|
||||
self.setupTool(self.tool)
|
||||
|
||||
def setupTool(self, tool):
|
||||
layout = self.form.bitParams.layout()
|
||||
for i in range(layout.rowCount() - 1, -1, -1):
|
||||
layout.removeRow(i)
|
||||
editor = {}
|
||||
ui = FreeCADGui.UiLoader()
|
||||
for name in tool.PropertiesList:
|
||||
if tool.getGroupOfProperty(name) == PathToolBit.PropertyGroupBit:
|
||||
qsb = ui.createWidget('Gui::QuantitySpinBox')
|
||||
editor[name] = PathGui.QuantitySpinBox(qsb, tool, name)
|
||||
label = QtGui.QLabel(re.sub('([A-Z][a-z]+)', r' \1', re.sub('([A-Z]+)', r' \1', name)))
|
||||
#if parameter.get('Desc'):
|
||||
# qsb.setToolTip(parameter['Desc'])
|
||||
layout.addRow(label, qsb)
|
||||
self.bitEditor = editor
|
||||
|
||||
def accept(self):
|
||||
self.refresh()
|
||||
|
||||
def reject(self):
|
||||
pass
|
||||
|
||||
def updateUI(self):
|
||||
PathLog.track()
|
||||
self.form.toolName.setText(self.tool.Label)
|
||||
self.form.templatePath.setText(self.tool.BitTemplate)
|
||||
|
||||
for editor in self.bitEditor:
|
||||
self.bitEditor[editor].updateSpinBox()
|
||||
|
||||
def updateTemplate(self):
|
||||
self.tool.BitTemplate = str(self.form.templatePath.text())
|
||||
self.setupTool(self.tool)
|
||||
|
||||
def updateTool(self):
|
||||
PathLog.track()
|
||||
self.tool.Label = str(self.form.toolName.text())
|
||||
self.tool.BitTemplate = str(self.form.templatePath.text())
|
||||
|
||||
for editor in self.bitEditor:
|
||||
self.bitEditor[editor].updateProperty()
|
||||
|
||||
self.tool.Proxy._updateBitShape(self.tool)
|
||||
|
||||
def refresh(self):
|
||||
PathLog.track()
|
||||
self.form.blockSignals(True)
|
||||
self.updateTool()
|
||||
self.updateUI()
|
||||
self.form.blockSignals(False)
|
||||
|
||||
def setupUI(self):
|
||||
PathLog.track()
|
||||
self.updateUI()
|
||||
|
||||
self.form.toolName.editingFinished.connect(self.refresh)
|
||||
self.form.templatePath.editingFinished.connect(self.updateTemplate)
|
||||
153
src/Mod/Path/PathScripts/PathToolBitGui.py
Normal file
153
src/Mod/Path/PathScripts/PathToolBitGui.py
Normal file
@@ -0,0 +1,153 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# ***************************************************************************
|
||||
# * *
|
||||
# * Copyright (c) 2019 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathGui as PathGui
|
||||
import PathScripts.PathIconViewProvider as PathIconViewProvider
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathToolBit as PathToolBit
|
||||
import PathScripts.PathToolBitEdit as PathToolBitEdit
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
__title__ = "Tool Bit UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Task panel editor for a ToolBit"
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
class ViewProvider(object):
|
||||
'''ViewProvider for a ToolBit.
|
||||
It's sole job is to provide an icon and invoke the TaskPanel on edit.'''
|
||||
|
||||
def __init__(self, vobj, name):
|
||||
PathLog.track(name, vobj.Object)
|
||||
self.icon = name
|
||||
self.obj = vobj.Object
|
||||
self.vobj = vobj
|
||||
vobj.Proxy = self
|
||||
|
||||
def attach(self, vobj):
|
||||
PathLog.track(vobj.Object)
|
||||
self.vobj = vobj
|
||||
self.obj = vobj.Object
|
||||
|
||||
def getIcon(self):
|
||||
png = self.obj.Proxy.getBitThumbnail(self.obj)
|
||||
if png:
|
||||
pixmap = QtGui.QPixmap()
|
||||
pixmap.loadFromData(png, "PNG")
|
||||
return QtGui.QIcon(pixmap)
|
||||
return ':/icons/Path-ToolChange.svg'
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
# pylint: disable=unused-argument
|
||||
return None
|
||||
|
||||
def getDisplayMode(self, mode):
|
||||
# pylint: disable=unused-argument
|
||||
return 'Default'
|
||||
|
||||
def setEdit(self, vobj, mode=0):
|
||||
# pylint: disable=unused-argument
|
||||
PathLog.track()
|
||||
taskPanel = TaskPanel(vobj)
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCADGui.Control.showDialog(taskPanel)
|
||||
taskPanel.setupUi()
|
||||
return True
|
||||
|
||||
def unsetEdit(self, vobj, mode):
|
||||
# pylint: disable=unused-argument
|
||||
FreeCADGui.Control.closeDialog()
|
||||
return
|
||||
|
||||
def claimChildren(self):
|
||||
if self.obj.BitBody:
|
||||
return [self.obj.BitBody]
|
||||
return []
|
||||
|
||||
def doubleClicked(self, vobj):
|
||||
self.setEdit(vobj)
|
||||
|
||||
class TaskPanel:
|
||||
'''TaskPanel for the SetupSheet - if it is being edited directly.'''
|
||||
|
||||
def __init__(self, vobj):
|
||||
PathLog.track(vobj.Object.Label)
|
||||
self.vobj = vobj
|
||||
self.obj = vobj.Object
|
||||
self.editor = PathToolBitEdit.ToolBitEditor(self.obj)
|
||||
self.form = self.editor.form
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathToolBit", "Edit ToolBit"))
|
||||
|
||||
def reject(self):
|
||||
self.editor.reject()
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def accept(self):
|
||||
self.editor.accept()
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCADGui.ActiveDocument.resetEdit()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def getFields(self):
|
||||
self.editor.getFields()
|
||||
|
||||
def updateUI(self):
|
||||
self.editor.updateUI()
|
||||
|
||||
def updateModel(self):
|
||||
self.editor.updateTool()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
def setFields(self):
|
||||
self.editor.setFields()
|
||||
|
||||
def setupUi(self):
|
||||
self.editor.setupUI()
|
||||
|
||||
def Create(name = 'ToolBit'):
|
||||
'''Create(name = 'ToolBit') ... creates a new tool bit'''
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("PathToolBit", "Create ToolBit"))
|
||||
tool = PathToolBit.Create(name)
|
||||
PathIconViewProvider.Attach(tool.ViewObject, name)
|
||||
return tool
|
||||
|
||||
PathIconViewProvider.RegisterViewProvider('ToolBit', ViewProvider)
|
||||
@@ -34,14 +34,40 @@ other than PathLog, then it probably doesn't belong here.
|
||||
|
||||
import six
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PySide
|
||||
|
||||
LOGLEVEL = False
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
if LOGLEVEL:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
def _getProperty(obj, prop):
|
||||
o = obj
|
||||
attr = obj
|
||||
name = None
|
||||
for name in prop.split('.'):
|
||||
o = attr
|
||||
if not hasattr(o, name):
|
||||
break
|
||||
attr = getattr(o, name)
|
||||
|
||||
if o == attr:
|
||||
PathLog.warning(translate('PathGui', "%s has no property %s (%s))") % (obj.Label, prop, name))
|
||||
return (None, None, None)
|
||||
|
||||
#PathLog.debug("found property %s of %s (%s: %s)" % (prop, obj.Label, name, attr))
|
||||
return(o, attr, name)
|
||||
|
||||
def getProperty(obj, prop):
|
||||
'''getProperty(obj, prop) ... answer obj's property defined by its canonical name.'''
|
||||
o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable
|
||||
return attr
|
||||
|
||||
def setProperty(obj, prop, value):
|
||||
'''setProperty(obj, prop, value) ... set the property value of obj's property defined by its canonical name.'''
|
||||
o, attr, name = _getProperty(obj, prop) # pylint: disable=unused-variable
|
||||
if o and name:
|
||||
setattr(o, name, value)
|
||||
|
||||
# NotValidBaseTypeIds = ['Sketcher::SketchObject']
|
||||
NotValidBaseTypeIds = []
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user