Merge pull request #5324 from sliptonic/bug/translation3
Path Translation cleanup part 2 [PATH]
This commit is contained in:
@@ -67,16 +67,11 @@
|
||||
<item row="0" column="1">
|
||||
<widget class="QComboBox" name="cutSide">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default).</p></body></html></string>
|
||||
<string notr="true"><html><head/><body><p>Specify if the profile should be performed inside or outside the base geometry features. This only matters if Use Compensation is checked (the default).</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Outside</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Inside</string>
|
||||
<string>PLACEHOLDER</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
@@ -97,16 +92,11 @@
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="direction">
|
||||
<property name="toolTip">
|
||||
<string><html><head/><body><p>The direction in which the profile is performed, clockwise or counter clockwise.</p></body></html></string>
|
||||
<string notr="true"><html><head/><body><p>The direction in which the profile is performed, clockwise or counter clockwise.</p></body></html></string>
|
||||
</property>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CW</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>CCW</string>
|
||||
<string>PLACEHOLDER</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
|
||||
@@ -35,7 +35,7 @@ class PathCommandGroup:
|
||||
return tuple(self.cmdlist)
|
||||
|
||||
def GetResources(self):
|
||||
return {'MenuText': self.menu, 'ToolTip': self.tooltip}
|
||||
return {"MenuText": self.menu, "ToolTip": self.tooltip}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -45,11 +45,13 @@ class PathCommandGroup:
|
||||
return False
|
||||
|
||||
|
||||
class PathWorkbench (Workbench):
|
||||
class PathWorkbench(Workbench):
|
||||
"Path workbench"
|
||||
|
||||
def __init__(self):
|
||||
self.__class__.Icon = FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg"
|
||||
self.__class__.Icon = (
|
||||
FreeCAD.getResourceDir() + "Mod/Path/Resources/icons/PathWorkbench.svg"
|
||||
)
|
||||
self.__class__.MenuText = "Path"
|
||||
self.__class__.ToolTip = "Path workbench"
|
||||
|
||||
@@ -58,8 +60,11 @@ class PathWorkbench (Workbench):
|
||||
|
||||
# Add preferences pages - before loading PathGui to properly order pages of Path group
|
||||
from PathScripts import PathPreferencesPathJob, PathPreferencesPathDressup
|
||||
|
||||
FreeCADGui.addPreferencePage(PathPreferencesPathJob.JobPreferencesPage, "Path")
|
||||
FreeCADGui.addPreferencePage(PathPreferencesPathDressup.DressupPreferencesPage, "Path")
|
||||
FreeCADGui.addPreferencePage(
|
||||
PathPreferencesPathDressup.DressupPreferencesPage, "Path"
|
||||
)
|
||||
|
||||
# Check enablement of experimental features
|
||||
from PathScripts import PathPreferences
|
||||
@@ -69,6 +74,7 @@ class PathWorkbench (Workbench):
|
||||
import PathScripts
|
||||
import PathGui
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
FreeCADGui.addLanguagePath(":/translations")
|
||||
FreeCADGui.addIconPath(":/icons")
|
||||
from PathScripts import PathGuiInit
|
||||
@@ -77,30 +83,53 @@ class PathWorkbench (Workbench):
|
||||
from PathScripts import PathToolBitCmd
|
||||
from PathScripts import PathToolBitLibraryCmd
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
import PathCommands
|
||||
|
||||
PathGuiInit.Startup()
|
||||
|
||||
# build commands list
|
||||
projcmdlist = ["Path_Job", "Path_Post"]
|
||||
toolcmdlist = ["Path_Inspect", "Path_Simulator", "Path_SelectLoop",
|
||||
"Path_OpActiveToggle"]
|
||||
prepcmdlist = ["Path_Fixture", "Path_Comment", "Path_Stop",
|
||||
"Path_Custom", "Path_Probe"]
|
||||
twodopcmdlist = ["Path_Profile", "Path_Pocket_Shape", "Path_Drilling",
|
||||
"Path_MillFace", "Path_Helix", "Path_Adaptive"]
|
||||
toolcmdlist = [
|
||||
"Path_Inspect",
|
||||
"Path_Simulator",
|
||||
"Path_SelectLoop",
|
||||
"Path_OpActiveToggle",
|
||||
]
|
||||
prepcmdlist = [
|
||||
"Path_Fixture",
|
||||
"Path_Comment",
|
||||
"Path_Stop",
|
||||
"Path_Custom",
|
||||
"Path_Probe",
|
||||
]
|
||||
twodopcmdlist = [
|
||||
"Path_Profile",
|
||||
"Path_Pocket_Shape",
|
||||
"Path_Drilling",
|
||||
"Path_MillFace",
|
||||
"Path_Helix",
|
||||
"Path_Adaptive",
|
||||
]
|
||||
threedopcmdlist = ["Path_Pocket_3D"]
|
||||
engravecmdlist = ["Path_Engrave", "Path_Deburr", "Path_Vcarve"]
|
||||
modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy"]
|
||||
dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary",
|
||||
"Path_DressupDogbone", "Path_DressupDragKnife",
|
||||
"Path_DressupLeadInOut", "Path_DressupRampEntry",
|
||||
"Path_DressupTag", "Path_DressupZCorrect"]
|
||||
dressupcmdlist = [
|
||||
"Path_DressupAxisMap",
|
||||
"Path_DressupPathBoundary",
|
||||
"Path_DressupDogbone",
|
||||
"Path_DressupDragKnife",
|
||||
"Path_DressupLeadInOut",
|
||||
"Path_DressupRampEntry",
|
||||
"Path_DressupTag",
|
||||
"Path_DressupZCorrect",
|
||||
]
|
||||
extracmdlist = []
|
||||
# modcmdmore = ["Path_Hop",]
|
||||
# remotecmdlist = ["Path_Remote"]
|
||||
specialcmdlist = []
|
||||
|
||||
|
||||
if PathPreferences.toolsUseLegacyTools():
|
||||
toolcmdlist.append("Path_ToolLibraryEdit")
|
||||
toolbitcmdlist = []
|
||||
@@ -108,17 +137,20 @@ class PathWorkbench (Workbench):
|
||||
toolcmdlist.extend(PathToolBitLibraryCmd.BarList)
|
||||
toolbitcmdlist = PathToolBitLibraryCmd.MenuList
|
||||
|
||||
|
||||
|
||||
engravecmdgroup = ['Path_EngraveTools']
|
||||
FreeCADGui.addCommand('Path_EngraveTools', PathCommandGroup(engravecmdlist, QtCore.QT_TRANSLATE_NOOP("Path", 'Engraving Operations')))
|
||||
engravecmdgroup = ["Path_EngraveTools"]
|
||||
FreeCADGui.addCommand(
|
||||
"Path_EngraveTools",
|
||||
PathCommandGroup(
|
||||
engravecmdlist, QT_TRANSLATE_NOOP("Path", "Engraving Operations")
|
||||
),
|
||||
)
|
||||
|
||||
threedcmdgroup = threedopcmdlist
|
||||
if PathPreferences.experimentalFeaturesEnabled():
|
||||
projcmdlist.append("Path_Sanity")
|
||||
prepcmdlist.append("Path_Shape")
|
||||
extracmdlist.extend(["Path_Area", "Path_Area_Workplane"])
|
||||
specialcmdlist.append('Path_Thread_Milling')
|
||||
specialcmdlist.append("Path_Thread_Milling")
|
||||
twodopcmdlist.append("Path_Slot")
|
||||
|
||||
if PathPreferences.advancedOCLFeaturesEnabled():
|
||||
@@ -126,38 +158,91 @@ class PathWorkbench (Workbench):
|
||||
import ocl # pylint: disable=unused-variable
|
||||
from PathScripts import PathSurfaceGui
|
||||
from PathScripts import PathWaterlineGui
|
||||
|
||||
threedopcmdlist.extend(["Path_Surface", "Path_Waterline"])
|
||||
threedcmdgroup = ['Path_3dTools']
|
||||
FreeCADGui.addCommand('Path_3dTools', PathCommandGroup(threedopcmdlist, QtCore.QT_TRANSLATE_NOOP("Path", '3D Operations')))
|
||||
threedcmdgroup = ["Path_3dTools"]
|
||||
FreeCADGui.addCommand(
|
||||
"Path_3dTools",
|
||||
PathCommandGroup(
|
||||
threedopcmdlist,
|
||||
QT_TRANSLATE_NOOP("Path", "3D Operations"),
|
||||
),
|
||||
)
|
||||
except ImportError:
|
||||
if not PathPreferences.suppressOpenCamLibWarning():
|
||||
FreeCAD.Console.PrintError("OpenCamLib is not working!\n")
|
||||
|
||||
self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist)
|
||||
self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist)
|
||||
self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "New Operations"), twodopcmdlist+engravecmdgroup+threedcmdgroup)
|
||||
self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist)
|
||||
self.appendToolbar(
|
||||
QT_TRANSLATE_NOOP("Path", "Project Setup"), projcmdlist
|
||||
)
|
||||
self.appendToolbar(
|
||||
QT_TRANSLATE_NOOP("Path", "Tool Commands"), toolcmdlist
|
||||
)
|
||||
self.appendToolbar(
|
||||
QT_TRANSLATE_NOOP("Path", "New Operations"),
|
||||
twodopcmdlist + engravecmdgroup + threedcmdgroup,
|
||||
)
|
||||
self.appendToolbar(
|
||||
QT_TRANSLATE_NOOP("Path", "Path Modification"), modcmdlist
|
||||
)
|
||||
if extracmdlist:
|
||||
self.appendToolbar(QtCore.QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist)
|
||||
self.appendToolbar(
|
||||
QT_TRANSLATE_NOOP("Path", "Helpful Tools"), extracmdlist
|
||||
)
|
||||
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], projcmdlist + ["Path_ExportTemplate", "Separator"] +
|
||||
toolcmdlist + toolbitcmdlist + ["Separator"] + twodopcmdlist + engravecmdlist + ["Separator"] +
|
||||
threedopcmdlist + ["Separator"])
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path", "Path Dressup")], dressupcmdlist)
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path", "Supplemental Commands")], prepcmdlist)
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path", "Path Modification")], modcmdlist)
|
||||
self.appendMenu(
|
||||
[QT_TRANSLATE_NOOP("Path", "&Path")],
|
||||
projcmdlist
|
||||
+ ["Path_ExportTemplate", "Separator"]
|
||||
+ toolcmdlist
|
||||
+ toolbitcmdlist
|
||||
+ ["Separator"]
|
||||
+ twodopcmdlist
|
||||
+ engravecmdlist
|
||||
+ ["Separator"]
|
||||
+ threedopcmdlist
|
||||
+ ["Separator"],
|
||||
)
|
||||
self.appendMenu(
|
||||
[
|
||||
QT_TRANSLATE_NOOP("Path", "&Path"),
|
||||
QT_TRANSLATE_NOOP("Path", "Path Dressup"),
|
||||
],
|
||||
dressupcmdlist,
|
||||
)
|
||||
self.appendMenu(
|
||||
[
|
||||
QT_TRANSLATE_NOOP("Path", "&Path"),
|
||||
QT_TRANSLATE_NOOP("Path", "Supplemental Commands"),
|
||||
],
|
||||
prepcmdlist,
|
||||
)
|
||||
self.appendMenu(
|
||||
[
|
||||
QT_TRANSLATE_NOOP("Path", "&Path"),
|
||||
QT_TRANSLATE_NOOP("Path", "Path Modification"),
|
||||
],
|
||||
modcmdlist,
|
||||
)
|
||||
if specialcmdlist:
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP(
|
||||
"Path", "Specialty Operations")], specialcmdlist)
|
||||
self.appendMenu(
|
||||
[
|
||||
QT_TRANSLATE_NOOP("Path", "&Path"),
|
||||
QT_TRANSLATE_NOOP("Path", "Specialty Operations"),
|
||||
],
|
||||
specialcmdlist,
|
||||
)
|
||||
if extracmdlist:
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist)
|
||||
self.appendMenu([QT_TRANSLATE_NOOP("Path", "&Path")], extracmdlist)
|
||||
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"])
|
||||
self.appendMenu([QtCore.QT_TRANSLATE_NOOP("Path", "&Path"), QtCore.QT_TRANSLATE_NOOP("Path", "Utils")],
|
||||
["Path_PropertyBag"])
|
||||
self.appendMenu([QT_TRANSLATE_NOOP("Path", "&Path")], ["Separator"])
|
||||
self.appendMenu(
|
||||
[
|
||||
QT_TRANSLATE_NOOP("Path", "&Path"),
|
||||
QT_TRANSLATE_NOOP("Path", "Utils"),
|
||||
],
|
||||
["Path_PropertyBag"],
|
||||
)
|
||||
|
||||
self.dressupcmds = dressupcmdlist
|
||||
|
||||
@@ -167,8 +252,11 @@ class PathWorkbench (Workbench):
|
||||
|
||||
# keep this one the last entry in the preferences
|
||||
import PathScripts.PathPreferencesAdvanced as PathPreferencesAdvanced
|
||||
FreeCADGui.addPreferencePage(PathPreferencesAdvanced.AdvancedPreferencesPage, "Path")
|
||||
Log('Loading Path workbench... done\n')
|
||||
|
||||
FreeCADGui.addPreferencePage(
|
||||
PathPreferencesAdvanced.AdvancedPreferencesPage, "Path"
|
||||
)
|
||||
Log("Loading Path workbench... done\n")
|
||||
|
||||
def GetClassName(self):
|
||||
return "Gui::PythonWorkbench"
|
||||
@@ -184,6 +272,7 @@ class PathWorkbench (Workbench):
|
||||
|
||||
def ContextMenu(self, recipient):
|
||||
import PathScripts
|
||||
|
||||
menuAppended = False
|
||||
if len(FreeCADGui.Selection.getSelection()) == 1:
|
||||
obj = FreeCADGui.Selection.getSelection()[0]
|
||||
@@ -194,13 +283,21 @@ class PathWorkbench (Workbench):
|
||||
if "Remote" in selectedName:
|
||||
self.appendContextMenu("", ["Refresh_Path"])
|
||||
if "Job" in selectedName:
|
||||
self.appendContextMenu("", ["Path_ExportTemplate"] + self.toolbitctxmenu)
|
||||
self.appendContextMenu(
|
||||
"", ["Path_ExportTemplate"] + self.toolbitctxmenu
|
||||
)
|
||||
menuAppended = True
|
||||
if isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp):
|
||||
self.appendContextMenu("", ["Path_OperationCopy", "Path_OpActiveToggle"])
|
||||
self.appendContextMenu(
|
||||
"", ["Path_OperationCopy", "Path_OpActiveToggle"]
|
||||
)
|
||||
menuAppended = True
|
||||
if obj.isDerivedFrom("Path::Feature"):
|
||||
if "Profile" in selectedName or "Contour" in selectedName or "Dressup" in selectedName:
|
||||
if (
|
||||
"Profile" in selectedName
|
||||
or "Contour" in selectedName
|
||||
or "Dressup" in selectedName
|
||||
):
|
||||
self.appendContextMenu("", "Separator")
|
||||
# self.appendContextMenu("", ["Set_StartPoint"])
|
||||
# self.appendContextMenu("", ["Set_EndPoint"])
|
||||
@@ -216,7 +313,4 @@ class PathWorkbench (Workbench):
|
||||
|
||||
Gui.addWorkbench(PathWorkbench())
|
||||
|
||||
FreeCAD.addImportType(
|
||||
"GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui")
|
||||
# FreeCAD.addExportType(
|
||||
# "GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui")
|
||||
FreeCAD.addImportType("GCode (*.nc *.gc *.ncc *.ngc *.cnc *.tap *.gcode)", "PathGui")
|
||||
|
||||
@@ -31,13 +31,14 @@ from PathScripts.PathUtils import horizontalFaceLoop
|
||||
from PathScripts.PathUtils import addToJob
|
||||
from PathScripts.PathUtils import findParentJob
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
from PySide import QtGui
|
||||
else:
|
||||
def translate(ctxt, txt):
|
||||
return txt
|
||||
|
||||
# translate = FreeCAD.Qt.translate
|
||||
|
||||
__title__ = "FreeCAD Path Commands"
|
||||
__author__ = "sliptonic"
|
||||
@@ -46,17 +47,22 @@ __url__ = "https://www.freecadweb.org"
|
||||
|
||||
class _CommandSelectLoop:
|
||||
"the Path command to complete loop selection definition"
|
||||
|
||||
def __init__(self):
|
||||
self.obj = None
|
||||
self.sub = []
|
||||
self.active = False
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_SelectLoop',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Finish Selecting Loop"),
|
||||
'Accel': "P, L",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_SelectLoop", "Complete loop selection from two edges"),
|
||||
'CmdType': "ForEdit"}
|
||||
return {
|
||||
"Pixmap": "Path_SelectLoop",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_SelectLoop", "Finish Selecting Loop"),
|
||||
"Accel": "P, L",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_SelectLoop", "Complete loop selection from two edges"
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
@@ -68,7 +74,7 @@ class _CommandSelectLoop:
|
||||
self.obj = sel.Object
|
||||
self.sub = sel.SubElementNames
|
||||
if sel.SubObjects:
|
||||
#self.active = self.formsPartOfALoop(sel.Object, sel.SubObjects[0], sel.SubElementNames)
|
||||
# self.active = self.formsPartOfALoop(sel.Object, sel.SubObjects[0], sel.SubElementNames)
|
||||
self.active = True
|
||||
else:
|
||||
self.active = False
|
||||
@@ -81,11 +87,14 @@ class _CommandSelectLoop:
|
||||
def Activated(self):
|
||||
from PathScripts.PathUtils import horizontalEdgeLoop
|
||||
from PathScripts.PathUtils import horizontalFaceLoop
|
||||
|
||||
sel = FreeCADGui.Selection.getSelectionEx()[0]
|
||||
obj = sel.Object
|
||||
edge1 = sel.SubObjects[0]
|
||||
if 'Face' in sel.SubElementNames[0]:
|
||||
loop = horizontalFaceLoop(sel.Object, sel.SubObjects[0], sel.SubElementNames)
|
||||
if "Face" in sel.SubElementNames[0]:
|
||||
loop = horizontalFaceLoop(
|
||||
sel.Object, sel.SubObjects[0], sel.SubElementNames
|
||||
)
|
||||
if loop:
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCADGui.Selection.addSelection(sel.Object, loop)
|
||||
@@ -102,21 +111,25 @@ class _CommandSelectLoop:
|
||||
for e in elist:
|
||||
for i in loopwire.Edges:
|
||||
if e.hashCode() == i.hashCode():
|
||||
FreeCADGui.Selection.addSelection(obj, "Edge" + str(elist.index(e) + 1))
|
||||
FreeCADGui.Selection.addSelection(
|
||||
obj, "Edge" + str(elist.index(e) + 1)
|
||||
)
|
||||
elif FreeCAD.GuiUp:
|
||||
QtGui.QMessageBox.information(None,
|
||||
QtCore.QT_TRANSLATE_NOOP('Path_SelectLoop', 'Feature Completion'),
|
||||
QtCore.QT_TRANSLATE_NOOP('Path_SelectLoop', 'Closed loop detection failed.'))
|
||||
QtGui.QMessageBox.information(
|
||||
None,
|
||||
QT_TRANSLATE_NOOP("Path_SelectLoop", "Feature Completion"),
|
||||
QT_TRANSLATE_NOOP("Path_SelectLoop", "Closed loop detection failed."),
|
||||
)
|
||||
|
||||
def formsPartOfALoop(self, obj, sub, names):
|
||||
try:
|
||||
if names[0][0:4] != 'Edge':
|
||||
if names[0][0:4] == 'Face' and horizontalFaceLoop(obj, sub, names):
|
||||
if names[0][0:4] != "Edge":
|
||||
if names[0][0:4] == "Face" and horizontalFaceLoop(obj, sub, names):
|
||||
return True
|
||||
return False
|
||||
if len(names) == 1 and horizontalEdgeLoop(obj, sub):
|
||||
return True
|
||||
if len(names) == 1 or names[1][0:4] != 'Edge':
|
||||
if len(names) == 1 or names[1][0:4] != "Edge":
|
||||
return False
|
||||
return True
|
||||
except Exception:
|
||||
@@ -124,17 +137,24 @@ class _CommandSelectLoop:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Path_SelectLoop', _CommandSelectLoop())
|
||||
FreeCADGui.addCommand("Path_SelectLoop", _CommandSelectLoop())
|
||||
|
||||
|
||||
class _ToggleOperation:
|
||||
"command definition to toggle Operation Active state"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_OpActive',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_OpActiveToggle", "Toggle the Active State of the Operation"),
|
||||
'Accel': "P, X",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_OpActiveToggle", "Toggle the Active State of the Operation"),
|
||||
'CmdType': "ForEdit"}
|
||||
return {
|
||||
"Pixmap": "Path_OpActive",
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Path_OpActiveToggle", "Toggle the Active State of the Operation"
|
||||
),
|
||||
"Accel": "P, X",
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_OpActiveToggle", "Toggle the Active State of the Operation"
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
@@ -142,11 +162,12 @@ class _ToggleOperation:
|
||||
try:
|
||||
for sel in FreeCADGui.Selection.getSelectionEx():
|
||||
selProxy = PathScripts.PathDressup.baseOp(sel.Object).Proxy
|
||||
if not isinstance(selProxy, PathScripts.PathOp.ObjectOp) and \
|
||||
not isinstance(selProxy, PathScripts.PathArray.ObjectArray):
|
||||
return False
|
||||
if not isinstance(
|
||||
selProxy, PathScripts.PathOp.ObjectOp
|
||||
) and not isinstance(selProxy, PathScripts.PathArray.ObjectArray):
|
||||
return False
|
||||
return True
|
||||
except(IndexError, AttributeError):
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
@@ -159,16 +180,23 @@ class _ToggleOperation:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Path_OpActiveToggle', _ToggleOperation())
|
||||
FreeCADGui.addCommand("Path_OpActiveToggle", _ToggleOperation())
|
||||
|
||||
|
||||
class _CopyOperation:
|
||||
"the Path Copy Operation command definition"
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_OpCopy',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_OperationCopy", "Copy the operation in the job"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_OperationCopy", "Copy the operation in the job"),
|
||||
'CmdType': "ForEdit"}
|
||||
return {
|
||||
"Pixmap": "Path_OpCopy",
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Path_OperationCopy", "Copy the operation in the job"
|
||||
),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_OperationCopy", "Copy the operation in the job"
|
||||
),
|
||||
"CmdType": "ForEdit",
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
@@ -178,7 +206,7 @@ class _CopyOperation:
|
||||
if not isinstance(sel.Object.Proxy, PathScripts.PathOp.ObjectOp):
|
||||
return False
|
||||
return True
|
||||
except(IndexError, AttributeError):
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
@@ -188,27 +216,27 @@ class _CopyOperation:
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Path_OperationCopy', _CopyOperation())
|
||||
FreeCADGui.addCommand("Path_OperationCopy", _CopyOperation())
|
||||
|
||||
|
||||
# \c findShape() is referenced from Gui/Command.cpp and used by Path.Area commands.
|
||||
# Do not remove!
|
||||
def findShape(shape, subname=None, subtype=None):
|
||||
'''To find a higher order shape containing the subshape with subname.
|
||||
E.g. to find the wire containing 'Edge1' in shape,
|
||||
findShape(shape,'Edge1','Wires')
|
||||
'''
|
||||
"""To find a higher order shape containing the subshape with subname.
|
||||
E.g. to find the wire containing 'Edge1' in shape,
|
||||
findShape(shape,'Edge1','Wires')
|
||||
"""
|
||||
if not subname:
|
||||
return shape
|
||||
ret = shape.getElement(subname)
|
||||
if not subtype or not ret or ret.isNull():
|
||||
return ret
|
||||
if subname.startswith('Face'):
|
||||
tp = 'Faces'
|
||||
elif subname.startswith('Edge'):
|
||||
tp = 'Edges'
|
||||
elif subname.startswith('Vertex'):
|
||||
tp = 'Vertex'
|
||||
if subname.startswith("Face"):
|
||||
tp = "Faces"
|
||||
elif subname.startswith("Edge"):
|
||||
tp = "Edges"
|
||||
elif subname.startswith("Vertex"):
|
||||
tp = "Vertex"
|
||||
else:
|
||||
return ret
|
||||
for obj in getattr(shape, subtype):
|
||||
|
||||
@@ -28,45 +28,115 @@ from PathScripts import PathLog
|
||||
from PySide import QtCore
|
||||
import math
|
||||
import random
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__doc__ = """Path Array object and FreeCAD command"""
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectArray:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLinkList", "Base",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The path(s) to array"))
|
||||
obj.addProperty("App::PropertyEnumeration", "Type",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Pattern method"))
|
||||
obj.addProperty("App::PropertyVectorDistance", "Offset",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The spacing between the array copies in Linear pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "CopiesX",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in X direction in Linear pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "CopiesY",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Y direction in Linear pattern"))
|
||||
obj.addProperty("App::PropertyAngle", "Angle",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Total angle in Polar pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "Copies",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Linear 1D and Polar pattern"))
|
||||
obj.addProperty("App::PropertyVector", "Centre",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The centre of rotation in Polar pattern"))
|
||||
obj.addProperty("App::PropertyBool", "SwapDirection",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Make copies in X direction before Y in Linear 2D pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "JitterPercent",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Percent of copies to randomly offset"))
|
||||
obj.addProperty("App::PropertyVectorDistance", "JitterMagnitude",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Maximum random offset of copies"))
|
||||
obj.addProperty("App::PropertyLink", "ToolController",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
obj.addProperty("App::PropertyBool", "Active",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLinkList",
|
||||
"Base",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The path(s) to array"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"Type",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Pattern method"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVectorDistance",
|
||||
"Offset",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The spacing between the array copies in Linear pattern",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"CopiesX",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in X direction in Linear pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"CopiesY",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in Y direction in Linear pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyAngle",
|
||||
"Angle",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Total angle in Polar pattern"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"Copies",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The number of copies in Linear 1D and Polar pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVector",
|
||||
"Centre",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The centre of rotation in Polar pattern"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"SwapDirection",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Make copies in X direction before Y in Linear 2D pattern",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyInteger",
|
||||
"JitterPercent",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Percent of copies to randomly offset"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyVectorDistance",
|
||||
"JitterMagnitude",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Maximum random offset of copies"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"ToolController",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The tool controller that will be used to calculate the path",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"Active",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"PathOp", "Make False, to prevent operation from generating code"
|
||||
),
|
||||
)
|
||||
|
||||
obj.Active = True
|
||||
obj.Type = ['Linear1D', 'Linear2D', 'Polar']
|
||||
obj.Type = ["Linear1D", "Linear2D", "Polar"]
|
||||
|
||||
self.setEditorModes(obj)
|
||||
obj.Proxy = self
|
||||
@@ -78,26 +148,26 @@ class ObjectArray:
|
||||
return None
|
||||
|
||||
def setEditorModes(self, obj):
|
||||
if obj.Type == 'Linear1D':
|
||||
if obj.Type == "Linear1D":
|
||||
angleMode = centreMode = copiesXMode = copiesYMode = swapDirectionMode = 2
|
||||
copiesMode = offsetMode = 0
|
||||
elif obj.Type == 'Linear2D':
|
||||
elif obj.Type == "Linear2D":
|
||||
angleMode = copiesMode = centreMode = 2
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 0
|
||||
elif obj.Type == 'Polar':
|
||||
elif obj.Type == "Polar":
|
||||
angleMode = copiesMode = centreMode = 0
|
||||
copiesXMode = copiesYMode = offsetMode = swapDirectionMode = 2
|
||||
|
||||
obj.setEditorMode('Angle', angleMode)
|
||||
obj.setEditorMode('Copies', copiesMode)
|
||||
obj.setEditorMode('Centre', centreMode)
|
||||
obj.setEditorMode('CopiesX', copiesXMode)
|
||||
obj.setEditorMode('CopiesY', copiesYMode)
|
||||
obj.setEditorMode('Offset', offsetMode)
|
||||
obj.setEditorMode('SwapDirection', swapDirectionMode)
|
||||
obj.setEditorMode('JitterPercent', 0)
|
||||
obj.setEditorMode('JitterMagnitude', 0)
|
||||
obj.setEditorMode('ToolController', 2)
|
||||
obj.setEditorMode("Angle", angleMode)
|
||||
obj.setEditorMode("Copies", copiesMode)
|
||||
obj.setEditorMode("Centre", centreMode)
|
||||
obj.setEditorMode("CopiesX", copiesXMode)
|
||||
obj.setEditorMode("CopiesY", copiesYMode)
|
||||
obj.setEditorMode("Offset", offsetMode)
|
||||
obj.setEditorMode("SwapDirection", swapDirectionMode)
|
||||
obj.setEditorMode("JitterPercent", 0)
|
||||
obj.setEditorMode("JitterMagnitude", 0)
|
||||
obj.setEditorMode("ToolController", 2)
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop == "Type":
|
||||
@@ -107,31 +177,41 @@ class ObjectArray:
|
||||
"""onDocumentRestored(obj) ... Called automatically when document is restored."""
|
||||
|
||||
if not hasattr(obj, "Active"):
|
||||
obj.addProperty("App::PropertyBool", "Active",
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("PathOp", "Make False, to prevent operation from generating code"))
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"Active",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"PathOp", "Make False, to prevent operation from generating code"
|
||||
),
|
||||
)
|
||||
obj.Active = True
|
||||
|
||||
self.setEditorModes(obj)
|
||||
|
||||
def rotatePath(self, path, angle, centre):
|
||||
'''
|
||||
Rotates Path around given centre vector
|
||||
Only X and Y is considered
|
||||
'''
|
||||
CmdMoveRapid = ['G0', 'G00']
|
||||
CmdMoveStraight = ['G1', 'G01']
|
||||
CmdMoveCW = ['G2', 'G02']
|
||||
CmdMoveCCW = ['G3', 'G03']
|
||||
CmdDrill = ['G81', 'G82', 'G83']
|
||||
CmdMoveArc = CmdMoveCW + CmdMoveCCW
|
||||
CmdMove = CmdMoveStraight + CmdMoveArc
|
||||
"""
|
||||
Rotates Path around given centre vector
|
||||
Only X and Y is considered
|
||||
"""
|
||||
CmdMoveRapid = ["G0", "G00"]
|
||||
CmdMoveStraight = ["G1", "G01"]
|
||||
CmdMoveCW = ["G2", "G02"]
|
||||
CmdMoveCCW = ["G3", "G03"]
|
||||
CmdDrill = ["G81", "G82", "G83"]
|
||||
CmdMoveArc = CmdMoveCW + CmdMoveCCW
|
||||
CmdMove = CmdMoveStraight + CmdMoveArc
|
||||
|
||||
commands = []
|
||||
ang = angle / 180 * math.pi
|
||||
currX = 0
|
||||
currY = 0
|
||||
for cmd in path.Commands:
|
||||
if (cmd.Name in CmdMoveRapid) or (cmd.Name in CmdMove) or (cmd.Name in CmdDrill):
|
||||
if (
|
||||
(cmd.Name in CmdMoveRapid)
|
||||
or (cmd.Name in CmdMove)
|
||||
or (cmd.Name in CmdDrill)
|
||||
):
|
||||
params = cmd.Parameters
|
||||
x = params.get("X")
|
||||
if x is None:
|
||||
@@ -151,7 +231,7 @@ class ObjectArray:
|
||||
ny = y * math.cos(ang) + x * math.sin(ang)
|
||||
|
||||
# "move" the centre back and update
|
||||
params.update({'X': nx + centre.x, 'Y': ny + centre.y})
|
||||
params.update({"X": nx + centre.x, "Y": ny + centre.y})
|
||||
|
||||
# Arcs need to have the I and J params rotated as well
|
||||
if cmd.Name in CmdMoveArc:
|
||||
@@ -164,7 +244,7 @@ class ObjectArray:
|
||||
|
||||
ni = i * math.cos(ang) - j * math.sin(ang)
|
||||
nj = j * math.cos(ang) + i * math.sin(ang)
|
||||
params.update({'I': ni, 'J': nj})
|
||||
params.update({"I": ni, "J": nj})
|
||||
|
||||
cmd.Parameters = params
|
||||
commands.append(cmd)
|
||||
@@ -184,15 +264,26 @@ class ObjectArray:
|
||||
|
||||
obj.ToolController = base[0].ToolController
|
||||
|
||||
# Do not generate paths and clear current Path data if operation not
|
||||
# Do not generate paths and clear current Path data if operation not
|
||||
if not obj.Active:
|
||||
if obj.Path:
|
||||
obj.Path = Path.Path()
|
||||
return
|
||||
|
||||
pa = PathArray(obj.Base, obj.Type, obj.Copies, obj.Offset,
|
||||
obj.CopiesX, obj.CopiesY, obj.Angle, obj.Centre, obj.SwapDirection,
|
||||
obj.JitterMagnitude, obj.JitterPercent, obj.Name)
|
||||
pa = PathArray(
|
||||
obj.Base,
|
||||
obj.Type,
|
||||
obj.Copies,
|
||||
obj.Offset,
|
||||
obj.CopiesX,
|
||||
obj.CopiesY,
|
||||
obj.Angle,
|
||||
obj.Centre,
|
||||
obj.SwapDirection,
|
||||
obj.JitterMagnitude,
|
||||
obj.JitterPercent,
|
||||
obj.Name,
|
||||
)
|
||||
obj.Path = pa.getPath()
|
||||
|
||||
|
||||
@@ -201,10 +292,21 @@ class PathArray:
|
||||
This class receives one or more base operations and repeats those operations
|
||||
at set intervals based upon array type requested and the related settings for that type."""
|
||||
|
||||
def __init__(self, baseList, arrayType, copies, offsetVector,
|
||||
copiesX, copiesY, angle, centre, swapDirection,
|
||||
jitterMagnitude=FreeCAD.Vector(0, 0, 0), jitterPercent=0,
|
||||
seed='FreeCAD'):
|
||||
def __init__(
|
||||
self,
|
||||
baseList,
|
||||
arrayType,
|
||||
copies,
|
||||
offsetVector,
|
||||
copiesX,
|
||||
copiesY,
|
||||
angle,
|
||||
centre,
|
||||
swapDirection,
|
||||
jitterMagnitude=FreeCAD.Vector(0, 0, 0),
|
||||
jitterPercent=0,
|
||||
seed="FreeCAD",
|
||||
):
|
||||
self.baseList = list()
|
||||
self.arrayType = arrayType # ['Linear1D', 'Linear2D', 'Polar']
|
||||
self.copies = copies
|
||||
@@ -230,10 +332,16 @@ class PathArray:
|
||||
Returns the position argument with a random vector shift applied."""
|
||||
if self.jitterPercent == 0:
|
||||
pass
|
||||
elif random.randint(0,100) < self.jitterPercent:
|
||||
pos.x = pos.x + random.uniform(-self.jitterMagnitude.x, self.jitterMagnitude.y)
|
||||
pos.y = pos.y + random.uniform(-self.jitterMagnitude.y, self.jitterMagnitude.y)
|
||||
pos.z = pos.z + random.uniform(-self.jitterMagnitude.z, self.jitterMagnitude.z)
|
||||
elif random.randint(0, 100) < self.jitterPercent:
|
||||
pos.x = pos.x + random.uniform(
|
||||
-self.jitterMagnitude.x, self.jitterMagnitude.y
|
||||
)
|
||||
pos.y = pos.y + random.uniform(
|
||||
-self.jitterMagnitude.y, self.jitterMagnitude.y
|
||||
)
|
||||
pos.z = pos.z + random.uniform(
|
||||
-self.jitterMagnitude.z, self.jitterMagnitude.z
|
||||
)
|
||||
return pos
|
||||
|
||||
# Public method
|
||||
@@ -255,32 +363,48 @@ class PathArray:
|
||||
return
|
||||
if b.ToolController != base[0].ToolController:
|
||||
# this may be important if Job output is split by tool controller
|
||||
PathLog.warning(translate("PathArray", "Arrays of paths having different tool controllers are handled according to the tool controller of the first path."))
|
||||
PathLog.warning(
|
||||
translate(
|
||||
"PathArray",
|
||||
"Arrays of paths having different tool controllers are handled according to the tool controller of the first path.",
|
||||
)
|
||||
)
|
||||
|
||||
# build copies
|
||||
output = ""
|
||||
random.seed(self.seed)
|
||||
|
||||
if self.arrayType == 'Linear1D':
|
||||
if self.arrayType == "Linear1D":
|
||||
for i in range(self.copies):
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * (i + 1), self.offsetVector.y * (i + 1), self.offsetVector.z * (i + 1))
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * (i + 1),
|
||||
self.offsetVector.y * (i + 1),
|
||||
self.offsetVector.z * (i + 1),
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl)
|
||||
for cm in b.Path.Commands])
|
||||
np = Path.Path([cm.transform(pl) for cm in b.Path.Commands])
|
||||
output += np.toGCode()
|
||||
|
||||
elif self.arrayType == 'Linear2D':
|
||||
elif self.arrayType == "Linear2D":
|
||||
if self.swapDirection:
|
||||
for i in range(self.copiesY + 1):
|
||||
for j in range(self.copiesX + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * j, self.offsetVector.y * i, self.offsetVector.z * i)
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * j,
|
||||
self.offsetVector.y * i,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
else:
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * (self.copiesX - j), self.offsetVector.y * i, self.offsetVector.z * i)
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * (self.copiesX - j),
|
||||
self.offsetVector.y * i,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
@@ -288,15 +412,25 @@ class PathArray:
|
||||
# do not process the index 0,0. It will be processed by the base Paths themselves
|
||||
if not (i == 0 and j == 0):
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl) for cm in b.Path.Commands])
|
||||
np = Path.Path(
|
||||
[cm.transform(pl) for cm in b.Path.Commands]
|
||||
)
|
||||
output += np.toGCode()
|
||||
else:
|
||||
for i in range(self.copiesX + 1):
|
||||
for j in range(self.copiesY + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * j, self.offsetVector.z * i)
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * i,
|
||||
self.offsetVector.y * j,
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
else:
|
||||
pos = FreeCAD.Vector(self.offsetVector.x * i, self.offsetVector.y * (self.copiesY - j), self.offsetVector.z * i)
|
||||
pos = FreeCAD.Vector(
|
||||
self.offsetVector.x * i,
|
||||
self.offsetVector.y * (self.copiesY - j),
|
||||
self.offsetVector.z * i,
|
||||
)
|
||||
pos = self._calculateJitter(pos)
|
||||
|
||||
for b in base:
|
||||
@@ -304,7 +438,9 @@ class PathArray:
|
||||
# do not process the index 0,0. It will be processed by the base Paths themselves
|
||||
if not (i == 0 and j == 0):
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl) for cm in b.Path.Commands])
|
||||
np = Path.Path(
|
||||
[cm.transform(pl) for cm in b.Path.Commands]
|
||||
)
|
||||
output += np.toGCode()
|
||||
# Eif
|
||||
else:
|
||||
@@ -321,7 +457,6 @@ class PathArray:
|
||||
|
||||
|
||||
class ViewProviderArray:
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
@@ -345,11 +480,14 @@ class ViewProviderArray:
|
||||
|
||||
|
||||
class CommandPathArray:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Array',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Array"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")}
|
||||
return {
|
||||
"Pixmap": "Path_Array",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Array", "Array"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_Array", "Creates an array from selected path(s)"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
@@ -357,7 +495,7 @@ class CommandPathArray:
|
||||
try:
|
||||
obj = FreeCADGui.Selection.getSelectionEx()[0].Object
|
||||
return isinstance(obj.Proxy, PathScripts.PathOp.ObjectOp)
|
||||
except(IndexError, AttributeError):
|
||||
except (IndexError, AttributeError):
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
@@ -366,9 +504,13 @@ class CommandPathArray:
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
|
||||
for sel in selection:
|
||||
if not(sel.isDerivedFrom("Path::Feature")):
|
||||
if not (sel.isDerivedFrom("Path::Feature")):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Array", "Arrays can be created only from Path operations.")+"\n")
|
||||
translate(
|
||||
"Path_Array", "Arrays can be created only from Path operations."
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
return
|
||||
|
||||
# if everything is ok, execute and register the transaction in the
|
||||
@@ -377,19 +519,23 @@ class CommandPathArray:
|
||||
FreeCADGui.addModule("PathScripts.PathArray")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")')
|
||||
FreeCADGui.doCommand(
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Array")'
|
||||
)
|
||||
|
||||
FreeCADGui.doCommand('PathScripts.PathArray.ObjectArray(obj)')
|
||||
FreeCADGui.doCommand("PathScripts.PathArray.ObjectArray(obj)")
|
||||
|
||||
baseString = "[%s]" % ','.join(["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection])
|
||||
FreeCADGui.doCommand('obj.Base = %s' % baseString)
|
||||
baseString = "[%s]" % ",".join(
|
||||
["FreeCAD.ActiveDocument.%s" % sel.Name for sel in selection]
|
||||
)
|
||||
FreeCADGui.doCommand("obj.Base = %s" % baseString)
|
||||
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy = 0')
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
FreeCADGui.doCommand("obj.ViewObject.Proxy = 0")
|
||||
FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Array', CommandPathArray())
|
||||
FreeCADGui.addCommand("Path_Array", CommandPathArray())
|
||||
|
||||
@@ -24,16 +24,12 @@ import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
from PySide import QtCore
|
||||
from PathScripts.PathUtils import waiting_effects
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
LOG_MODULE = 'PathCollision'
|
||||
LOG_MODULE = "PathCollision"
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, LOG_MODULE)
|
||||
PathLog.trackModule('PathCollision')
|
||||
FreeCAD.setLogLevel('Path.Area', 0)
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
PathLog.trackModule("PathCollision")
|
||||
FreeCAD.setLogLevel("Path.Area", 0)
|
||||
|
||||
__title__ = "Path Collision Utility"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -44,19 +40,25 @@ __url__ = "https://www.freecadweb.org"
|
||||
|
||||
class _CollisionSim:
|
||||
def __init__(self, obj):
|
||||
#obj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to"))
|
||||
obj.Proxy = self
|
||||
|
||||
def execute(self, fp):
|
||||
'''Do something when doing a recomputation, this method is mandatory'''
|
||||
print('_CollisionSim', fp)
|
||||
"""Do something when doing a recomputation, this method is mandatory"""
|
||||
print("_CollisionSim", fp)
|
||||
|
||||
|
||||
class _ViewProviderCollisionSim:
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
vobj.addProperty("App::PropertyLink", "Original", "reference", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base object this collision refers to"))
|
||||
vobj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Original",
|
||||
"reference",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The base object this collision refers to"
|
||||
),
|
||||
)
|
||||
|
||||
def attach(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
@@ -82,12 +84,14 @@ class _ViewProviderCollisionSim:
|
||||
|
||||
|
||||
def __compareBBSpace(bb1, bb2):
|
||||
if (bb1.XMin == bb2.XMin and
|
||||
bb1.XMax == bb2.XMax and
|
||||
bb1.YMin == bb2.YMin and
|
||||
bb1.YMax == bb2.YMax and
|
||||
bb1.ZMin == bb2.ZMin and
|
||||
bb1.ZMax == bb2.ZMax):
|
||||
if (
|
||||
bb1.XMin == bb2.XMin
|
||||
and bb1.XMax == bb2.XMax
|
||||
and bb1.YMin == bb2.YMin
|
||||
and bb1.YMax == bb2.YMax
|
||||
and bb1.ZMin == bb2.ZMin
|
||||
and bb1.ZMax == bb2.ZMax
|
||||
):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -124,6 +128,3 @@ def getCollisionObject(baseobject, simobject):
|
||||
obj.ViewObject.Original = baseobject
|
||||
|
||||
return result
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -20,25 +20,29 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
'''Used for CNC machine comments for Path module. Create a comment and place it in the Document tree.'''
|
||||
"""Used for CNC machine comments for Path module. Create a comment and place it in the Document tree."""
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
from PySide import QtCore
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class Comment:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyString", "Comment",
|
||||
"Path", "Comment or note for CNC program")
|
||||
obj.addProperty(
|
||||
"App::PropertyString",
|
||||
"Comment",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Comment or note for CNC program"),
|
||||
)
|
||||
obj.Proxy = self
|
||||
mode = 2
|
||||
obj.setEditorMode('Placement', mode)
|
||||
obj.setEditorMode("Placement", mode)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
@@ -51,25 +55,24 @@ class Comment:
|
||||
|
||||
def execute(self, obj):
|
||||
output = ""
|
||||
output += '(' + str(obj.Comment) + ')\n'
|
||||
output += "(" + str(obj.Comment) + ")\n"
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
class _ViewProviderComment:
|
||||
|
||||
def __init__(self, vobj): # mandatory
|
||||
vobj.Proxy = self
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def __getstate__(self): # mandatory
|
||||
return None
|
||||
@@ -83,23 +86,26 @@ class _ViewProviderComment:
|
||||
def onChanged(self, vobj, prop): # optional
|
||||
# pylint: disable=unused-argument
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
|
||||
class CommandPathComment:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Comment',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Comment"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Comment", "Add a Comment to your CNC program")}
|
||||
return {
|
||||
"Pixmap": "Path_Comment",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Comment", "Comment"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_Comment", "Add a Comment to your CNC program"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -109,10 +115,9 @@ class CommandPathComment:
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("Path_Comment", "Create a Comment in your CNC program"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create a Comment in your CNC program")
|
||||
FreeCADGui.addModule("PathScripts.PathComment")
|
||||
snippet = '''
|
||||
snippet = """
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathUtils
|
||||
@@ -121,14 +126,15 @@ PathScripts.PathComment.Comment(obj)
|
||||
PathScripts.PathComment._ViewProviderComment(obj.ViewObject)
|
||||
|
||||
PathUtils.addToJob(obj)
|
||||
'''
|
||||
"""
|
||||
FreeCADGui.doCommand(snippet)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Comment', CommandPathComment())
|
||||
FreeCADGui.addCommand("Path_Comment", CommandPathComment())
|
||||
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathComment... done\n")
|
||||
|
||||
@@ -24,18 +24,31 @@ import FreeCAD
|
||||
import FreeCADGui
|
||||
from PySide import QtCore
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__doc__ = """Path Copy object and FreeCAD command"""
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectPathCopy:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The path to be copied"))
|
||||
obj.addProperty("App::PropertyLink", "ToolController", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The tool controller that will be used to calculate the path"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Base",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The path to be copied"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"ToolController",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"The tool controller that will be used to calculate the path",
|
||||
),
|
||||
)
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -46,14 +59,13 @@ class ObjectPathCopy:
|
||||
|
||||
def execute(self, obj):
|
||||
if obj.Base:
|
||||
if hasattr(obj.Base, 'ToolController'):
|
||||
if hasattr(obj.Base, "ToolController"):
|
||||
obj.ToolController = obj.Base.ToolController
|
||||
if obj.Base.Path:
|
||||
obj.Path = obj.Base.Path.copy()
|
||||
|
||||
|
||||
class ViewProviderPathCopy:
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
@@ -73,11 +85,14 @@ class ViewProviderPathCopy:
|
||||
|
||||
|
||||
class CommandPathCopy:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Copy',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Copy"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Copy", "Creates a linked copy of another path")}
|
||||
return {
|
||||
"Pixmap": "Path_Copy",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Copy", "Copy"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_Copy", "Creates a linked copy of another path"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -88,11 +103,10 @@ class CommandPathCopy:
|
||||
|
||||
def Activated(self):
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("Path_Copy", "Create Copy"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Copy")
|
||||
FreeCADGui.addModule("PathScripts.PathCopy")
|
||||
|
||||
consolecode = '''
|
||||
consolecode = """
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathCopy
|
||||
@@ -123,7 +137,7 @@ proj.Group = g
|
||||
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
FreeCADGui.doCommand(consolecode)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
@@ -132,6 +146,6 @@ FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Copy', CommandPathCopy())
|
||||
FreeCADGui.addCommand("Path_Copy", CommandPathCopy())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathCopy... done\n")
|
||||
|
||||
@@ -26,7 +26,7 @@ import Path
|
||||
import PathScripts.PathOp as PathOp
|
||||
import PathScripts.PathLog as PathLog
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__title__ = "Path Custom Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -34,13 +34,14 @@ __url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Path Custom object and FreeCAD command"
|
||||
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectCustom(PathOp.ObjectOp):
|
||||
@@ -48,8 +49,12 @@ class ObjectCustom(PathOp.ObjectOp):
|
||||
return PathOp.FeatureTool | PathOp.FeatureCoolant
|
||||
|
||||
def initOperation(self, obj):
|
||||
obj.addProperty("App::PropertyStringList", "Gcode", "Path",
|
||||
QtCore.QT_TRANSLATE_NOOP("PathCustom", "The gcode to be inserted"))
|
||||
obj.addProperty(
|
||||
"App::PropertyStringList",
|
||||
"Gcode",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The gcode to be inserted"),
|
||||
)
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
@@ -69,7 +74,7 @@ def SetupProperties():
|
||||
|
||||
|
||||
def Create(name, obj=None, parentJob=None):
|
||||
'''Create(name) ... Creates and returns a Custom operation.'''
|
||||
"""Create(name) ... Creates and returns a Custom operation."""
|
||||
if obj is None:
|
||||
obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython", name)
|
||||
obj.Proxy = ObjectCustom(obj, name, parentJob)
|
||||
|
||||
@@ -25,7 +25,8 @@ import FreeCADGui
|
||||
import PathScripts.PathCustom as PathCustom
|
||||
import PathScripts.PathOpGui as PathOpGui
|
||||
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
__title__ = "Path Custom Operation UI"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -33,38 +34,26 @@ __url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Custom operation page controller and command implementation."
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
# class TaskPanelBaseGeometryPage(PathOpGui.TaskPanelBaseGeometryPage):
|
||||
# '''Page controller for the base geometry.'''
|
||||
|
||||
# def getForm(self):
|
||||
# return None
|
||||
|
||||
|
||||
class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
'''Page controller class for the Custom operation.'''
|
||||
"""Page controller class for the Custom operation."""
|
||||
|
||||
def getForm(self):
|
||||
'''getForm() ... returns UI'''
|
||||
"""getForm() ... returns UI"""
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpCustomEdit.ui")
|
||||
|
||||
def getFields(self, obj):
|
||||
'''getFields(obj) ... transfers values from UI to obj's properties'''
|
||||
"""getFields(obj) ... transfers values from UI to obj's properties"""
|
||||
self.updateToolController(obj, self.form.toolController)
|
||||
self.updateCoolant(obj, self.form.coolantController)
|
||||
|
||||
def setFields(self, obj):
|
||||
'''setFields(obj) ... transfers obj's property values to UI'''
|
||||
"""setFields(obj) ... transfers obj's property values to UI"""
|
||||
self.setupToolController(obj, self.form.toolController)
|
||||
self.form.txtGCode.setText("\n".join(obj.Gcode))
|
||||
self.setupCoolant(obj, self.form.coolantController)
|
||||
|
||||
def getSignalsForUpdate(self, obj):
|
||||
'''getSignalsForUpdate(obj) ... return list of signals for updating obj'''
|
||||
"""getSignalsForUpdate(obj) ... return list of signals for updating obj"""
|
||||
signals = []
|
||||
signals.append(self.form.toolController.currentIndexChanged)
|
||||
signals.append(self.form.coolantController.currentIndexChanged)
|
||||
@@ -75,10 +64,14 @@ class TaskPanelOpPage(PathOpGui.TaskPanelPage):
|
||||
self.obj.Gcode = self.form.txtGCode.toPlainText().splitlines()
|
||||
|
||||
|
||||
Command = PathOpGui.SetupOperation('Custom', PathCustom.Create, TaskPanelOpPage,
|
||||
'Path_Custom',
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Custom"),
|
||||
QtCore.QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"),
|
||||
PathCustom.SetupProperties)
|
||||
Command = PathOpGui.SetupOperation(
|
||||
"Custom",
|
||||
PathCustom.Create,
|
||||
TaskPanelOpPage,
|
||||
"Path_Custom",
|
||||
QT_TRANSLATE_NOOP("Path_Custom", "Custom"),
|
||||
QT_TRANSLATE_NOOP("Path_Custom", "Create custom gcode snippet"),
|
||||
PathCustom.SetupProperties,
|
||||
)
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathCustomGui... done\n")
|
||||
|
||||
@@ -27,10 +27,12 @@ from PySide import QtCore
|
||||
import math
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import PathScripts.PathGui as PathGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
D = LazyLoader('DraftVecUtils', globals(), 'DraftVecUtils')
|
||||
|
||||
D = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils")
|
||||
|
||||
__doc__ = """Dragknife Dressup object and FreeCAD command"""
|
||||
|
||||
@@ -38,25 +40,47 @@ if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
movecommands = ['G1', 'G01', 'G2', 'G02', 'G3', 'G03']
|
||||
rapidcommands = ['G0', 'G00']
|
||||
arccommands = ['G2', 'G3', 'G02', 'G03']
|
||||
movecommands = ["G1", "G01", "G2", "G02", "G3", "G03"]
|
||||
rapidcommands = ["G0", "G00"]
|
||||
arccommands = ["G2", "G3", "G02", "G03"]
|
||||
|
||||
currLocation = {}
|
||||
|
||||
|
||||
class ObjectDressup:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLink", "Base", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "The base path to modify"))
|
||||
obj.addProperty("App::PropertyAngle", "filterAngle", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Angles less than filter angle will not receive corner actions"))
|
||||
obj.addProperty("App::PropertyFloat", "offset", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Distance the point trails behind the spindle"))
|
||||
obj.addProperty("App::PropertyFloat", "pivotheight", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property", "Height to raise during corner action"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Base",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The base path to modify"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyAngle",
|
||||
"filterAngle",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Angles less than filter angle will not receive corner actions",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"offset",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Distance the point trails behind the spindle"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyFloat",
|
||||
"pivotheight",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Height to raise during corner action"),
|
||||
)
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
@@ -67,13 +91,17 @@ class ObjectDressup:
|
||||
return None
|
||||
|
||||
def shortcut(self, queue):
|
||||
'''Determines whether its shorter to twist CW or CCW to align with
|
||||
the next move'''
|
||||
"""Determines whether its shorter to twist CW or CCW to align with
|
||||
the next move"""
|
||||
# get the vector of the last move
|
||||
|
||||
if queue[1].Name in arccommands:
|
||||
arcLoc = FreeCAD.Vector(queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation['Z'])
|
||||
radvector = arcLoc.sub(queue[1].Placement.Base) # .sub(arcLoc) # vector of chord from center to point
|
||||
arcLoc = FreeCAD.Vector(
|
||||
queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation["Z"]
|
||||
)
|
||||
radvector = arcLoc.sub(
|
||||
queue[1].Placement.Base
|
||||
) # .sub(arcLoc) # vector of chord from center to point
|
||||
# vector of line perp to chord.
|
||||
v1 = radvector.cross(FreeCAD.Vector(0, 0, 1))
|
||||
else:
|
||||
@@ -81,7 +109,9 @@ class ObjectDressup:
|
||||
|
||||
# get the vector of the current move
|
||||
if queue[0].Name in arccommands:
|
||||
arcLoc = FreeCAD.Vector((queue[1].x + queue[0].I), (queue[1].y + queue[0].J), currLocation['Z'])
|
||||
arcLoc = FreeCAD.Vector(
|
||||
(queue[1].x + queue[0].I), (queue[1].y + queue[0].J), currLocation["Z"]
|
||||
)
|
||||
radvector = queue[1].Placement.Base.sub(arcLoc) # calculate arcangle
|
||||
v2 = radvector.cross(FreeCAD.Vector(0, 0, 1))
|
||||
else:
|
||||
@@ -93,24 +123,36 @@ class ObjectDressup:
|
||||
return "CCW"
|
||||
|
||||
def segmentAngleXY(self, prevCommand, currCommand, endpos=False, currentZ=0):
|
||||
'''returns in the starting angle in radians for a Path command.
|
||||
"""returns in the starting angle in radians for a Path command.
|
||||
requires the previous command in order to calculate arcs correctly
|
||||
if endpos = True, return the angle at the end of the segment.'''
|
||||
if endpos = True, return the angle at the end of the segment."""
|
||||
|
||||
if currCommand.Name in arccommands:
|
||||
arcLoc = FreeCAD.Vector((prevCommand.x + currCommand.I), (prevCommand.y + currCommand.J), currentZ)
|
||||
arcLoc = FreeCAD.Vector(
|
||||
(prevCommand.x + currCommand.I),
|
||||
(prevCommand.y + currCommand.J),
|
||||
currentZ,
|
||||
)
|
||||
if endpos is True:
|
||||
radvector = arcLoc.sub(currCommand.Placement.Base) # Calculate vector at start of arc
|
||||
radvector = arcLoc.sub(
|
||||
currCommand.Placement.Base
|
||||
) # Calculate vector at start of arc
|
||||
else:
|
||||
radvector = arcLoc.sub(prevCommand.Placement.Base) # Calculate vector at end of arc
|
||||
radvector = arcLoc.sub(
|
||||
prevCommand.Placement.Base
|
||||
) # Calculate vector at end of arc
|
||||
|
||||
v1 = radvector.cross(FreeCAD.Vector(0, 0, 1))
|
||||
if currCommand.Name in ["G2", "G02"]:
|
||||
v1 = D.rotate2D(v1, math.radians(180))
|
||||
else:
|
||||
v1 = currCommand.Placement.Base.sub(prevCommand.Placement.Base) # Straight segments are easy
|
||||
v1 = currCommand.Placement.Base.sub(
|
||||
prevCommand.Placement.Base
|
||||
) # Straight segments are easy
|
||||
|
||||
myAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1))
|
||||
myAngle = D.angle(
|
||||
v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)
|
||||
)
|
||||
return myAngle
|
||||
|
||||
def getIncidentAngle(self, queue):
|
||||
@@ -123,18 +165,20 @@ class ObjectDressup:
|
||||
if angleatstart < 0:
|
||||
angleatstart = 360 + angleatstart
|
||||
|
||||
incident_angle = angleatend-angleatstart
|
||||
incident_angle = angleatend - angleatstart
|
||||
|
||||
return incident_angle
|
||||
|
||||
def arcExtension(self, obj, queue):
|
||||
'''returns gcode for arc extension'''
|
||||
"""returns gcode for arc extension"""
|
||||
global currLocation # pylint: disable=global-statement
|
||||
results = []
|
||||
|
||||
offset = obj.offset
|
||||
# Find the center of the old arc
|
||||
C = FreeCAD.Base.Vector(queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation['Z'])
|
||||
C = FreeCAD.Base.Vector(
|
||||
queue[2].x + queue[1].I, queue[2].y + queue[1].J, currLocation["Z"]
|
||||
)
|
||||
|
||||
# Find radius of old arc
|
||||
R = math.hypot(queue[1].I, queue[1].J)
|
||||
@@ -165,8 +209,8 @@ class ObjectDressup:
|
||||
return (results, replace)
|
||||
|
||||
def arcTwist(self, obj, queue, lastXY, twistCW=False):
|
||||
'''returns gcode to do an arc move toward an arc to perform
|
||||
a corner action twist. Includes lifting and plungeing the knife'''
|
||||
"""returns gcode to do an arc move toward an arc to perform
|
||||
a corner action twist. Includes lifting and plungeing the knife"""
|
||||
|
||||
global currLocation # pylint: disable=global-statement
|
||||
pivotheight = obj.pivotheight
|
||||
@@ -186,7 +230,9 @@ class ObjectDressup:
|
||||
currLocation.update(retract.Parameters)
|
||||
|
||||
# get the center of the destination arc
|
||||
arccenter = FreeCAD.Base.Vector(queue[1].x + queue[0].I, queue[1].y + queue[0].J, currLocation["Z"])
|
||||
arccenter = FreeCAD.Base.Vector(
|
||||
queue[1].x + queue[0].I, queue[1].y + queue[0].J, currLocation["Z"]
|
||||
)
|
||||
|
||||
# The center of the twist arc is the old line end point.
|
||||
C = queue[1].Placement.Base
|
||||
@@ -196,7 +242,9 @@ class ObjectDressup:
|
||||
|
||||
# find angle of original center to startpoint
|
||||
v1 = queue[1].Placement.Base.sub(arccenter)
|
||||
segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1))
|
||||
segAngle = D.angle(
|
||||
v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)
|
||||
)
|
||||
|
||||
# Find angle subtended by the offset
|
||||
theta = offset / R
|
||||
@@ -210,14 +258,21 @@ class ObjectDressup:
|
||||
# calculate endpoints
|
||||
Bx = arccenter.x + R * math.cos(newangle)
|
||||
By = arccenter.y + R * math.sin(newangle)
|
||||
endpointvector = FreeCAD.Base.Vector(Bx, By, currLocation['Z'])
|
||||
endpointvector = FreeCAD.Base.Vector(Bx, By, currLocation["Z"])
|
||||
|
||||
# calculate IJ offsets of twist arc from current position.
|
||||
offsetvector = C.sub(lastXY)
|
||||
|
||||
# add G2/G3 move
|
||||
arcmove = Path.Command(
|
||||
arcdir, {"X": endpointvector.x, "Y": endpointvector.y, "I": offsetvector.x, "J": offsetvector.y})
|
||||
arcdir,
|
||||
{
|
||||
"X": endpointvector.x,
|
||||
"Y": endpointvector.y,
|
||||
"I": offsetvector.x,
|
||||
"J": offsetvector.y,
|
||||
},
|
||||
)
|
||||
results.append(arcmove)
|
||||
currLocation.update(arcmove.Parameters)
|
||||
|
||||
@@ -230,11 +285,13 @@ class ObjectDressup:
|
||||
offsetv = arccenter.sub(endpointvector)
|
||||
|
||||
replace = Path.Command(
|
||||
queue[0].Name, {"X": queue[0].X, "Y": queue[0].Y, "I": offsetv.x, "J": offsetv.y})
|
||||
queue[0].Name,
|
||||
{"X": queue[0].X, "Y": queue[0].Y, "I": offsetv.x, "J": offsetv.y},
|
||||
)
|
||||
return (results, replace)
|
||||
|
||||
def lineExtension(self, obj, queue):
|
||||
'''returns gcode for line extension'''
|
||||
"""returns gcode for line extension"""
|
||||
global currLocation # pylint: disable=global-statement
|
||||
|
||||
offset = float(obj.offset)
|
||||
@@ -243,14 +300,16 @@ class ObjectDressup:
|
||||
v1 = queue[1].Placement.Base.sub(queue[2].Placement.Base)
|
||||
|
||||
# extend the current segment to comp for offset
|
||||
segAngle = D.angle(v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1))
|
||||
segAngle = D.angle(
|
||||
v1, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)
|
||||
)
|
||||
xoffset = math.cos(segAngle) * offset
|
||||
yoffset = math.sin(segAngle) * offset
|
||||
|
||||
newX = currLocation["X"] + xoffset
|
||||
newY = currLocation["Y"] + yoffset
|
||||
|
||||
extendcommand = Path.Command('G1', {"X": newX, "Y": newY})
|
||||
extendcommand = Path.Command("G1", {"X": newX, "Y": newY})
|
||||
results.append(extendcommand)
|
||||
|
||||
currLocation.update(extendcommand.Parameters)
|
||||
@@ -259,8 +318,8 @@ class ObjectDressup:
|
||||
return (results, replace)
|
||||
|
||||
def lineTwist(self, obj, queue, lastXY, twistCW=False):
|
||||
'''returns gcode to do an arc move toward a line to perform
|
||||
a corner action twist. Includes lifting and plungeing the knife'''
|
||||
"""returns gcode to do an arc move toward a line to perform
|
||||
a corner action twist. Includes lifting and plungeing the knife"""
|
||||
global currLocation # pylint: disable=global-statement
|
||||
pivotheight = obj.pivotheight
|
||||
offset = obj.offset
|
||||
@@ -285,7 +344,9 @@ class ObjectDressup:
|
||||
v2 = queue[0].Placement.Base.sub(queue[1].Placement.Base)
|
||||
|
||||
# calc arc endpoints to twist to
|
||||
segAngle = D.angle(v2, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1))
|
||||
segAngle = D.angle(
|
||||
v2, FreeCAD.Base.Vector(1, 0, 0), FreeCAD.Base.Vector(0, 0, -1)
|
||||
)
|
||||
xoffset = math.cos(segAngle) * offset
|
||||
yoffset = math.sin(segAngle) * offset
|
||||
newX = queue[1].x + xoffset
|
||||
@@ -297,7 +358,8 @@ class ObjectDressup:
|
||||
|
||||
# add the arc move
|
||||
arcmove = Path.Command(
|
||||
arcdir, {"X": newX, "Y": newY, "I": I, "J": J}) # add G2/G3 move
|
||||
arcdir, {"X": newX, "Y": newY, "I": I, "J": J}
|
||||
) # add G2/G3 move
|
||||
results.append(arcmove)
|
||||
|
||||
currLocation.update(arcmove.Parameters)
|
||||
@@ -335,11 +397,11 @@ class ObjectDressup:
|
||||
continue
|
||||
|
||||
if curCommand.x is None:
|
||||
curCommand.x = currLocation['X']
|
||||
curCommand.x = currLocation["X"]
|
||||
if curCommand.y is None:
|
||||
curCommand.y = currLocation['Y']
|
||||
curCommand.y = currLocation["Y"]
|
||||
if curCommand.z is None:
|
||||
curCommand.z = currLocation['Z']
|
||||
curCommand.z = currLocation["Z"]
|
||||
|
||||
# rapid retract triggers exit move, else just add to output
|
||||
if curCommand.Name in rapidcommands:
|
||||
@@ -348,7 +410,7 @@ class ObjectDressup:
|
||||
tempqueue = queue
|
||||
tempqueue.insert(0, curCommand)
|
||||
|
||||
if queue[1].Name in ['G01', 'G1']:
|
||||
if queue[1].Name in ["G01", "G1"]:
|
||||
temp = self.lineExtension(obj, tempqueue)
|
||||
newpath.extend(temp[0])
|
||||
lastxy = temp[0][-1].Placement.Base
|
||||
@@ -395,7 +457,7 @@ class ObjectDressup:
|
||||
#
|
||||
# DO THE EXTENSION
|
||||
#
|
||||
if queue[1].Name in ['G01', 'G1']:
|
||||
if queue[1].Name in ["G01", "G1"]:
|
||||
temp = self.lineExtension(obj, queue)
|
||||
newpath.extend(temp[0])
|
||||
replace = temp[1]
|
||||
@@ -410,7 +472,7 @@ class ObjectDressup:
|
||||
#
|
||||
# DO THE TWIST
|
||||
#
|
||||
if queue[0].Name in ['G01', 'G1']:
|
||||
if queue[0].Name in ["G01", "G1"]:
|
||||
temp = self.lineTwist(obj, queue, lastxy, twistCW)
|
||||
replace = temp[1]
|
||||
newpath.extend(temp[0])
|
||||
@@ -433,15 +495,20 @@ class ObjectDressup:
|
||||
|
||||
|
||||
class TaskPanel:
|
||||
|
||||
def __init__(self, obj):
|
||||
self.obj = obj
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/DragKnifeEdit.ui")
|
||||
self.filterAngle = PathGui.QuantitySpinBox(self.form.filterAngle, obj, 'filterAngle')
|
||||
self.offsetDistance = PathGui.QuantitySpinBox(self.form.offsetDistance, obj, 'offset')
|
||||
self.pivotHeight = PathGui.QuantitySpinBox(self.form.pivotHeight, obj, 'pivotheight')
|
||||
self.filterAngle = PathGui.QuantitySpinBox(
|
||||
self.form.filterAngle, obj, "filterAngle"
|
||||
)
|
||||
self.offsetDistance = PathGui.QuantitySpinBox(
|
||||
self.form.offsetDistance, obj, "offset"
|
||||
)
|
||||
self.pivotHeight = PathGui.QuantitySpinBox(
|
||||
self.form.pivotHeight, obj, "pivotheight"
|
||||
)
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDragKnife", "Edit Dragknife Dress-up"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Edit Dragknife Dress-up")
|
||||
|
||||
def reject(self):
|
||||
FreeCAD.ActiveDocument.abortTransaction()
|
||||
@@ -483,7 +550,6 @@ class TaskPanel:
|
||||
|
||||
|
||||
class ViewProviderDressup:
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
|
||||
@@ -537,9 +603,16 @@ class CommandDressupDragknife:
|
||||
# pylint: disable=no-init
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Dressup',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_DressupDragKnife", "DragKnife Dress-up"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_DressupDragKnife", "Modifies a path to add dragknife corner actions")}
|
||||
return {
|
||||
"Pixmap": "Path_Dressup",
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Path_DressupDragKnife", "DragKnife Dress-up"
|
||||
),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_DressupDragKnife",
|
||||
"Modifies a path to add dragknife corner actions",
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -554,33 +627,44 @@ class CommandDressupDragknife:
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_DressupDragKnife", "Please select one path object")+"\n")
|
||||
translate("Path_DressupDragKnife", "Please select one path object")
|
||||
+ "\n"
|
||||
)
|
||||
return
|
||||
if not selection[0].isDerivedFrom("Path::Feature"):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_DressupDragKnife", "The selected object is not a path")+"\n")
|
||||
translate("Path_DressupDragKnife", "The selected object is not a path")
|
||||
+ "\n"
|
||||
)
|
||||
return
|
||||
if selection[0].isDerivedFrom("Path::FeatureCompoundPython"):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_DressupDragKnife", "Please select a Path object"))
|
||||
translate("Path_DressupDragKnife", "Please select a Path object")
|
||||
)
|
||||
return
|
||||
|
||||
# everything ok!
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_DressupDragKnife", "Create Dress-up"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Dress-up")
|
||||
FreeCADGui.addModule("PathScripts.PathDressupDragknife")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.doCommand('obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")')
|
||||
FreeCADGui.doCommand('PathScripts.PathDressupDragknife.ObjectDressup(obj)')
|
||||
FreeCADGui.doCommand('base = FreeCAD.ActiveDocument.' + selection[0].Name)
|
||||
FreeCADGui.doCommand('job = PathScripts.PathUtils.findParentJob(base)')
|
||||
FreeCADGui.doCommand('obj.Base = base')
|
||||
FreeCADGui.doCommand('job.Proxy.addOperation(obj, base)')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Proxy = PathScripts.PathDressupDragknife.ViewProviderDressup(obj.ViewObject)')
|
||||
FreeCADGui.doCommand('Gui.ActiveDocument.getObject(base.Name).Visibility = False')
|
||||
FreeCADGui.doCommand('obj.filterAngle = 20')
|
||||
FreeCADGui.doCommand('obj.offset = 2')
|
||||
FreeCADGui.doCommand('obj.pivotheight = 4')
|
||||
FreeCADGui.doCommand('obj.ViewObject.Document.setEdit(obj.ViewObject, 0)')
|
||||
FreeCADGui.doCommand(
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","DragknifeDressup")'
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathDressupDragknife.ObjectDressup(obj)")
|
||||
FreeCADGui.doCommand("base = FreeCAD.ActiveDocument." + selection[0].Name)
|
||||
FreeCADGui.doCommand("job = PathScripts.PathUtils.findParentJob(base)")
|
||||
FreeCADGui.doCommand("obj.Base = base")
|
||||
FreeCADGui.doCommand("job.Proxy.addOperation(obj, base)")
|
||||
FreeCADGui.doCommand(
|
||||
"obj.ViewObject.Proxy = PathScripts.PathDressupDragknife.ViewProviderDressup(obj.ViewObject)"
|
||||
)
|
||||
FreeCADGui.doCommand(
|
||||
"Gui.ActiveDocument.getObject(base.Name).Visibility = False"
|
||||
)
|
||||
FreeCADGui.doCommand("obj.filterAngle = 20")
|
||||
FreeCADGui.doCommand("obj.offset = 2")
|
||||
FreeCADGui.doCommand("obj.pivotheight = 4")
|
||||
FreeCADGui.doCommand("obj.ViewObject.Document.setEdit(obj.ViewObject, 0)")
|
||||
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
@@ -588,6 +672,6 @@ class CommandDressupDragknife:
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_DressupDragKnife', CommandDressupDragknife())
|
||||
FreeCADGui.addCommand("Path_DressupDragKnife", CommandDressupDragknife())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading Path_DressupDragKnife... done\n")
|
||||
|
||||
@@ -20,29 +20,71 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
''' Used to create CNC machine fixture offsets such as G54,G55, etc...'''
|
||||
""" Used to create CNC machine fixture offsets such as G54,G55, etc..."""
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
from PySide import QtCore#, QtGui
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class Fixture:
|
||||
def __init__(self,obj):
|
||||
obj.addProperty("App::PropertyEnumeration", "Fixture", "Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Fixture Offset Number"))
|
||||
obj.Fixture=['G53','G54','G55','G56','G57','G58','G59','G59.1', 'G59.2', 'G59.3', 'G59.4', 'G59.5','G59.6','G59.7', 'G59.8', 'G59.9']
|
||||
obj.addProperty("App::PropertyBool","Active","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code"))
|
||||
def __init__(self, obj):
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"Fixture",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Fixture Offset Number"),
|
||||
)
|
||||
obj.Fixture = [
|
||||
"G53",
|
||||
"G54",
|
||||
"G55",
|
||||
"G56",
|
||||
"G57",
|
||||
"G58",
|
||||
"G59",
|
||||
"G59.1",
|
||||
"G59.2",
|
||||
"G59.3",
|
||||
"G59.4",
|
||||
"G59.5",
|
||||
"G59.6",
|
||||
"G59.7",
|
||||
"G59.8",
|
||||
"G59.9",
|
||||
]
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"Active",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Make False, to prevent operation from generating code"
|
||||
),
|
||||
)
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
def execute(self, obj):
|
||||
fixlist = ['G53', 'G54', 'G55', 'G56', 'G57', 'G58', 'G59', 'G59.1',
|
||||
'G59.2', 'G59.3', 'G59.4', 'G59.5', 'G59.6', 'G59.7', 'G59.8', 'G59.9']
|
||||
fixlist = [
|
||||
"G53",
|
||||
"G54",
|
||||
"G55",
|
||||
"G56",
|
||||
"G57",
|
||||
"G58",
|
||||
"G59",
|
||||
"G59.1",
|
||||
"G59.2",
|
||||
"G59.3",
|
||||
"G59.4",
|
||||
"G59.5",
|
||||
"G59.6",
|
||||
"G59.7",
|
||||
"G59.8",
|
||||
"G59.9",
|
||||
]
|
||||
fixture = fixlist.index(obj.Fixture)
|
||||
obj.Path = Path.Path(str(obj.Fixture))
|
||||
obj.Label = "Fixture" + str(fixture)
|
||||
@@ -58,20 +100,19 @@ class Fixture:
|
||||
|
||||
|
||||
class _ViewProviderFixture:
|
||||
|
||||
def __init__(self, vobj): # mandatory
|
||||
# obj.addProperty("App::PropertyFloat","SomePropertyName","PropertyGroup","Description of this property")
|
||||
vobj.Proxy = self
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def __getstate__(self): # mandatory
|
||||
return None
|
||||
@@ -85,15 +126,15 @@ class _ViewProviderFixture:
|
||||
def onChanged(self, vobj, prop): # optional
|
||||
# pylint: disable=unused-argument
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def updateData(self, vobj, prop): # optional
|
||||
# this is executed when a property of the APP OBJECT changes
|
||||
@@ -109,11 +150,14 @@ class _ViewProviderFixture:
|
||||
|
||||
|
||||
class CommandPathFixture:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Datums',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Fixture"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Fixture", "Creates a Fixture Offset object")}
|
||||
return {
|
||||
"Pixmap": "Path_Datums",
|
||||
"MenuText": QT_TRANSLATE_NOOP("PathFixture", "Fixture"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"PathFixture", "Creates a Fixture Offset object"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -123,9 +167,9 @@ class CommandPathFixture:
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
FreeCAD.ActiveDocument.openTransaction(translate("Path_Fixture", "Create a Fixture Offset"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create a Fixture Offset")
|
||||
FreeCADGui.addModule("PathScripts.PathFixture")
|
||||
snippet = '''
|
||||
snippet = """
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathUtils
|
||||
@@ -137,14 +181,15 @@ PathScripts.PathFixture._ViewProviderFixture(obj.ViewObject)
|
||||
|
||||
PathUtils.addToJob(obj)
|
||||
|
||||
'''
|
||||
"""
|
||||
FreeCADGui.doCommand(snippet)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Fixture', CommandPathFixture())
|
||||
FreeCADGui.addCommand("PathFixture", CommandPathFixture())
|
||||
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathFixture... done\n")
|
||||
|
||||
@@ -31,7 +31,8 @@ from PySide import QtCore
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
__title__ = "PathGeom - geometry utilities for Path"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
@@ -40,16 +41,19 @@ __doc__ = "Functions to extract and convert between Path.Command and Part.Edge a
|
||||
|
||||
Tolerance = 0.000001
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class Side:
|
||||
"""Class to determine and define the side a Path is on, or Vectors are in relation to each other."""
|
||||
Left = +1
|
||||
|
||||
Left = +1
|
||||
Right = -1
|
||||
Straight = 0
|
||||
On = 0
|
||||
@@ -59,10 +63,10 @@ class Side:
|
||||
"""toString(side)
|
||||
Returns a string representation of the enum value."""
|
||||
if side == cls.Left:
|
||||
return 'Left'
|
||||
return "Left"
|
||||
if side == cls.Right:
|
||||
return 'Right'
|
||||
return 'On'
|
||||
return "Right"
|
||||
return "On"
|
||||
|
||||
@classmethod
|
||||
def of(cls, ptRef, pt):
|
||||
@@ -71,72 +75,89 @@ class Side:
|
||||
If both Points are viewed as vectors with their origin in (0,0,0)
|
||||
then the two vectors either form a straight line (On) or pt
|
||||
lies in the left or right hemisphere in regards to ptRef."""
|
||||
d = -ptRef.x*pt.y + ptRef.y*pt.x
|
||||
d = -ptRef.x * pt.y + ptRef.y * pt.x
|
||||
if d < 0:
|
||||
return cls.Left
|
||||
if d > 0:
|
||||
return cls.Right
|
||||
return cls.Straight
|
||||
|
||||
CmdMoveRapid = ['G0', 'G00']
|
||||
CmdMoveStraight = ['G1', 'G01']
|
||||
CmdMoveCW = ['G2', 'G02']
|
||||
CmdMoveCCW = ['G3', 'G03']
|
||||
CmdMoveArc = CmdMoveCW + CmdMoveCCW
|
||||
CmdMove = CmdMoveStraight + CmdMoveArc
|
||||
CmdMoveAll = CmdMove + CmdMoveRapid
|
||||
|
||||
CmdMoveRapid = ["G0", "G00"]
|
||||
CmdMoveStraight = ["G1", "G01"]
|
||||
CmdMoveCW = ["G2", "G02"]
|
||||
CmdMoveCCW = ["G3", "G03"]
|
||||
CmdMoveArc = CmdMoveCW + CmdMoveCCW
|
||||
CmdMove = CmdMoveStraight + CmdMoveArc
|
||||
CmdMoveAll = CmdMove + CmdMoveRapid
|
||||
|
||||
|
||||
def isRoughly(float1, float2, error=Tolerance):
|
||||
"""isRoughly(float1, float2, [error=Tolerance])
|
||||
Returns true if the two values are the same within a given error."""
|
||||
return math.fabs(float1 - float2) <= error
|
||||
|
||||
|
||||
def pointsCoincide(p1, p2, error=Tolerance):
|
||||
"""pointsCoincide(p1, p2, [error=Tolerance])
|
||||
Return True if two points are roughly identical (see also isRoughly)."""
|
||||
return isRoughly(p1.x, p2.x, error) and isRoughly(p1.y, p2.y, error) and isRoughly(p1.z, p2.z, error)
|
||||
return (
|
||||
isRoughly(p1.x, p2.x, error)
|
||||
and isRoughly(p1.y, p2.y, error)
|
||||
and isRoughly(p1.z, p2.z, error)
|
||||
)
|
||||
|
||||
|
||||
def edgesMatch(e0, e1, error=Tolerance):
|
||||
"""edgesMatch(e0, e1, [error=Tolerance]
|
||||
Return true if the edges start and end at the same point and have the same type of curve."""
|
||||
if type(e0.Curve) != type(e1.Curve) or len(e0.Vertexes) != len(e1.Vertexes):
|
||||
return False
|
||||
return all(pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point, error) for i in range(len(e0.Vertexes)))
|
||||
return all(
|
||||
pointsCoincide(e0.Vertexes[i].Point, e1.Vertexes[i].Point, error)
|
||||
for i in range(len(e0.Vertexes))
|
||||
)
|
||||
|
||||
|
||||
def edgeConnectsTo(edge, vector, error=Tolerance):
|
||||
"""edgeConnectsTop(edge, vector, error=Tolerance)
|
||||
Returns True if edge connects to given vector."""
|
||||
return pointsCoincide(edge.valueAt(edge.FirstParameter), vector, error) or pointsCoincide(edge.valueAt(edge.LastParameter), vector, error)
|
||||
return pointsCoincide(
|
||||
edge.valueAt(edge.FirstParameter), vector, error
|
||||
) or pointsCoincide(edge.valueAt(edge.LastParameter), vector, error)
|
||||
|
||||
|
||||
def getAngle(vector):
|
||||
"""getAngle(vector)
|
||||
Returns the angle [-pi,pi] of a vector using the X-axis as the reference.
|
||||
Positive angles for vertexes in the upper hemisphere (positive y values)
|
||||
and negative angles for the lower hemisphere."""
|
||||
a = vector.getAngle(Vector(1,0,0))
|
||||
a = vector.getAngle(Vector(1, 0, 0))
|
||||
if vector.y < 0:
|
||||
return -a
|
||||
return a
|
||||
|
||||
def diffAngle(a1, a2, direction = 'CW'):
|
||||
|
||||
def diffAngle(a1, a2, direction="CW"):
|
||||
"""diffAngle(a1, a2, [direction='CW'])
|
||||
Returns the difference between two angles (a1 -> a2) into a given direction."""
|
||||
if direction == 'CW':
|
||||
if direction == "CW":
|
||||
while a1 < a2:
|
||||
a1 += 2*math.pi
|
||||
a1 += 2 * math.pi
|
||||
a = a1 - a2
|
||||
else:
|
||||
while a2 < a1:
|
||||
a2 += 2*math.pi
|
||||
a2 += 2 * math.pi
|
||||
a = a2 - a1
|
||||
return a
|
||||
|
||||
|
||||
def isVertical(obj):
|
||||
'''isVertical(obj) ... answer True if obj points into Z'''
|
||||
"""isVertical(obj) ... answer True if obj points into Z"""
|
||||
if type(obj) == FreeCAD.Vector:
|
||||
return isRoughly(obj.x, 0) and isRoughly(obj.y, 0)
|
||||
|
||||
if obj.ShapeType == 'Face':
|
||||
if obj.ShapeType == "Face":
|
||||
if type(obj.Surface) == Part.Plane:
|
||||
return isHorizontal(obj.Surface.Axis)
|
||||
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
|
||||
@@ -148,30 +169,39 @@ def isVertical(obj):
|
||||
if type(obj.Surface) == Part.SurfaceOfRevolution:
|
||||
return isHorizontal(obj.Surface.Direction)
|
||||
if type(obj.Surface) != Part.BSplineSurface:
|
||||
PathLog.info(translate('PathGeom', "face %s not handled, assuming not vertical") % type(obj.Surface))
|
||||
PathLog.info(
|
||||
translate("PathGeom", "face %s not handled, assuming not vertical")
|
||||
% type(obj.Surface)
|
||||
)
|
||||
return None
|
||||
|
||||
if obj.ShapeType == 'Edge':
|
||||
if obj.ShapeType == "Edge":
|
||||
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
|
||||
return isVertical(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
|
||||
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
|
||||
if (
|
||||
type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse
|
||||
): # or type(obj.Curve) == Part.BSplineCurve:
|
||||
return isHorizontal(obj.Curve.Axis)
|
||||
if type(obj.Curve) == Part.BezierCurve:
|
||||
# the current assumption is that a bezier curve is vertical if its end points are vertical
|
||||
return isVertical(obj.Curve.EndPoint - obj.Curve.StartPoint)
|
||||
if type(obj.Curve) != Part.BSplineCurve:
|
||||
PathLog.info(translate('PathGeom', "edge %s not handled, assuming not vertical") % type(obj.Curve))
|
||||
PathLog.info(
|
||||
translate("PathGeom", "edge %s not handled, assuming not vertical")
|
||||
% type(obj.Curve)
|
||||
)
|
||||
return None
|
||||
|
||||
PathLog.error(translate('PathGeom', "isVertical(%s) not supported") % obj)
|
||||
PathLog.error(translate("PathGeom", "isVertical(%s) not supported") % obj)
|
||||
return None
|
||||
|
||||
|
||||
def isHorizontal(obj):
|
||||
'''isHorizontal(obj) ... answer True if obj points into X or Y'''
|
||||
"""isHorizontal(obj) ... answer True if obj points into X or Y"""
|
||||
if type(obj) == FreeCAD.Vector:
|
||||
return isRoughly(obj.z, 0)
|
||||
|
||||
if obj.ShapeType == 'Face':
|
||||
if obj.ShapeType == "Face":
|
||||
if type(obj.Surface) == Part.Plane:
|
||||
return isVertical(obj.Surface.Axis)
|
||||
if type(obj.Surface) == Part.Cylinder or type(obj.Surface) == Part.Cone:
|
||||
@@ -184,18 +214,20 @@ def isHorizontal(obj):
|
||||
return isVertical(obj.Surface.Direction)
|
||||
return isRoughly(obj.BoundBox.ZLength, 0.0)
|
||||
|
||||
if obj.ShapeType == 'Edge':
|
||||
if obj.ShapeType == "Edge":
|
||||
if type(obj.Curve) == Part.Line or type(obj.Curve) == Part.LineSegment:
|
||||
return isHorizontal(obj.Vertexes[1].Point - obj.Vertexes[0].Point)
|
||||
if type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse: # or type(obj.Curve) == Part.BSplineCurve:
|
||||
if (
|
||||
type(obj.Curve) == Part.Circle or type(obj.Curve) == Part.Ellipse
|
||||
): # or type(obj.Curve) == Part.BSplineCurve:
|
||||
return isVertical(obj.Curve.Axis)
|
||||
return isRoughly(obj.BoundBox.ZLength, 0.0)
|
||||
|
||||
PathLog.error(translate('PathGeom', "isHorizontal(%s) not supported") % obj)
|
||||
PathLog.error(translate("PathGeom", "isHorizontal(%s) not supported") % obj)
|
||||
return None
|
||||
|
||||
|
||||
def commandEndPoint(cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'):
|
||||
def commandEndPoint(cmd, defaultPoint=Vector(), X="X", Y="Y", Z="Z"):
|
||||
"""commandEndPoint(cmd, [defaultPoint=Vector()], [X='X'], [Y='Y'], [Z='Z'])
|
||||
Extracts the end point from a Path Command."""
|
||||
x = cmd.Parameters.get(X, defaultPoint.x)
|
||||
@@ -203,11 +235,13 @@ def commandEndPoint(cmd, defaultPoint = Vector(), X='X', Y='Y', Z='Z'):
|
||||
z = cmd.Parameters.get(Z, defaultPoint.z)
|
||||
return Vector(x, y, z)
|
||||
|
||||
|
||||
def xy(point):
|
||||
"""xy(point)
|
||||
Convenience function to return the projection of the Vector in the XY-plane."""
|
||||
return Vector(point.x, point.y, 0)
|
||||
|
||||
|
||||
def speedBetweenPoints(p0, p1, hSpeed, vSpeed):
|
||||
if isRoughly(hSpeed, vSpeed):
|
||||
return hSpeed
|
||||
@@ -223,7 +257,10 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed):
|
||||
pitch = pitch + 1
|
||||
while pitch > 1:
|
||||
pitch = pitch - 1
|
||||
PathLog.debug(" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f" % (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length))
|
||||
PathLog.debug(
|
||||
" pitch = %g %g (%.2f, %.2f, %.2f) -> %.2f"
|
||||
% (pitch, math.atan2(xy(d).Length, d.z), d.x, d.y, d.z, xy(d).Length)
|
||||
)
|
||||
speed = vSpeed + pitch * (hSpeed - vSpeed)
|
||||
if speed > hSpeed and speed > vSpeed:
|
||||
return max(hSpeed, vSpeed)
|
||||
@@ -231,7 +268,8 @@ def speedBetweenPoints(p0, p1, hSpeed, vSpeed):
|
||||
return min(hSpeed, vSpeed)
|
||||
return speed
|
||||
|
||||
def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed = 0, vSpeed = 0):
|
||||
|
||||
def cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50, hSpeed=0, vSpeed=0):
|
||||
"""cmdsForEdge(edge, flip=False, useHelixForBSpline=True, segm=50) -> List(Path.Command)
|
||||
Returns a list of Path.Command representing the given edge.
|
||||
If flip is True the edge is considered to be backwards.
|
||||
@@ -240,31 +278,67 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed
|
||||
no direct Path.Command mapping and will be approximated by straight segments.
|
||||
segm is a factor for the segmentation of arbitrary curves not mapped to G1/2/3
|
||||
commands. The higher the value the more segments will be used."""
|
||||
pt = edge.valueAt(edge.LastParameter) if not flip else edge.valueAt(edge.FirstParameter)
|
||||
params = {'X': pt.x, 'Y': pt.y, 'Z': pt.z}
|
||||
pt = (
|
||||
edge.valueAt(edge.LastParameter)
|
||||
if not flip
|
||||
else edge.valueAt(edge.FirstParameter)
|
||||
)
|
||||
params = {"X": pt.x, "Y": pt.y, "Z": pt.z}
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
if hSpeed > 0 and vSpeed > 0:
|
||||
pt2 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter)
|
||||
params.update({'F': speedBetweenPoints(pt, pt2, hSpeed, vSpeed)})
|
||||
commands = [Path.Command('G1', params)]
|
||||
pt2 = (
|
||||
edge.valueAt(edge.FirstParameter)
|
||||
if not flip
|
||||
else edge.valueAt(edge.LastParameter)
|
||||
)
|
||||
params.update({"F": speedBetweenPoints(pt, pt2, hSpeed, vSpeed)})
|
||||
commands = [Path.Command("G1", params)]
|
||||
else:
|
||||
p1 = edge.valueAt(edge.FirstParameter) if not flip else edge.valueAt(edge.LastParameter)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
|
||||
p1 = (
|
||||
edge.valueAt(edge.FirstParameter)
|
||||
if not flip
|
||||
else edge.valueAt(edge.LastParameter)
|
||||
)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2)
|
||||
p3 = pt
|
||||
|
||||
if hasattr(edge.Curve, 'Axis') and ((type(edge.Curve) == Part.Circle and isRoughly(edge.Curve.Axis.x, 0) and isRoughly(edge.Curve.Axis.y, 0)) or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve)):
|
||||
if hasattr(edge.Curve, "Axis") and (
|
||||
(
|
||||
type(edge.Curve) == Part.Circle
|
||||
and isRoughly(edge.Curve.Axis.x, 0)
|
||||
and isRoughly(edge.Curve.Axis.y, 0)
|
||||
)
|
||||
or (useHelixForBSpline and type(edge.Curve) == Part.BSplineCurve)
|
||||
):
|
||||
# This is an arc or a helix and it should be represented by a simple G2/G3 command
|
||||
if edge.Curve.Axis.z < 0:
|
||||
cmd = 'G2' if not flip else 'G3'
|
||||
cmd = "G2" if not flip else "G3"
|
||||
else:
|
||||
cmd = 'G3' if not flip else 'G2'
|
||||
cmd = "G3" if not flip else "G2"
|
||||
|
||||
if pointsCoincide(p1, p3):
|
||||
# A full circle
|
||||
offset = edge.Curve.Center - pt
|
||||
else:
|
||||
pd = Part.Circle(xy(p1), xy(p2), xy(p3)).Center
|
||||
PathLog.debug("**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)" % (cmd, flip, p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, p3.x, p3.y, p3.z, pd.x, pd.y))
|
||||
PathLog.debug(
|
||||
"**** %s.%d: (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
% (
|
||||
cmd,
|
||||
flip,
|
||||
p1.x,
|
||||
p1.y,
|
||||
p1.z,
|
||||
p2.x,
|
||||
p2.y,
|
||||
p2.z,
|
||||
p3.x,
|
||||
p3.y,
|
||||
p3.z,
|
||||
pd.x,
|
||||
pd.y,
|
||||
)
|
||||
)
|
||||
|
||||
# Have to calculate the center in the XY plane, using pd leads to an error if this is a helix
|
||||
pa = xy(p1)
|
||||
@@ -272,15 +346,21 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed
|
||||
pc = xy(p3)
|
||||
offset = Part.Circle(pa, pb, pc).Center - pa
|
||||
|
||||
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z))
|
||||
PathLog.debug("**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)" % (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z))
|
||||
PathLog.debug(
|
||||
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
% (pa.x, pa.y, pa.z, pc.x, pc.y, pc.z)
|
||||
)
|
||||
PathLog.debug(
|
||||
"**** (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f)"
|
||||
% (pb.x, pb.y, pb.z, pd.x, pd.y, pd.z)
|
||||
)
|
||||
PathLog.debug("**** (%.2f, %.2f, %.2f)" % (offset.x, offset.y, offset.z))
|
||||
|
||||
params.update({'I': offset.x, 'J': offset.y, 'K': (p3.z - p1.z)/2})
|
||||
params.update({"I": offset.x, "J": offset.y, "K": (p3.z - p1.z) / 2})
|
||||
# G2/G3 commands are always performed at hSpeed
|
||||
if hSpeed > 0:
|
||||
params.update({'F': hSpeed})
|
||||
commands = [ Path.Command(cmd, params) ]
|
||||
params.update({"F": hSpeed})
|
||||
commands = [Path.Command(cmd, params)]
|
||||
|
||||
else:
|
||||
# We're dealing with a helix or a more complex shape and it has to get approximated
|
||||
@@ -291,18 +371,19 @@ def cmdsForEdge(edge, flip = False, useHelixForBSpline = True, segm = 50, hSpeed
|
||||
|
||||
commands = []
|
||||
if points:
|
||||
p0 = points[0]
|
||||
p0 = points[0]
|
||||
for p in points[1:]:
|
||||
params = {'X': p.x, 'Y': p.y, 'Z': p.z}
|
||||
params = {"X": p.x, "Y": p.y, "Z": p.z}
|
||||
if hSpeed > 0 and vSpeed > 0:
|
||||
params['F'] = speedBetweenPoints(p0, p, hSpeed, vSpeed)
|
||||
cmd = Path.Command('G1', params)
|
||||
params["F"] = speedBetweenPoints(p0, p, hSpeed, vSpeed)
|
||||
cmd = Path.Command("G1", params)
|
||||
# print("***** {}".format(cmd))
|
||||
commands.append(cmd)
|
||||
p0 = p
|
||||
#print commands
|
||||
# print commands
|
||||
return commands
|
||||
|
||||
|
||||
def edgeForCmd(cmd, startPoint):
|
||||
"""edgeForCmd(cmd, startPoint).
|
||||
Returns an Edge representing the given command, assuming a given startPoint."""
|
||||
@@ -317,29 +398,50 @@ def edgeForCmd(cmd, startPoint):
|
||||
return Part.Edge(Part.LineSegment(startPoint, endPoint))
|
||||
|
||||
if cmd.Name in CmdMoveArc:
|
||||
center = startPoint + commandEndPoint(cmd, Vector(0,0,0), 'I', 'J', 'K')
|
||||
center = startPoint + commandEndPoint(cmd, Vector(0, 0, 0), "I", "J", "K")
|
||||
A = xy(startPoint - center)
|
||||
B = xy(endPoint - center)
|
||||
d = -B.x * A.y + B.y * A.x
|
||||
|
||||
if isRoughly(d, 0, 0.005):
|
||||
PathLog.debug("Half circle arc at: (%.2f, %.2f, %.2f)" % (center.x, center.y, center.z))
|
||||
PathLog.debug(
|
||||
"Half circle arc at: (%.2f, %.2f, %.2f)"
|
||||
% (center.x, center.y, center.z)
|
||||
)
|
||||
# we're dealing with half a circle here
|
||||
angle = getAngle(A) + math.pi/2
|
||||
angle = getAngle(A) + math.pi / 2
|
||||
if cmd.Name in CmdMoveCW:
|
||||
angle -= math.pi
|
||||
else:
|
||||
C = A + B
|
||||
angle = getAngle(C)
|
||||
PathLog.debug("Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f" % (d, center.x, center.y, center.z, angle / math.pi))
|
||||
PathLog.debug(
|
||||
"Arc (%8f) at: (%.2f, %.2f, %.2f) -> angle=%f"
|
||||
% (d, center.x, center.y, center.z, angle / math.pi)
|
||||
)
|
||||
|
||||
R = A.Length
|
||||
PathLog.debug("arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)" % (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y))
|
||||
PathLog.debug("arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d))
|
||||
PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle/math.pi))
|
||||
PathLog.debug(
|
||||
"arc: p1=(%.2f, %.2f) p2=(%.2f, %.2f) -> center=(%.2f, %.2f)"
|
||||
% (startPoint.x, startPoint.y, endPoint.x, endPoint.y, center.x, center.y)
|
||||
)
|
||||
PathLog.debug(
|
||||
"arc: A=(%.2f, %.2f) B=(%.2f, %.2f) -> d=%.2f" % (A.x, A.y, B.x, B.y, d)
|
||||
)
|
||||
PathLog.debug("arc: R=%.2f angle=%.2f" % (R, angle / math.pi))
|
||||
if isRoughly(startPoint.z, endPoint.z):
|
||||
midPoint = center + Vector(math.cos(angle), math.sin(angle), 0) * R
|
||||
PathLog.debug("arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)" % (startPoint.x, startPoint.y, midPoint.x, midPoint.y, endPoint.x, endPoint.y))
|
||||
PathLog.debug(
|
||||
"arc: (%.2f, %.2f) -> (%.2f, %.2f) -> (%.2f, %.2f)"
|
||||
% (
|
||||
startPoint.x,
|
||||
startPoint.y,
|
||||
midPoint.x,
|
||||
midPoint.y,
|
||||
endPoint.x,
|
||||
endPoint.y,
|
||||
)
|
||||
)
|
||||
PathLog.debug("StartPoint:{}".format(startPoint))
|
||||
PathLog.debug("MidPoint:{}".format(midPoint))
|
||||
PathLog.debug("EndPoint:{}".format(endPoint))
|
||||
@@ -350,25 +452,26 @@ def edgeForCmd(cmd, startPoint):
|
||||
return Part.Edge(Part.Arc(startPoint, midPoint, endPoint))
|
||||
|
||||
# It's a Helix
|
||||
#print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi))
|
||||
# print('angle: A=%.2f B=%.2f' % (getAngle(A)/math.pi, getAngle(B)/math.pi))
|
||||
if cmd.Name in CmdMoveCW:
|
||||
cw = True
|
||||
else:
|
||||
cw = False
|
||||
angle = diffAngle(getAngle(A), getAngle(B), 'CW' if cw else 'CCW')
|
||||
angle = diffAngle(getAngle(A), getAngle(B), "CW" if cw else "CCW")
|
||||
height = endPoint.z - startPoint.z
|
||||
pitch = height * math.fabs(2 * math.pi / angle)
|
||||
if angle > 0:
|
||||
cw = not cw
|
||||
#print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch))
|
||||
# print("Helix: R=%.2f h=%.2f angle=%.2f pitch=%.2f" % (R, height, angle/math.pi, pitch))
|
||||
helix = Part.makeHelix(pitch, height, R, 0, not cw)
|
||||
helix.rotate(Vector(), Vector(0,0,1), 180 * getAngle(A) / math.pi)
|
||||
helix.rotate(Vector(), Vector(0, 0, 1), 180 * getAngle(A) / math.pi)
|
||||
e = helix.Edges[0]
|
||||
helix.translate(startPoint - e.valueAt(e.FirstParameter))
|
||||
return helix.Edges[0]
|
||||
return None
|
||||
|
||||
def wireForPath(path, startPoint = Vector(0, 0, 0)):
|
||||
|
||||
def wireForPath(path, startPoint=Vector(0, 0, 0)):
|
||||
"""wireForPath(path, [startPoint=Vector(0,0,0)])
|
||||
Returns a wire representing all move commands found in the given path."""
|
||||
edges = []
|
||||
@@ -385,7 +488,8 @@ def wireForPath(path, startPoint = Vector(0, 0, 0)):
|
||||
return (None, rapid)
|
||||
return (Part.Wire(edges), rapid)
|
||||
|
||||
def wiresForPath(path, startPoint = Vector(0, 0, 0)):
|
||||
|
||||
def wiresForPath(path, startPoint=Vector(0, 0, 0)):
|
||||
"""wiresForPath(path, [startPoint=Vector(0,0,0)])
|
||||
Returns a collection of wires, each representing a continuous cutting Path in path."""
|
||||
wires = []
|
||||
@@ -404,36 +508,37 @@ def wiresForPath(path, startPoint = Vector(0, 0, 0)):
|
||||
wires.append(Part.Wire(edges))
|
||||
return wires
|
||||
|
||||
|
||||
def arcToHelix(edge, z0, z1):
|
||||
"""arcToHelix(edge, z0, z1)
|
||||
Assuming edge is an arc it'll return a helix matching the arc starting at z0 and rising/falling to z1."""
|
||||
|
||||
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
# p2 = edge.valueAt(edge.LastParameter)
|
||||
|
||||
cmd = cmdsForEdge(edge)[0]
|
||||
params = cmd.Parameters
|
||||
params.update({'Z': z1, 'K': (z1 - z0)/2})
|
||||
params.update({"Z": z1, "K": (z1 - z0) / 2})
|
||||
command = Path.Command(cmd.Name, params)
|
||||
|
||||
#print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1))
|
||||
#print("- %s -> %s" % (cmd, command))
|
||||
# print("- (%.2f, %.2f, %.2f) - (%.2f, %.2f, %.2f): %.2f:%.2f" % (edge.Vertexes[0].X, edge.Vertexes[0].Y, edge.Vertexes[0].Z, edge.Vertexes[1].X, edge.Vertexes[1].Y, edge.Vertexes[1].Z, z0, z1))
|
||||
# print("- %s -> %s" % (cmd, command))
|
||||
|
||||
return edgeForCmd(command, Vector(p1.x, p1.y, z0))
|
||||
|
||||
|
||||
def helixToArc(edge, z = 0):
|
||||
def helixToArc(edge, z=0):
|
||||
"""helixToArc(edge, z=0)
|
||||
Returns the projection of the helix onto the XY-plane with a given offset."""
|
||||
p1 = edge.valueAt(edge.FirstParameter)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter)/2)
|
||||
p2 = edge.valueAt((edge.FirstParameter + edge.LastParameter) / 2)
|
||||
p3 = edge.valueAt(edge.LastParameter)
|
||||
p01 = Vector(p1.x, p1.y, z)
|
||||
p02 = Vector(p2.x, p2.y, z)
|
||||
p03 = Vector(p3.x, p3.y, z)
|
||||
return Part.Edge(Part.Arc(p01, p02, p03))
|
||||
|
||||
|
||||
def splitArcAt(edge, pt):
|
||||
"""splitArcAt(edge, pt)
|
||||
Returns a list of 2 edges which together form the original arc split at the given point.
|
||||
@@ -443,6 +548,7 @@ def splitArcAt(edge, pt):
|
||||
e1 = Part.Arc(edge.Curve.copy(), p, edge.LastParameter).toShape()
|
||||
return [e0, e1]
|
||||
|
||||
|
||||
def splitEdgeAt(edge, pt):
|
||||
"""splitEdgeAt(edge, pt)
|
||||
Returns a list of 2 edges, forming the original edge split at the given point.
|
||||
@@ -456,7 +562,10 @@ def splitEdgeAt(edge, pt):
|
||||
|
||||
if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment:
|
||||
# it's a line
|
||||
return [Part.Edge(Part.LineSegment(p1, p2)), Part.Edge(Part.LineSegment(p2, p3))]
|
||||
return [
|
||||
Part.Edge(Part.LineSegment(p1, p2)),
|
||||
Part.Edge(Part.LineSegment(p2, p3)),
|
||||
]
|
||||
elif type(edge.Curve) == Part.Circle:
|
||||
# it's an arc
|
||||
return splitArcAt(edge, pt)
|
||||
@@ -466,6 +575,7 @@ def splitEdgeAt(edge, pt):
|
||||
aes = splitArcAt(arc, Vector(pt.x, pt.y, 0))
|
||||
return [arcToHelix(aes[0], p1.z, p2.z), arcToHelix(aes[1], p2.z, p3.z)]
|
||||
|
||||
|
||||
def combineConnectedShapes(shapes):
|
||||
done = False
|
||||
while not done:
|
||||
@@ -474,7 +584,13 @@ def combineConnectedShapes(shapes):
|
||||
PathLog.debug("shapes: {}".format(shapes))
|
||||
for shape in shapes:
|
||||
connected = [f for f in combined if isRoughly(shape.distToShape(f)[0], 0.0)]
|
||||
PathLog.debug(" {}: connected: {} dist: {}".format(len(combined), connected, [shape.distToShape(f)[0] for f in combined]))
|
||||
PathLog.debug(
|
||||
" {}: connected: {} dist: {}".format(
|
||||
len(combined),
|
||||
connected,
|
||||
[shape.distToShape(f)[0] for f in combined],
|
||||
)
|
||||
)
|
||||
if connected:
|
||||
combined = [f for f in combined if f not in connected]
|
||||
connected.append(shape)
|
||||
@@ -485,6 +601,7 @@ def combineConnectedShapes(shapes):
|
||||
shapes = combined
|
||||
return shapes
|
||||
|
||||
|
||||
def removeDuplicateEdges(wire):
|
||||
unique = []
|
||||
for e in wire.Edges:
|
||||
@@ -492,22 +609,36 @@ def removeDuplicateEdges(wire):
|
||||
unique.append(e)
|
||||
return Part.Wire(unique)
|
||||
|
||||
|
||||
OddsAndEnds = []
|
||||
|
||||
|
||||
def flipEdge(edge):
|
||||
'''flipEdge(edge)
|
||||
"""flipEdge(edge)
|
||||
Flips given edge around so the new Vertexes[0] was the old Vertexes[-1] and vice versa, without changing the shape.
|
||||
Currently only lines, line segments, circles and arcs are supported.'''
|
||||
Currently only lines, line segments, circles and arcs are supported."""
|
||||
|
||||
if Part.Line == type(edge.Curve) and not edge.Vertexes:
|
||||
return Part.Edge(Part.Line(edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter)))
|
||||
return Part.Edge(
|
||||
Part.Line(
|
||||
edge.valueAt(edge.LastParameter), edge.valueAt(edge.FirstParameter)
|
||||
)
|
||||
)
|
||||
elif Part.Line == type(edge.Curve) or Part.LineSegment == type(edge.Curve):
|
||||
return Part.Edge(Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point))
|
||||
return Part.Edge(
|
||||
Part.LineSegment(edge.Vertexes[-1].Point, edge.Vertexes[0].Point)
|
||||
)
|
||||
elif Part.Circle == type(edge.Curve):
|
||||
# Create an inverted circle
|
||||
circle = Part.Circle(edge.Curve.Center, -edge.Curve.Axis, edge.Curve.Radius)
|
||||
# Rotate the circle appropriately so it starts at edge.valueAt(edge.LastParameter)
|
||||
circle.rotate(FreeCAD.Placement(circle.Center, circle.Axis, 180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU)))
|
||||
circle.rotate(
|
||||
FreeCAD.Placement(
|
||||
circle.Center,
|
||||
circle.Axis,
|
||||
180 - math.degrees(edge.LastParameter + edge.Curve.AngleXU),
|
||||
)
|
||||
)
|
||||
# Now the edge always starts at 0 and LastParameter is the value range
|
||||
arc = Part.Edge(circle, 0, edge.LastParameter - edge.FirstParameter)
|
||||
return arc
|
||||
@@ -527,7 +658,7 @@ def flipEdge(edge):
|
||||
|
||||
ma = max(knots)
|
||||
mi = min(knots)
|
||||
knots = [ma+mi-k for k in knots]
|
||||
knots = [ma + mi - k for k in knots]
|
||||
|
||||
mults.reverse()
|
||||
weights.reverse()
|
||||
@@ -535,29 +666,36 @@ def flipEdge(edge):
|
||||
knots.reverse()
|
||||
|
||||
flipped = Part.BSplineCurve()
|
||||
flipped.buildFromPolesMultsKnots(poles, mults , knots, perio, degree, weights, ratio)
|
||||
flipped.buildFromPolesMultsKnots(
|
||||
poles, mults, knots, perio, degree, weights, ratio
|
||||
)
|
||||
|
||||
return Part.Edge(flipped)
|
||||
elif type(edge.Curve) == Part.OffsetCurve:
|
||||
return edge.reversed()
|
||||
|
||||
global OddsAndEnds # pylint: disable=global-statement
|
||||
global OddsAndEnds # pylint: disable=global-statement
|
||||
OddsAndEnds.append(edge)
|
||||
PathLog.warning(translate('PathGeom', "%s not supported for flipping") % type(edge.Curve))
|
||||
PathLog.warning(
|
||||
translate("PathGeom", "%s not supported for flipping") % type(edge.Curve)
|
||||
)
|
||||
|
||||
|
||||
Wire = []
|
||||
|
||||
|
||||
def flipWire(wire):
|
||||
'''Flip the entire wire and all its edges so it is being processed the other way around.'''
|
||||
"""Flip the entire wire and all its edges so it is being processed the other way around."""
|
||||
Wire.append(wire)
|
||||
edges = [flipEdge(e) for e in wire.Edges]
|
||||
edges.reverse()
|
||||
PathLog.debug(edges)
|
||||
return Part.Wire(edges)
|
||||
|
||||
|
||||
def makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0):
|
||||
'''makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0)...
|
||||
Function to create boundbox face, with possible extra offset and custom Z-height.'''
|
||||
"""makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0)...
|
||||
Function to create boundbox face, with possible extra offset and custom Z-height."""
|
||||
p1 = FreeCAD.Vector(bBox.XMin - offset, bBox.YMin - offset, zHeight)
|
||||
p2 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMin - offset, zHeight)
|
||||
p3 = FreeCAD.Vector(bBox.XMax + offset, bBox.YMax + offset, zHeight)
|
||||
@@ -570,9 +708,10 @@ def makeBoundBoxFace(bBox, offset=0.0, zHeight=0.0):
|
||||
|
||||
return Part.Face(Part.Wire([L1, L2, L3, L4]))
|
||||
|
||||
|
||||
# Method to combine faces if connected
|
||||
def combineHorizontalFaces(faces):
|
||||
'''combineHorizontalFaces(faces)...
|
||||
"""combineHorizontalFaces(faces)...
|
||||
This function successfully identifies and combines multiple connected faces and
|
||||
works on multiple independent faces with multiple connected faces within the list.
|
||||
The return value is a list of simplified faces.
|
||||
@@ -580,7 +719,7 @@ def combineHorizontalFaces(faces):
|
||||
|
||||
Attempts to do the same shape connecting failed with TechDraw.findShapeOutline() and
|
||||
PathGeom.combineConnectedShapes(), so this algorithm was created.
|
||||
'''
|
||||
"""
|
||||
horizontal = list()
|
||||
offset = 10.0
|
||||
topFace = None
|
||||
@@ -594,8 +733,10 @@ def combineHorizontalFaces(faces):
|
||||
# Make offset compound boundbox solid and cut incoming face extrusions from it
|
||||
allFaces = Part.makeCompound(faces)
|
||||
if hasattr(allFaces, "Area") and isRoughly(allFaces.Area, 0.0):
|
||||
msg = translate('PathGeom',
|
||||
'Zero working area to process. Check your selection and settings.')
|
||||
msg = translate(
|
||||
"PathGeom",
|
||||
"Zero working area to process. Check your selection and settings.",
|
||||
)
|
||||
PathLog.info(msg)
|
||||
return horizontal
|
||||
|
||||
@@ -621,10 +762,12 @@ def combineHorizontalFaces(faces):
|
||||
for f in cut.Faces:
|
||||
fbb = f.BoundBox
|
||||
if isRoughly(fbb.ZMin, 5.0) and isRoughly(fbb.ZMax, 5.0):
|
||||
if (isRoughly(afbb.XMin - offset, fbb.XMin) and
|
||||
isRoughly(afbb.XMax + offset, fbb.XMax) and
|
||||
isRoughly(afbb.YMin - offset, fbb.YMin) and
|
||||
isRoughly(afbb.YMax + offset, fbb.YMax)):
|
||||
if (
|
||||
isRoughly(afbb.XMin - offset, fbb.XMin)
|
||||
and isRoughly(afbb.XMax + offset, fbb.XMax)
|
||||
and isRoughly(afbb.YMin - offset, fbb.YMin)
|
||||
and isRoughly(afbb.YMax + offset, fbb.YMax)
|
||||
):
|
||||
topFace = f
|
||||
else:
|
||||
innerFaces.append(f)
|
||||
|
||||
@@ -23,19 +23,27 @@
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
__doc__ = """Path Hop object and FreeCAD command"""
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
class ObjectHop:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLink", "NextObject", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The object to be reached by this hop"))
|
||||
obj.addProperty("App::PropertyDistance", "HopHeight", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The Z height of the hop"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"NextObject",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The object to be reached by this hop"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"HopHeight",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "The Z height of the hop"),
|
||||
)
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
@@ -69,7 +77,6 @@ class ObjectHop:
|
||||
|
||||
|
||||
class ViewProviderPathHop:
|
||||
|
||||
def __init__(self, vobj):
|
||||
self.Object = vobj.Object
|
||||
vobj.Proxy = self
|
||||
@@ -88,11 +95,12 @@ class ViewProviderPathHop:
|
||||
|
||||
|
||||
class CommandPathHop:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Hop',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Hop"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Hop", "Creates a Path Hop object")}
|
||||
return {
|
||||
"Pixmap": "Path_Hop",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Hop", "Hop"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP("Path_Hop", "Creates a Path Hop object"),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -107,31 +115,33 @@ class CommandPathHop:
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Hop", "Please select one path object")+"\n")
|
||||
translate("Path_Hop", "Please select one path object") + "\n"
|
||||
)
|
||||
return
|
||||
if not selection[0].isDerivedFrom("Path::Feature"):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Hop", "The selected object is not a path")+"\n")
|
||||
translate("Path_Hop", "The selected object is not a path") + "\n"
|
||||
)
|
||||
return
|
||||
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("Path_Hop", "Create Hop"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Hop")
|
||||
FreeCADGui.addModule("PathScripts.PathHop")
|
||||
FreeCADGui.addModule("PathScripts.PathUtils")
|
||||
FreeCADGui.doCommand(
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")')
|
||||
FreeCADGui.doCommand('PathScripts.PathHop.ObjectHop(obj)')
|
||||
'obj = FreeCAD.ActiveDocument.addObject("Path::FeaturePython","Hop")'
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathHop.ObjectHop(obj)")
|
||||
FreeCADGui.doCommand("PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)")
|
||||
FreeCADGui.doCommand(
|
||||
'PathScripts.PathHop.ViewProviderPathHop(obj.ViewObject)')
|
||||
FreeCADGui.doCommand(
|
||||
'obj.NextObject = FreeCAD.ActiveDocument.' + selection[0].Name)
|
||||
FreeCADGui.doCommand('PathScripts.PathUtils.addToJob(obj)')
|
||||
"obj.NextObject = FreeCAD.ActiveDocument." + selection[0].Name
|
||||
)
|
||||
FreeCADGui.doCommand("PathScripts.PathUtils.addToJob(obj)")
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Hop', CommandPathHop())
|
||||
FreeCADGui.addCommand("Path_Hop", CommandPathHop())
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathHop... done\n")
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import PathGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
@@ -30,11 +31,17 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "ViewProvider who's main and only task is to assign an icon."
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
class ViewProvider(object):
|
||||
'''Generic view provider to assign an icon.'''
|
||||
"""Generic view provider to assign an icon."""
|
||||
|
||||
def __init__(self, vobj, icon):
|
||||
self.icon = icon
|
||||
@@ -50,17 +57,17 @@ class ViewProvider(object):
|
||||
self.obj = vobj.Object
|
||||
|
||||
def __getstate__(self):
|
||||
attrs = {'icon': self.icon }
|
||||
if hasattr(self, 'editModule'):
|
||||
attrs['editModule'] = self.editModule
|
||||
attrs['editCallback'] = self.editCallback
|
||||
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']
|
||||
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)
|
||||
@@ -70,7 +77,7 @@ class ViewProvider(object):
|
||||
self.editCallback = callback.__name__
|
||||
|
||||
def _onEditCallback(self, edit):
|
||||
if hasattr(self, 'editModule'):
|
||||
if hasattr(self, "editModule"):
|
||||
mod = importlib.import_module(self.editModule)
|
||||
callback = getattr(mod, self.editCallback)
|
||||
callback(self.obj, self.vobj, edit)
|
||||
@@ -88,31 +95,34 @@ class ViewProvider(object):
|
||||
def setupContextMenu(self, vobj, menu):
|
||||
# pylint: disable=unused-argument
|
||||
PathLog.track()
|
||||
from PySide import QtCore, QtGui
|
||||
edit = QtCore.QCoreApplication.translate('Path', 'Edit', None)
|
||||
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.'''
|
||||
"""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."""
|
||||
|
||||
PathLog.track(vobj.Object.Label, name)
|
||||
global _factory # pylint: disable=global-statement
|
||||
for key,value in PathUtil.keyValueIter(_factory):
|
||||
global _factory # pylint: disable=global-statement
|
||||
for key, value in PathUtil.keyValueIter(_factory):
|
||||
if key == name:
|
||||
return value(vobj, name)
|
||||
PathLog.track(vobj.Object.Label, name, 'PathIconViewProvider')
|
||||
PathLog.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.'''
|
||||
"""RegisterViewProvider(name, provider) ... if an IconViewProvider is created for an object with the given name
|
||||
an instance of provider is used instead."""
|
||||
|
||||
PathLog.track(name)
|
||||
global _factory # pylint: disable=global-statement
|
||||
global _factory # pylint: disable=global-statement
|
||||
_factory[name] = provider
|
||||
|
||||
|
||||
@@ -20,29 +20,38 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
''' Used for CNC machine plane selection G17,G18,G19 '''
|
||||
""" Used for CNC machine plane selection G17,G18,G19 """
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
from PySide import QtCore
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
|
||||
class Plane:
|
||||
def __init__(self,obj):
|
||||
obj.addProperty("App::PropertyEnumeration", "SelectionPlane","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Orientation plane of CNC path"))
|
||||
obj.SelectionPlane=['XY', 'XZ', 'YZ']
|
||||
obj.addProperty("App::PropertyBool","Active","Path",QtCore.QT_TRANSLATE_NOOP("App::Property","Make False, to prevent operation from generating code"))
|
||||
def __init__(self, obj):
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"SelectionPlane",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Orientation plane of CNC path"),
|
||||
)
|
||||
obj.SelectionPlane = ["XY", "XZ", "YZ"]
|
||||
obj.addProperty(
|
||||
"App::PropertyBool",
|
||||
"Active",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Make False, to prevent operation from generating code"
|
||||
),
|
||||
)
|
||||
obj.Proxy = self
|
||||
|
||||
def execute(self, obj):
|
||||
clonelist = ['XY', 'XZ', 'YZ']
|
||||
clonelist = ["XY", "XZ", "YZ"]
|
||||
cindx = clonelist.index(str(obj.SelectionPlane))
|
||||
pathlist = ['G17', 'G18', 'G19']
|
||||
pathlist = ["G17", "G18", "G19"]
|
||||
labelindx = clonelist.index(obj.SelectionPlane) + 1
|
||||
obj.Label = "Plane" + str(labelindx)
|
||||
if obj.Active:
|
||||
@@ -54,19 +63,18 @@ class Plane:
|
||||
|
||||
|
||||
class _ViewProviderPlane:
|
||||
|
||||
def __init__(self, vobj): # mandatory
|
||||
vobj.Proxy = self
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def __getstate__(self): # mandatory
|
||||
return None
|
||||
@@ -80,15 +88,15 @@ class _ViewProviderPlane:
|
||||
def onChanged(self, vobj, prop): # optional
|
||||
# pylint: disable=unused-argument
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def updateData(self, vobj, prop): # optional
|
||||
# this is executed when a property of the APP OBJECT changes
|
||||
@@ -104,11 +112,14 @@ class _ViewProviderPlane:
|
||||
|
||||
|
||||
class CommandPathPlane:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Plane',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Selection Plane"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Plane", "Create a Selection Plane object")}
|
||||
return {
|
||||
"Pixmap": "PathPlane",
|
||||
"MenuText": QT_TRANSLATE_NOOP("PathPlane", "Selection Plane"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"PathPlane", "Create a Selection Plane object"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -118,10 +129,9 @@ class CommandPathPlane:
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("Path_Plane", "Create a Selection Plane object"))
|
||||
FreeCAD.ActiveDocument.openTransaction("Create a Selection Plane object")
|
||||
FreeCADGui.addModule("PathScripts.PathPlane")
|
||||
snippet = '''
|
||||
snippet = """
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathUtils
|
||||
@@ -132,15 +142,16 @@ obj.Active = True
|
||||
PathScripts.PathPlane._ViewProviderPlane(obj.ViewObject)
|
||||
PathUtils.addToJob(obj)
|
||||
|
||||
'''
|
||||
"""
|
||||
|
||||
FreeCADGui.doCommand(snippet)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Plane', CommandPathPlane())
|
||||
FreeCADGui.addCommand("Path_Plane", CommandPathPlane())
|
||||
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathPlane... done\n")
|
||||
|
||||
@@ -31,35 +31,36 @@ __author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Task panel editor for Properties"
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
class _PropertyEditor(object):
|
||||
'''Base class of all property editors - just outlines the TableView delegate interface.'''
|
||||
"""Base class of all property editors - just outlines the TableView delegate interface."""
|
||||
|
||||
def __init__(self, obj, prop):
|
||||
self.obj = obj
|
||||
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 # pylint: disable=unnecessary-pass
|
||||
"""widget(parent) ... called by the delegate to get a new editor widget.
|
||||
Must be implemented by subclasses and return the widget."""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setEditorData(self, widget):
|
||||
'''setEditorData(widget) ... called by the delegate to initialize the editor.
|
||||
"""setEditorData(widget) ... called by the delegate to initialize the editor.
|
||||
The widget is the object returned by widget().
|
||||
Must be implemented by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
Must be implemented by subclasses."""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def setModelData(self, widget):
|
||||
'''setModelData(widget) ... called by the delegate to store new values.
|
||||
Must be implemented by subclasses.'''
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
"""setModelData(widget) ... called by the delegate to store new values.
|
||||
Must be implemented by subclasses."""
|
||||
pass # pylint: disable=unnecessary-pass
|
||||
|
||||
def propertyValue(self):
|
||||
return self.obj.getPropertyByName(self.prop)
|
||||
@@ -70,8 +71,9 @@ class _PropertyEditor(object):
|
||||
def displayString(self):
|
||||
return self.propertyValue()
|
||||
|
||||
|
||||
class _PropertyEditorBool(_PropertyEditor):
|
||||
'''Editor for boolean values - uses a combo box.'''
|
||||
"""Editor for boolean values - uses a combo box."""
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QComboBox(parent)
|
||||
@@ -85,21 +87,22 @@ class _PropertyEditorBool(_PropertyEditor):
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.currentText() == str(True))
|
||||
|
||||
|
||||
class _PropertyEditorString(_PropertyEditor):
|
||||
'''Editor for string values - uses a line edit.'''
|
||||
"""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()
|
||||
text = "" if self.propertyValue() is None else self.propertyValue()
|
||||
widget.setText(text)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.text())
|
||||
|
||||
class _PropertyEditorQuantity(_PropertyEditor):
|
||||
|
||||
class _PropertyEditorQuantity(_PropertyEditor):
|
||||
def widget(self, parent):
|
||||
return QtGui.QLineEdit(parent)
|
||||
|
||||
@@ -117,23 +120,26 @@ class _PropertyEditorQuantity(_PropertyEditor):
|
||||
|
||||
def displayString(self):
|
||||
if self.propertyValue() is None:
|
||||
return ''
|
||||
return ""
|
||||
return self.propertyValue().getUserPreferred()[0]
|
||||
|
||||
|
||||
class _PropertyEditorAngle(_PropertyEditorQuantity):
|
||||
'''Editor for angle values - uses a line edit'''
|
||||
"""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.'''
|
||||
"""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.'''
|
||||
"""Editor for percent values - uses a spin box."""
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QSpinBox(parent)
|
||||
@@ -148,8 +154,9 @@ class _PropertyEditorPercent(_PropertyEditor):
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
|
||||
class _PropertyEditorInteger(_PropertyEditor):
|
||||
'''Editor for integer values - uses a spin box.'''
|
||||
"""Editor for integer values - uses a spin box."""
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QSpinBox(parent)
|
||||
@@ -163,8 +170,9 @@ class _PropertyEditorInteger(_PropertyEditor):
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
|
||||
class _PropertyEditorFloat(_PropertyEditor):
|
||||
'''Editor for float values - uses a double spin box.'''
|
||||
"""Editor for float values - uses a double spin box."""
|
||||
|
||||
def widget(self, parent):
|
||||
return QtGui.QDoubleSpinBox(parent)
|
||||
@@ -178,20 +186,20 @@ class _PropertyEditorFloat(_PropertyEditor):
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.value())
|
||||
|
||||
class _PropertyEditorFile(_PropertyEditor):
|
||||
|
||||
class _PropertyEditorFile(_PropertyEditor):
|
||||
def widget(self, parent):
|
||||
return QtGui.QLineEdit(parent)
|
||||
|
||||
def setEditorData(self, widget):
|
||||
text = '' if self.propertyValue() is None else self.propertyValue()
|
||||
text = "" if self.propertyValue() is None else self.propertyValue()
|
||||
widget.setText(text)
|
||||
|
||||
def setModelData(self, widget):
|
||||
self.setProperty(widget.text())
|
||||
|
||||
class _PropertyEditorEnumeration(_PropertyEditor):
|
||||
|
||||
class _PropertyEditorEnumeration(_PropertyEditor):
|
||||
def widget(self, parent):
|
||||
return QtGui.QComboBox(parent)
|
||||
|
||||
@@ -203,25 +211,28 @@ class _PropertyEditorEnumeration(_PropertyEditor):
|
||||
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,
|
||||
}
|
||||
"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 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.'''
|
||||
"""Returns an editor class to be used for the given property."""
|
||||
factory = _EditorFactory[obj.getTypeIdOfProperty(prop)]
|
||||
if factory:
|
||||
return factory(obj, prop)
|
||||
|
||||
@@ -41,12 +41,9 @@ from datetime import datetime
|
||||
import os
|
||||
import webbrowser
|
||||
import subprocess
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
# Qt translation handling
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
LOG_MODULE = "PathSanity"
|
||||
@@ -103,11 +100,11 @@ class CommandPathSanity:
|
||||
def GetResources(self):
|
||||
return {
|
||||
"Pixmap": "Path_Sanity",
|
||||
"MenuText": QtCore.QT_TRANSLATE_NOOP(
|
||||
"MenuText": QT_TRANSLATE_NOOP(
|
||||
"Path_Sanity", "Check the path job for common errors"
|
||||
),
|
||||
"Accel": "P, S",
|
||||
"ToolTip": QtCore.QT_TRANSLATE_NOOP(
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_Sanity", "Check the path job for common errors"
|
||||
),
|
||||
}
|
||||
|
||||
@@ -20,51 +20,55 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
'''Used to create material stock around a machined part - for visualization'''
|
||||
"""Used to create material stock around a machined part - for visualization"""
|
||||
|
||||
import FreeCAD
|
||||
import PathScripts.PathLog as PathLog
|
||||
import math
|
||||
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
from PySide import QtCore
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
#PathLog.trackModule(PathLog.thisModule())
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class StockType:
|
||||
# pylint: disable=no-init
|
||||
|
||||
NoStock = 'None'
|
||||
FromBase = 'FromBase'
|
||||
CreateBox = 'CreateBox'
|
||||
CreateCylinder = 'CreateCylinder'
|
||||
Unknown = 'Unknown'
|
||||
NoStock = "None"
|
||||
FromBase = "FromBase"
|
||||
CreateBox = "CreateBox"
|
||||
CreateCylinder = "CreateCylinder"
|
||||
Unknown = "Unknown"
|
||||
|
||||
@classmethod
|
||||
def FromStock(cls, stock):
|
||||
'''FromStock(stock) ... Answer a string representing the type of stock.'''
|
||||
"""FromStock(stock) ... Answer a string representing the type of stock."""
|
||||
if not stock:
|
||||
return cls.NoStock
|
||||
if hasattr(stock, 'StockType'):
|
||||
if hasattr(stock, "StockType"):
|
||||
return stock.StockType
|
||||
|
||||
# fallback in case somebody messed with internals
|
||||
if hasattr(stock, 'ExtXneg') and hasattr(stock, 'ExtZpos'):
|
||||
if hasattr(stock, "ExtXneg") and hasattr(stock, "ExtZpos"):
|
||||
return cls.FromBase
|
||||
if hasattr(stock, 'Length') and hasattr(stock, 'Width'):
|
||||
if hasattr(stock, "Length") and hasattr(stock, "Width"):
|
||||
return cls.CreateBox
|
||||
if hasattr(stock, 'Radius') and hasattr(stock, 'Height'):
|
||||
if hasattr(stock, "Radius") and hasattr(stock, "Height"):
|
||||
return cls.CreateCylinder
|
||||
return cls.Unknown
|
||||
|
||||
|
||||
def shapeBoundBox(obj):
|
||||
PathLog.track(type(obj))
|
||||
if list == type(obj) and obj:
|
||||
@@ -73,9 +77,9 @@ def shapeBoundBox(obj):
|
||||
bb.add(shapeBoundBox(o))
|
||||
return bb
|
||||
|
||||
if hasattr(obj, 'Shape'):
|
||||
if hasattr(obj, "Shape"):
|
||||
return obj.Shape.BoundBox
|
||||
if obj and 'App::Part' == obj.TypeId:
|
||||
if obj and "App::Part" == obj.TypeId:
|
||||
bounds = [shapeBoundBox(o) for o in obj.Group]
|
||||
if bounds:
|
||||
bb = bounds[0]
|
||||
@@ -83,39 +87,104 @@ def shapeBoundBox(obj):
|
||||
bb = bb.united(b)
|
||||
return bb
|
||||
if obj:
|
||||
PathLog.error(translate('PathStock', "Invalid base object %s - no shape found") % obj.Name)
|
||||
PathLog.error(
|
||||
translate("PathStock", "Invalid base object %s - no shape found") % obj.Name
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
class Stock(object):
|
||||
def onDocumentRestored(self, obj):
|
||||
if hasattr(obj, 'StockType'):
|
||||
obj.setEditorMode('StockType', 2) # hide
|
||||
if hasattr(obj, "StockType"):
|
||||
obj.setEditorMode("StockType", 2) # hide
|
||||
|
||||
|
||||
class StockFromBase(Stock):
|
||||
|
||||
def __init__(self, obj, base):
|
||||
"Make stock"
|
||||
obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("PathStock", "The base object this stock is derived from"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtXneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative X direction"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtXpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive X direction"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtYneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Y direction"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Y direction"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Z direction"))
|
||||
obj.addProperty("App::PropertyDistance", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Z direction"))
|
||||
obj.addProperty("App::PropertyLink","Material","Component", QtCore.QT_TRANSLATE_NOOP("App::Property","A material for this object"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Base",
|
||||
"Base",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "The base object this stock is derived from"
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtXneg",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in negative X direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtXpos",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in positive X direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtYneg",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in negative Y direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtYpos",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in positive Y direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtZneg",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in negative Z direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyDistance",
|
||||
"ExtZpos",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property",
|
||||
"Extra allowance from part bound box in positive Z direction",
|
||||
),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLink",
|
||||
"Material",
|
||||
"Component",
|
||||
QT_TRANSLATE_NOOP("App::Property", "A material for this object"),
|
||||
)
|
||||
|
||||
obj.Base = base
|
||||
obj.ExtXneg= 1.0
|
||||
obj.ExtXpos= 1.0
|
||||
obj.ExtYneg= 1.0
|
||||
obj.ExtYpos= 1.0
|
||||
obj.ExtZneg= 1.0
|
||||
obj.ExtZpos= 1.0
|
||||
obj.ExtXneg = 1.0
|
||||
obj.ExtXpos = 1.0
|
||||
obj.ExtYneg = 1.0
|
||||
obj.ExtYpos = 1.0
|
||||
obj.ExtZneg = 1.0
|
||||
obj.ExtZpos = 1.0
|
||||
|
||||
# placement is only tracked on creation
|
||||
bb = shapeBoundBox(base.Group) if base else None
|
||||
if bb:
|
||||
obj.Placement = FreeCAD.Placement(FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation())
|
||||
obj.Placement = FreeCAD.Placement(
|
||||
FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin), FreeCAD.Rotation()
|
||||
)
|
||||
else:
|
||||
PathLog.track(obj.Label, base.Label)
|
||||
obj.Proxy = self
|
||||
@@ -123,25 +192,32 @@ class StockFromBase(Stock):
|
||||
# debugging aids
|
||||
self.origin = None
|
||||
self.length = None
|
||||
self.width = None
|
||||
self.width = None
|
||||
self.height = None
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
def execute(self, obj):
|
||||
bb = shapeBoundBox(obj.Base.Group) if obj.Base and hasattr(obj.Base, 'Group') else None
|
||||
bb = (
|
||||
shapeBoundBox(obj.Base.Group)
|
||||
if obj.Base and hasattr(obj.Base, "Group")
|
||||
else None
|
||||
)
|
||||
PathLog.track(obj.Label, bb)
|
||||
|
||||
# Sometimes, when the Base changes it's temporarily not assigned when
|
||||
# Stock.execute is triggered - it'll be set correctly the next time around.
|
||||
if bb:
|
||||
self.origin = FreeCAD.Vector(-obj.ExtXneg.Value, -obj.ExtYneg.Value, -obj.ExtZneg.Value)
|
||||
self.origin = FreeCAD.Vector(
|
||||
-obj.ExtXneg.Value, -obj.ExtYneg.Value, -obj.ExtZneg.Value
|
||||
)
|
||||
|
||||
self.length = bb.XLength + obj.ExtXneg.Value + obj.ExtXpos.Value
|
||||
self.width = bb.YLength + obj.ExtYneg.Value + obj.ExtYpos.Value
|
||||
self.width = bb.YLength + obj.ExtYneg.Value + obj.ExtYpos.Value
|
||||
self.height = bb.ZLength + obj.ExtZneg.Value + obj.ExtZpos.Value
|
||||
|
||||
shape = Part.makeBox(self.length, self.width, self.height, self.origin)
|
||||
@@ -149,7 +225,10 @@ class StockFromBase(Stock):
|
||||
obj.Shape = shape
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop in ['ExtXneg', 'ExtXpos', 'ExtYneg', 'ExtYpos', 'ExtZneg', 'ExtZpos'] and not 'Restore' in obj.State:
|
||||
if (
|
||||
prop in ["ExtXneg", "ExtXpos", "ExtYneg", "ExtYpos", "ExtZneg", "ExtZpos"]
|
||||
and not "Restore" in obj.State
|
||||
):
|
||||
self.execute(obj)
|
||||
|
||||
|
||||
@@ -157,18 +236,34 @@ class StockCreateBox(Stock):
|
||||
MinExtent = 0.001
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty('App::PropertyLength', 'Length', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Length of this stock box"))
|
||||
obj.addProperty('App::PropertyLength', 'Width', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Width of this stock box"))
|
||||
obj.addProperty('App::PropertyLength', 'Height', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock box"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLength",
|
||||
"Length",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Length of this stock box"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLength",
|
||||
"Width",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Width of this stock box"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLength",
|
||||
"Height",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Height of this stock box"),
|
||||
)
|
||||
|
||||
obj.Length = 10
|
||||
obj.Width = 10
|
||||
obj.Width = 10
|
||||
obj.Height = 10
|
||||
|
||||
obj.Proxy = self
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
@@ -185,15 +280,26 @@ class StockCreateBox(Stock):
|
||||
obj.Shape = shape
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop in ['Length', 'Width', 'Height'] and not 'Restore' in obj.State:
|
||||
if prop in ["Length", "Width", "Height"] and not "Restore" in obj.State:
|
||||
self.execute(obj)
|
||||
|
||||
|
||||
class StockCreateCylinder(Stock):
|
||||
MinExtent = 0.001
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty('App::PropertyLength', 'Radius', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Radius of this stock cylinder"))
|
||||
obj.addProperty('App::PropertyLength', 'Height', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Height of this stock cylinder"))
|
||||
obj.addProperty(
|
||||
"App::PropertyLength",
|
||||
"Radius",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Radius of this stock cylinder"),
|
||||
)
|
||||
obj.addProperty(
|
||||
"App::PropertyLength",
|
||||
"Height",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Height of this stock cylinder"),
|
||||
)
|
||||
|
||||
obj.Radius = 2
|
||||
obj.Height = 10
|
||||
@@ -202,6 +308,7 @@ class StockCreateCylinder(Stock):
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
|
||||
def __setstate__(self, state):
|
||||
return None
|
||||
|
||||
@@ -216,38 +323,49 @@ class StockCreateCylinder(Stock):
|
||||
obj.Shape = shape
|
||||
|
||||
def onChanged(self, obj, prop):
|
||||
if prop in ['Radius', 'Height'] and not 'Restore' in obj.State:
|
||||
if prop in ["Radius", "Height"] and not "Restore" in obj.State:
|
||||
self.execute(obj)
|
||||
|
||||
|
||||
def SetupStockObject(obj, stockType):
|
||||
PathLog.track(obj.Label, stockType)
|
||||
if FreeCAD.GuiUp and obj.ViewObject:
|
||||
obj.addProperty('App::PropertyString', 'StockType', 'Stock', QtCore.QT_TRANSLATE_NOOP("PathStock", "Internal representation of stock type"))
|
||||
obj.addProperty(
|
||||
"App::PropertyString",
|
||||
"StockType",
|
||||
"Stock",
|
||||
QT_TRANSLATE_NOOP("App::Property", "Internal representation of stock type"),
|
||||
)
|
||||
obj.StockType = stockType
|
||||
obj.setEditorMode('StockType', 2) # hide
|
||||
obj.setEditorMode("StockType", 2) # hide
|
||||
|
||||
import PathScripts.PathIconViewProvider
|
||||
PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, 'Stock')
|
||||
|
||||
PathScripts.PathIconViewProvider.ViewProvider(obj.ViewObject, "Stock")
|
||||
obj.ViewObject.Transparency = 90
|
||||
obj.ViewObject.DisplayMode = 'Wireframe'
|
||||
obj.ViewObject.DisplayMode = "Wireframe"
|
||||
|
||||
|
||||
class FakeJob(object):
|
||||
def __init__(self, base):
|
||||
self.Group = [base]
|
||||
|
||||
|
||||
def _getBase(job):
|
||||
if job and hasattr(job, 'Model'):
|
||||
if job and hasattr(job, "Model"):
|
||||
return job.Model
|
||||
if job:
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
|
||||
job = PathUtils.findParentJob(job)
|
||||
return job.Model if job else None
|
||||
return None
|
||||
|
||||
|
||||
def CreateFromBase(job, neg=None, pos=None, placement=None):
|
||||
PathLog.track(job.Label, neg, pos, placement)
|
||||
base = _getBase(job)
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock")
|
||||
obj.Proxy = StockFromBase(obj, base)
|
||||
|
||||
if neg:
|
||||
@@ -268,19 +386,20 @@ def CreateFromBase(job, neg=None, pos=None, placement=None):
|
||||
obj.purgeTouched()
|
||||
return obj
|
||||
|
||||
|
||||
def CreateBox(job, extent=None, placement=None):
|
||||
base = _getBase(job)
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock")
|
||||
obj.Proxy = StockCreateBox(obj)
|
||||
|
||||
if extent:
|
||||
obj.Length = extent.x
|
||||
obj.Width = extent.y
|
||||
obj.Width = extent.y
|
||||
obj.Height = extent.z
|
||||
elif base:
|
||||
bb = shapeBoundBox(base.Group)
|
||||
obj.Length = max(bb.XLength, 1)
|
||||
obj.Width = max(bb.YLength, 1)
|
||||
obj.Width = max(bb.YLength, 1)
|
||||
obj.Height = max(bb.ZLength, 1)
|
||||
|
||||
if placement:
|
||||
@@ -293,9 +412,10 @@ def CreateBox(job, extent=None, placement=None):
|
||||
SetupStockObject(obj, StockType.CreateBox)
|
||||
return obj
|
||||
|
||||
|
||||
def CreateCylinder(job, radius=None, height=None, placement=None):
|
||||
base = _getBase(job)
|
||||
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
|
||||
obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Stock")
|
||||
obj.Proxy = StockCreateCylinder(obj)
|
||||
|
||||
if radius:
|
||||
@@ -312,112 +432,179 @@ def CreateCylinder(job, radius=None, height=None, placement=None):
|
||||
obj.Placement = placement
|
||||
elif base:
|
||||
bb = shapeBoundBox(base.Group)
|
||||
origin = FreeCAD.Vector((bb.XMin + bb.XMax)/2, (bb.YMin + bb.YMax)/2, bb.ZMin)
|
||||
origin = FreeCAD.Vector(
|
||||
(bb.XMin + bb.XMax) / 2, (bb.YMin + bb.YMax) / 2, bb.ZMin
|
||||
)
|
||||
obj.Placement = FreeCAD.Placement(origin, FreeCAD.Vector(), 0)
|
||||
|
||||
SetupStockObject(obj, StockType.CreateCylinder)
|
||||
return obj
|
||||
|
||||
|
||||
def TemplateAttributes(stock, includeExtent=True, includePlacement=True):
|
||||
attrs = {}
|
||||
if stock:
|
||||
attrs['version'] = 1
|
||||
attrs["version"] = 1
|
||||
stockType = StockType.FromStock(stock)
|
||||
attrs['create'] = stockType
|
||||
attrs["create"] = stockType
|
||||
|
||||
if includeExtent:
|
||||
if stockType == StockType.FromBase:
|
||||
attrs['xneg'] = ("%s" % stock.ExtXneg)
|
||||
attrs['xpos'] = ("%s" % stock.ExtXpos)
|
||||
attrs['yneg'] = ("%s" % stock.ExtYneg)
|
||||
attrs['ypos'] = ("%s" % stock.ExtYpos)
|
||||
attrs['zneg'] = ("%s" % stock.ExtZneg)
|
||||
attrs['zpos'] = ("%s" % stock.ExtZpos)
|
||||
attrs["xneg"] = "%s" % stock.ExtXneg
|
||||
attrs["xpos"] = "%s" % stock.ExtXpos
|
||||
attrs["yneg"] = "%s" % stock.ExtYneg
|
||||
attrs["ypos"] = "%s" % stock.ExtYpos
|
||||
attrs["zneg"] = "%s" % stock.ExtZneg
|
||||
attrs["zpos"] = "%s" % stock.ExtZpos
|
||||
if stockType == StockType.CreateBox:
|
||||
attrs['length'] = ("%s" % stock.Length)
|
||||
attrs['width'] = ("%s" % stock.Width)
|
||||
attrs['height'] = ("%s" % stock.Height)
|
||||
attrs["length"] = "%s" % stock.Length
|
||||
attrs["width"] = "%s" % stock.Width
|
||||
attrs["height"] = "%s" % stock.Height
|
||||
if stockType == StockType.CreateCylinder:
|
||||
attrs['radius'] = ("%s" % stock.Radius)
|
||||
attrs['height'] = ("%s" % stock.Height)
|
||||
attrs["radius"] = "%s" % stock.Radius
|
||||
attrs["height"] = "%s" % stock.Height
|
||||
|
||||
if includePlacement:
|
||||
pos = stock.Placement.Base
|
||||
attrs['posX'] = pos.x
|
||||
attrs['posY'] = pos.y
|
||||
attrs['posZ'] = pos.z
|
||||
attrs["posX"] = pos.x
|
||||
attrs["posY"] = pos.y
|
||||
attrs["posZ"] = pos.z
|
||||
rot = stock.Placement.Rotation
|
||||
attrs['rotX'] = rot.Q[0]
|
||||
attrs['rotY'] = rot.Q[1]
|
||||
attrs['rotZ'] = rot.Q[2]
|
||||
attrs['rotW'] = rot.Q[3]
|
||||
attrs["rotX"] = rot.Q[0]
|
||||
attrs["rotY"] = rot.Q[1]
|
||||
attrs["rotZ"] = rot.Q[2]
|
||||
attrs["rotW"] = rot.Q[3]
|
||||
|
||||
return attrs
|
||||
|
||||
|
||||
def CreateFromTemplate(job, template):
|
||||
if template.get('version') and 1 == int(template['version']):
|
||||
stockType = template.get('create')
|
||||
if template.get("version") and 1 == int(template["version"]):
|
||||
stockType = template.get("create")
|
||||
if stockType:
|
||||
placement = None
|
||||
posX = template.get('posX')
|
||||
posY = template.get('posY')
|
||||
posZ = template.get('posZ')
|
||||
rotX = template.get('rotX')
|
||||
rotY = template.get('rotY')
|
||||
rotZ = template.get('rotZ')
|
||||
rotW = template.get('rotW')
|
||||
if posX is not None and posY is not None and posZ is not None and rotX is not None and rotY is not None and rotZ is not None and rotW is not None:
|
||||
posX = template.get("posX")
|
||||
posY = template.get("posY")
|
||||
posZ = template.get("posZ")
|
||||
rotX = template.get("rotX")
|
||||
rotY = template.get("rotY")
|
||||
rotZ = template.get("rotZ")
|
||||
rotW = template.get("rotW")
|
||||
if (
|
||||
posX is not None
|
||||
and posY is not None
|
||||
and posZ is not None
|
||||
and rotX is not None
|
||||
and rotY is not None
|
||||
and rotZ is not None
|
||||
and rotW is not None
|
||||
):
|
||||
pos = FreeCAD.Vector(float(posX), float(posY), float(posZ))
|
||||
rot = FreeCAD.Rotation(float(rotX), float(rotY), float(rotZ), float(rotW))
|
||||
rot = FreeCAD.Rotation(
|
||||
float(rotX), float(rotY), float(rotZ), float(rotW)
|
||||
)
|
||||
placement = FreeCAD.Placement(pos, rot)
|
||||
elif posX is not None or posY is not None or posZ is not None or rotX is not None or rotY is not None or rotZ is not None or rotW is not None:
|
||||
PathLog.warning(translate('PathStock', 'Corrupted or incomplete placement information in template - ignoring'))
|
||||
elif (
|
||||
posX is not None
|
||||
or posY is not None
|
||||
or posZ is not None
|
||||
or rotX is not None
|
||||
or rotY is not None
|
||||
or rotZ is not None
|
||||
or rotW is not None
|
||||
):
|
||||
PathLog.warning(
|
||||
"Corrupted or incomplete placement information in template - ignoring"
|
||||
)
|
||||
|
||||
if stockType == StockType.FromBase:
|
||||
xneg = template.get('xneg')
|
||||
xpos = template.get('xpos')
|
||||
yneg = template.get('yneg')
|
||||
ypos = template.get('ypos')
|
||||
zneg = template.get('zneg')
|
||||
zpos = template.get('zpos')
|
||||
xneg = template.get("xneg")
|
||||
xpos = template.get("xpos")
|
||||
yneg = template.get("yneg")
|
||||
ypos = template.get("ypos")
|
||||
zneg = template.get("zneg")
|
||||
zpos = template.get("zpos")
|
||||
neg = None
|
||||
pos = None
|
||||
if xneg is not None and xpos is not None and yneg is not None and ypos is not None and zneg is not None and zpos is not None:
|
||||
neg = FreeCAD.Vector(FreeCAD.Units.Quantity(xneg).Value, FreeCAD.Units.Quantity(yneg).Value, FreeCAD.Units.Quantity(zneg).Value)
|
||||
pos = FreeCAD.Vector(FreeCAD.Units.Quantity(xpos).Value, FreeCAD.Units.Quantity(ypos).Value, FreeCAD.Units.Quantity(zpos).Value)
|
||||
elif xneg is not None or xpos is not None or yneg is not None or ypos is not None or zneg is not None or zpos is not None:
|
||||
PathLog.error(translate('PathStock', 'Corrupted or incomplete specification for creating stock from base - ignoring extent'))
|
||||
if (
|
||||
xneg is not None
|
||||
and xpos is not None
|
||||
and yneg is not None
|
||||
and ypos is not None
|
||||
and zneg is not None
|
||||
and zpos is not None
|
||||
):
|
||||
neg = FreeCAD.Vector(
|
||||
FreeCAD.Units.Quantity(xneg).Value,
|
||||
FreeCAD.Units.Quantity(yneg).Value,
|
||||
FreeCAD.Units.Quantity(zneg).Value,
|
||||
)
|
||||
pos = FreeCAD.Vector(
|
||||
FreeCAD.Units.Quantity(xpos).Value,
|
||||
FreeCAD.Units.Quantity(ypos).Value,
|
||||
FreeCAD.Units.Quantity(zpos).Value,
|
||||
)
|
||||
elif (
|
||||
xneg is not None
|
||||
or xpos is not None
|
||||
or yneg is not None
|
||||
or ypos is not None
|
||||
or zneg is not None
|
||||
or zpos is not None
|
||||
):
|
||||
PathLog.error(
|
||||
"Corrupted or incomplete specification for creating stock from base - ignoring extent"
|
||||
)
|
||||
return CreateFromBase(job, neg, pos, placement)
|
||||
|
||||
if stockType == StockType.CreateBox:
|
||||
PathLog.track(' create box')
|
||||
length = template.get('length')
|
||||
width = template.get('width')
|
||||
height = template.get('height')
|
||||
PathLog.track(" create box")
|
||||
length = template.get("length")
|
||||
width = template.get("width")
|
||||
height = template.get("height")
|
||||
extent = None
|
||||
if length is not None and width is not None and height is not None:
|
||||
PathLog.track(' have extent')
|
||||
extent = FreeCAD.Vector(FreeCAD.Units.Quantity(length).Value, FreeCAD.Units.Quantity(width).Value, FreeCAD.Units.Quantity(height).Value)
|
||||
PathLog.track(" have extent")
|
||||
extent = FreeCAD.Vector(
|
||||
FreeCAD.Units.Quantity(length).Value,
|
||||
FreeCAD.Units.Quantity(width).Value,
|
||||
FreeCAD.Units.Quantity(height).Value,
|
||||
)
|
||||
elif length is not None or width is not None or height is not None:
|
||||
PathLog.error(translate('PathStock', 'Corrupted or incomplete size for creating a stock box - ignoring size'))
|
||||
PathLog.error(
|
||||
"Corrupted or incomplete size for creating a stock box - ignoring size"
|
||||
)
|
||||
else:
|
||||
PathLog.track(" take placement (%s) and extent (%s) from model" % (placement, extent))
|
||||
PathLog.track(
|
||||
" take placement (%s) and extent (%s) from model"
|
||||
% (placement, extent)
|
||||
)
|
||||
return CreateBox(job, extent, placement)
|
||||
|
||||
if stockType == StockType.CreateCylinder:
|
||||
radius = template.get('radius')
|
||||
height = template.get('height')
|
||||
radius = template.get("radius")
|
||||
height = template.get("height")
|
||||
if radius is not None and height is not None:
|
||||
pass
|
||||
elif radius is not None or height is not None:
|
||||
radius = None
|
||||
height = None
|
||||
PathLog.error(translate('PathStock', 'Corrupted or incomplete size for creating a stock cylinder - ignoring size'))
|
||||
PathLog.error(
|
||||
"Corrupted or incomplete size for creating a stock cylinder - ignoring size"
|
||||
)
|
||||
return CreateCylinder(job, radius, height, placement)
|
||||
|
||||
PathLog.error(translate('PathStock', 'Unsupported stock type named {}').format(stockType))
|
||||
PathLog.error(
|
||||
translate("PathStock", "Unsupported stock type named {}").format(
|
||||
stockType
|
||||
)
|
||||
)
|
||||
else:
|
||||
PathLog.error(translate('PathStock', 'Unsupported PathStock template version {}').format(template.get('version')))
|
||||
PathLog.error(
|
||||
translate(
|
||||
"PathStock", "Unsupported PathStock template version {}"
|
||||
).format(template.get("version"))
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
|
||||
@@ -20,24 +20,31 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
'''Used for CNC machine Stops for Path module. Create an Optional or Mandatory Stop.'''
|
||||
"""Used for CNC machine Stops for Path module. Create an Optional or Mandatory Stop."""
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
from PySide import QtCore
|
||||
from PySide.QtCore import QT_TRANSLATE_NOOP
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
class Stop:
|
||||
def __init__(self,obj):
|
||||
obj.addProperty("App::PropertyEnumeration", "Stop", "Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Add Optional or Mandatory Stop to the program"))
|
||||
obj.Stop=['Optional', 'Mandatory']
|
||||
def __init__(self, obj):
|
||||
obj.addProperty(
|
||||
"App::PropertyEnumeration",
|
||||
"Stop",
|
||||
"Path",
|
||||
QT_TRANSLATE_NOOP(
|
||||
"App::Property", "Add Optional or Mandatory Stop to the program"
|
||||
),
|
||||
)
|
||||
obj.Stop = ["Optional", "Mandatory"]
|
||||
obj.Proxy = self
|
||||
mode = 2
|
||||
obj.setEditorMode('Placement', mode)
|
||||
obj.setEditorMode("Placement", mode)
|
||||
|
||||
def __getstate__(self):
|
||||
return None
|
||||
@@ -49,31 +56,30 @@ class Stop:
|
||||
pass
|
||||
|
||||
def execute(self, obj):
|
||||
if obj.Stop == 'Optional':
|
||||
word = 'M1'
|
||||
if obj.Stop == "Optional":
|
||||
word = "M1"
|
||||
else:
|
||||
word = 'M0'
|
||||
word = "M0"
|
||||
|
||||
output = ""
|
||||
output = word + '\n'
|
||||
output = word + "\n"
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
class _ViewProviderStop:
|
||||
|
||||
def __init__(self, vobj): # mandatory
|
||||
vobj.Proxy = self
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
def __getstate__(self): # mandatory
|
||||
return None
|
||||
@@ -87,23 +93,26 @@ class _ViewProviderStop:
|
||||
def onChanged(self, vobj, prop): # optional
|
||||
# pylint: disable=unused-argument
|
||||
mode = 2
|
||||
vobj.setEditorMode('LineWidth', mode)
|
||||
vobj.setEditorMode('MarkerColor', mode)
|
||||
vobj.setEditorMode('NormalColor', mode)
|
||||
vobj.setEditorMode('DisplayMode', mode)
|
||||
vobj.setEditorMode('BoundingBox', mode)
|
||||
vobj.setEditorMode('Selectable', mode)
|
||||
vobj.setEditorMode('ShapeColor', mode)
|
||||
vobj.setEditorMode('Transparency', mode)
|
||||
vobj.setEditorMode('Visibility', mode)
|
||||
vobj.setEditorMode("LineWidth", mode)
|
||||
vobj.setEditorMode("MarkerColor", mode)
|
||||
vobj.setEditorMode("NormalColor", mode)
|
||||
vobj.setEditorMode("DisplayMode", mode)
|
||||
vobj.setEditorMode("BoundingBox", mode)
|
||||
vobj.setEditorMode("Selectable", mode)
|
||||
vobj.setEditorMode("ShapeColor", mode)
|
||||
vobj.setEditorMode("Transparency", mode)
|
||||
vobj.setEditorMode("Visibility", mode)
|
||||
|
||||
|
||||
class CommandPathStop:
|
||||
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Stop',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Stop"),
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Stop", "Add Optional or Mandatory Stop to the program")}
|
||||
return {
|
||||
"Pixmap": "Path_Stop",
|
||||
"MenuText": QT_TRANSLATE_NOOP("Path_Stop", "Stop"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"Path_Stop", "Add Optional or Mandatory Stop to the program"
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
if FreeCAD.ActiveDocument is not None:
|
||||
@@ -114,9 +123,10 @@ class CommandPathStop:
|
||||
|
||||
def Activated(self):
|
||||
FreeCAD.ActiveDocument.openTransaction(
|
||||
translate("Path_Stop", "Add Optional or Mandatory Stop to the program"))
|
||||
"Add Optional or Mandatory Stop to the program"
|
||||
)
|
||||
FreeCADGui.addModule("PathScripts.PathStop")
|
||||
snippet = '''
|
||||
snippet = """
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathUtils
|
||||
@@ -126,14 +136,15 @@ PathScripts.PathStop.Stop(obj)
|
||||
|
||||
PathScripts.PathStop._ViewProviderStop(obj.ViewObject)
|
||||
PathUtils.addToJob(obj)
|
||||
'''
|
||||
"""
|
||||
FreeCADGui.doCommand(snippet)
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
FreeCAD.ActiveDocument.recompute()
|
||||
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
# register the FreeCAD command
|
||||
FreeCADGui.addCommand('Path_Stop', CommandPathStop())
|
||||
FreeCADGui.addCommand("Path_Stop", CommandPathStop())
|
||||
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathStop... done\n")
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
'''
|
||||
"""
|
||||
The purpose of this file is to collect some handy functions. The reason they
|
||||
are not in PathUtils (and there is this confusing naming going on) is that
|
||||
PathUtils depends on PathJob. Which makes it impossible to use the functions
|
||||
@@ -28,125 +28,148 @@ and classes defined there in PathJob.
|
||||
|
||||
So if you add to this file and think about importing anything from PathScripts
|
||||
other than PathLog, then it probably doesn't belong here.
|
||||
'''
|
||||
"""
|
||||
|
||||
import FreeCAD
|
||||
import six
|
||||
import PathScripts.PathLog as PathLog
|
||||
import PySide
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return PySide.QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
def _getProperty(obj, prop):
|
||||
o = obj
|
||||
attr = obj
|
||||
name = None
|
||||
for name in prop.split('.'):
|
||||
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))
|
||||
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)
|
||||
# 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
|
||||
"""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 getPropertyValueString(obj, prop):
|
||||
'''getPropertyValueString(obj, prop) ... answer a string representation of an object's property's value.'''
|
||||
"""getPropertyValueString(obj, prop) ... answer a string representation of an object's property's value."""
|
||||
attr = getProperty(obj, prop)
|
||||
if hasattr(attr, 'UserString'):
|
||||
if hasattr(attr, "UserString"):
|
||||
return attr.UserString
|
||||
return str(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
|
||||
"""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 not attr is None and type(value) == str:
|
||||
if type(attr) == int:
|
||||
value = int(value, 0)
|
||||
elif type(attr) == bool:
|
||||
value = value.lower() in ['true', '1', 'yes', 'ok']
|
||||
value = value.lower() in ["true", "1", "yes", "ok"]
|
||||
if o and name:
|
||||
setattr(o, name, value)
|
||||
|
||||
|
||||
# NotValidBaseTypeIds = ['Sketcher::SketchObject']
|
||||
NotValidBaseTypeIds = []
|
||||
|
||||
|
||||
def isValidBaseObject(obj):
|
||||
'''isValidBaseObject(obj) ... returns true if the object can be used as a base for a job.'''
|
||||
if hasattr(obj, 'getParentGeoFeatureGroup') and obj.getParentGeoFeatureGroup():
|
||||
"""isValidBaseObject(obj) ... returns true if the object can be used as a base for a job."""
|
||||
if hasattr(obj, "getParentGeoFeatureGroup") and obj.getParentGeoFeatureGroup():
|
||||
# Can't link to anything inside a geo feature group anymore
|
||||
PathLog.debug("%s is inside a geo feature group" % obj.Label)
|
||||
return False
|
||||
if hasattr(obj, 'BitBody') and hasattr(obj, 'BitShape'):
|
||||
if hasattr(obj, "BitBody") and hasattr(obj, "BitShape"):
|
||||
# ToolBit's are not valid base objects
|
||||
return False
|
||||
if obj.TypeId in NotValidBaseTypeIds:
|
||||
PathLog.debug("%s is blacklisted (%s)" % (obj.Label, obj.TypeId))
|
||||
return False
|
||||
if hasattr(obj, 'Sheets') or hasattr(obj, 'TagText'): # Arch.Panels and Arch.PanelCut
|
||||
if hasattr(obj, "Sheets") or hasattr(
|
||||
obj, "TagText"
|
||||
): # Arch.Panels and Arch.PanelCut
|
||||
PathLog.debug("%s is not an Arch.Panel" % (obj.Label))
|
||||
return False
|
||||
import Part
|
||||
|
||||
return not Part.getShape(obj).isNull()
|
||||
|
||||
|
||||
def isSolid(obj):
|
||||
'''isSolid(obj) ... return True if the object is a valid solid.'''
|
||||
"""isSolid(obj) ... return True if the object is a valid solid."""
|
||||
import Part
|
||||
|
||||
shape = Part.getShape(obj)
|
||||
return not shape.isNull() and shape.Volume and shape.isClosed()
|
||||
|
||||
|
||||
def opProperty(op, prop):
|
||||
'''opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)'''
|
||||
"""opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)"""
|
||||
if hasattr(op, prop):
|
||||
return getattr(op, prop)
|
||||
if hasattr(op, 'Base'):
|
||||
if hasattr(op, "Base"):
|
||||
return opProperty(op.Base, prop)
|
||||
return None
|
||||
|
||||
|
||||
def toolControllerForOp(op):
|
||||
'''toolControllerForOp(op) ... return the tool controller used by the op.
|
||||
"""toolControllerForOp(op) ... return the tool controller used by the op.
|
||||
If the op doesn't have its own tool controller but has a Base object, return its tool controller.
|
||||
Otherwise return None.'''
|
||||
return opProperty(op, 'ToolController')
|
||||
Otherwise return None."""
|
||||
return opProperty(op, "ToolController")
|
||||
|
||||
|
||||
def getPublicObject(obj):
|
||||
'''getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object.'''
|
||||
if hasattr(obj, 'getParentGeoFeatureGroup'):
|
||||
"""getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object."""
|
||||
if hasattr(obj, "getParentGeoFeatureGroup"):
|
||||
body = obj.getParentGeoFeatureGroup()
|
||||
if body:
|
||||
return getPublicObject(body)
|
||||
return obj
|
||||
|
||||
def clearExpressionEngine(obj):
|
||||
'''clearExpressionEngine(obj) ... removes all expressions from obj.
|
||||
|
||||
There is currently a bug that invalidates the DAG if an object
|
||||
is deleted that still has one or more expressions attached to it.
|
||||
Use this function to remove all expressions before deletion.'''
|
||||
if hasattr(obj, 'ExpressionEngine'):
|
||||
for attr, expr in obj.ExpressionEngine: # pylint: disable=unused-variable
|
||||
def clearExpressionEngine(obj):
|
||||
"""clearExpressionEngine(obj) ... removes all expressions from obj.
|
||||
|
||||
There is currently a bug that invalidates the DAG if an object
|
||||
is deleted that still has one or more expressions attached to it.
|
||||
Use this function to remove all expressions before deletion."""
|
||||
if hasattr(obj, "ExpressionEngine"):
|
||||
for attr, expr in obj.ExpressionEngine: # pylint: disable=unused-variable
|
||||
obj.setExpression(attr, None)
|
||||
|
||||
|
||||
def toUnicode(string):
|
||||
'''toUnicode(string) ... returns a unicode version of string regardless of the python version.'''
|
||||
"""toUnicode(string) ... returns a unicode version of string regardless of the python version."""
|
||||
return six.text_type(string)
|
||||
|
||||
|
||||
def isString(string):
|
||||
'''isString(string) ... return True if string is a string, regardless of string type and python version.'''
|
||||
"""isString(string) ... return True if string is a string, regardless of string type and python version."""
|
||||
return isinstance(string, six.string_types)
|
||||
|
||||
|
||||
def keyValueIter(dictionary):
|
||||
'''keyValueIter(dict) ... return iterable object over dictionary's (key,value) tuples.'''
|
||||
"""keyValueIter(dict) ... return iterable object over dictionary's (key,value) tuples."""
|
||||
return six.iteritems(dictionary)
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
'''PathUtils -common functions used in PathScripts for filtering, sorting, and generating gcode toolpath data '''
|
||||
"""PathUtils -common functions used in PathScripts for filtering, sorting, and generating gcode toolpath data """
|
||||
import FreeCAD
|
||||
import Path
|
||||
|
||||
# import PathScripts
|
||||
import PathScripts.PathJob as PathJob
|
||||
import PathScripts.PathGeom as PathGeom
|
||||
@@ -35,16 +36,19 @@ from PySide import QtGui
|
||||
|
||||
# lazily loaded modules
|
||||
from lazy_loader.lazy_loader import LazyLoader
|
||||
DraftGeomUtils = LazyLoader('DraftGeomUtils', globals(), 'DraftGeomUtils')
|
||||
Part = LazyLoader('Part', globals(), 'Part')
|
||||
TechDraw = LazyLoader('TechDraw', globals(), 'TechDraw')
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
# PathLog.trackModule(PathLog.thisModule())
|
||||
DraftGeomUtils = LazyLoader("DraftGeomUtils", globals(), "DraftGeomUtils")
|
||||
Part = LazyLoader("Part", globals(), "Part")
|
||||
TechDraw = LazyLoader("TechDraw", globals(), "TechDraw")
|
||||
|
||||
translate = FreeCAD.Qt.translate
|
||||
|
||||
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
if False:
|
||||
PathLog.setLevel(PathLog.Level.DEBUG, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
else:
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
|
||||
|
||||
UserInput = None
|
||||
@@ -65,6 +69,7 @@ def waiting_effects(function):
|
||||
finally:
|
||||
QtGui.QApplication.restoreOverrideCursor()
|
||||
return res
|
||||
|
||||
return new_function
|
||||
|
||||
|
||||
@@ -78,7 +83,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
|
||||
candidate = Face or Edge
|
||||
tooldiameter=float
|
||||
"""
|
||||
PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(obj, candidate, tooldiameter))
|
||||
PathLog.track(
|
||||
"obj: {} candidate: {} tooldiameter {}".format(obj, candidate, tooldiameter)
|
||||
)
|
||||
if list == type(obj):
|
||||
for shape in obj:
|
||||
if isDrillable(shape, candidate, tooldiameter, includePartials):
|
||||
@@ -87,28 +94,47 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
|
||||
|
||||
drillable = False
|
||||
try:
|
||||
if candidate.ShapeType == 'Face':
|
||||
if candidate.ShapeType == "Face":
|
||||
face = candidate
|
||||
# eliminate flat faces
|
||||
if (round(face.ParameterRange[0], 8) == 0.0) and (round(face.ParameterRange[1], 8) == round(math.pi * 2, 8)):
|
||||
for edge in face.Edges: # Find seam edge and check if aligned to Z axis.
|
||||
if (isinstance(edge.Curve, Part.Line)):
|
||||
if (round(face.ParameterRange[0], 8) == 0.0) and (
|
||||
round(face.ParameterRange[1], 8) == round(math.pi * 2, 8)
|
||||
):
|
||||
for (
|
||||
edge
|
||||
) in face.Edges: # Find seam edge and check if aligned to Z axis.
|
||||
if isinstance(edge.Curve, Part.Line):
|
||||
PathLog.debug("candidate is a circle")
|
||||
v0 = edge.Vertexes[0].Point
|
||||
v1 = edge.Vertexes[1].Point
|
||||
# check if the cylinder seam is vertically aligned. Eliminate tilted holes
|
||||
if (numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)) and \
|
||||
(numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)):
|
||||
if (
|
||||
numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)
|
||||
) and (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)):
|
||||
drillable = True
|
||||
# vector of top center
|
||||
lsp = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMax)
|
||||
lsp = Vector(
|
||||
face.BoundBox.Center.x,
|
||||
face.BoundBox.Center.y,
|
||||
face.BoundBox.ZMax,
|
||||
)
|
||||
# vector of bottom center
|
||||
lep = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMin)
|
||||
lep = Vector(
|
||||
face.BoundBox.Center.x,
|
||||
face.BoundBox.Center.y,
|
||||
face.BoundBox.ZMin,
|
||||
)
|
||||
# check if the cylindrical 'lids' are inside the base
|
||||
# object. This eliminates extruded circles but allows
|
||||
# actual holes.
|
||||
if obj.isInside(lsp, 1e-6, False) or obj.isInside(lep, 1e-6, False):
|
||||
PathLog.track("inside check failed. lsp: {} lep: {}".format(lsp, lep))
|
||||
if obj.isInside(lsp, 1e-6, False) or obj.isInside(
|
||||
lep, 1e-6, False
|
||||
):
|
||||
PathLog.track(
|
||||
"inside check failed. lsp: {} lep: {}".format(
|
||||
lsp, lep
|
||||
)
|
||||
)
|
||||
drillable = False
|
||||
# eliminate elliptical holes
|
||||
elif not hasattr(face.Surface, "Radius"):
|
||||
@@ -119,7 +145,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
|
||||
drillable = face.Surface.Radius >= tooldiameter / 2
|
||||
else:
|
||||
drillable = True
|
||||
elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(face.Surface.Axis, FreeCAD.Vector(0, 0, 1)):
|
||||
elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(
|
||||
face.Surface.Axis, FreeCAD.Vector(0, 0, 1)
|
||||
):
|
||||
if len(face.Edges) == 1 and type(face.Edges[0].Curve) == Part.Circle:
|
||||
center = face.Edges[0].Curve.Center
|
||||
if obj.isInside(center, 1e-6, False):
|
||||
@@ -129,7 +157,9 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
|
||||
drillable = True
|
||||
else:
|
||||
for edge in candidate.Edges:
|
||||
if isinstance(edge.Curve, Part.Circle) and (includePartials or edge.isClosed()):
|
||||
if isinstance(edge.Curve, Part.Circle) and (
|
||||
includePartials or edge.isClosed()
|
||||
):
|
||||
PathLog.debug("candidate is a circle or ellipse")
|
||||
if not hasattr(edge.Curve, "Radius"):
|
||||
PathLog.debug("No radius. Ellipse.")
|
||||
@@ -142,33 +172,38 @@ def isDrillable(obj, candidate, tooldiameter=None, includePartials=False):
|
||||
FreeCAD.Console.PrintMessage(
|
||||
"Found a drillable hole with diameter: {}: "
|
||||
"too small for the current tool with "
|
||||
"diameter: {}".format(edge.Curve.Radius * 2, tooldiameter))
|
||||
"diameter: {}".format(
|
||||
edge.Curve.Radius * 2, tooldiameter
|
||||
)
|
||||
)
|
||||
else:
|
||||
drillable = True
|
||||
PathLog.debug("candidate is drillable: {}".format(drillable))
|
||||
except Exception as ex: # pylint: disable=broad-except
|
||||
PathLog.warning(translate("PathUtils", "Issue determine drillability: {}").format(ex))
|
||||
PathLog.warning(
|
||||
translate("Path", "Issue determine drillability: {}").format(ex)
|
||||
)
|
||||
return drillable
|
||||
|
||||
|
||||
# set at 4 decimal places for testing
|
||||
def fmt(val):
|
||||
return format(val, '.4f')
|
||||
return format(val, ".4f")
|
||||
|
||||
|
||||
def segments(poly):
|
||||
''' A sequence of (x,y) numeric coordinates pairs '''
|
||||
"""A sequence of (x,y) numeric coordinates pairs"""
|
||||
return zip(poly, poly[1:] + [poly[0]])
|
||||
|
||||
|
||||
def loopdetect(obj, edge1, edge2):
|
||||
'''
|
||||
"""
|
||||
Returns a loop wire that includes the two edges.
|
||||
Useful for detecting boundaries of negative space features ie 'holes'
|
||||
If a unique loop is not found, returns None
|
||||
edge1 = edge
|
||||
edge2 = edge
|
||||
'''
|
||||
"""
|
||||
|
||||
PathLog.track()
|
||||
candidates = []
|
||||
@@ -178,7 +213,9 @@ def loopdetect(obj, edge1, edge2):
|
||||
candidates.append((wire.hashCode(), wire))
|
||||
if e.hashCode() == edge2.hashCode():
|
||||
candidates.append((wire.hashCode(), wire))
|
||||
loop = set([x for x in candidates if candidates.count(x) > 1]) # return the duplicate item
|
||||
loop = set(
|
||||
[x for x in candidates if candidates.count(x) > 1]
|
||||
) # return the duplicate item
|
||||
if len(loop) != 1:
|
||||
return None
|
||||
loopwire = next(x for x in loop)[1]
|
||||
@@ -186,18 +223,23 @@ def loopdetect(obj, edge1, edge2):
|
||||
|
||||
|
||||
def horizontalEdgeLoop(obj, edge):
|
||||
'''horizontalEdgeLoop(obj, edge) ... returns a wire in the horizontal plane, if that is the only horizontal wire the given edge is a part of.'''
|
||||
"""horizontalEdgeLoop(obj, edge) ... returns a wire in the horizontal plane, if that is the only horizontal wire the given edge is a part of."""
|
||||
h = edge.hashCode()
|
||||
wires = [w for w in obj.Shape.Wires if any(e.hashCode() == h for e in w.Edges)]
|
||||
loops = [w for w in wires if all(PathGeom.isHorizontal(e) for e in w.Edges) and PathGeom.isHorizontal(Part.Face(w))]
|
||||
loops = [
|
||||
w
|
||||
for w in wires
|
||||
if all(PathGeom.isHorizontal(e) for e in w.Edges)
|
||||
and PathGeom.isHorizontal(Part.Face(w))
|
||||
]
|
||||
if len(loops) == 1:
|
||||
return loops[0]
|
||||
return None
|
||||
|
||||
|
||||
def horizontalFaceLoop(obj, face, faceList=None):
|
||||
'''horizontalFaceLoop(obj, face, faceList=None) ... returns a list of face names which form the walls of a vertical hole face is a part of.
|
||||
All face names listed in faceList must be part of the hole for the solution to be returned.'''
|
||||
"""horizontalFaceLoop(obj, face, faceList=None) ... returns a list of face names which form the walls of a vertical hole face is a part of.
|
||||
All face names listed in faceList must be part of the hole for the solution to be returned."""
|
||||
|
||||
wires = [horizontalEdgeLoop(obj, e) for e in face.Edges]
|
||||
# Not sure if sorting by Area is a premature optimization - but it seems
|
||||
@@ -208,7 +250,11 @@ def horizontalFaceLoop(obj, face, faceList=None):
|
||||
hashes = [e.hashCode() for e in wire.Edges]
|
||||
|
||||
# find all faces that share a an edge with the wire and are vertical
|
||||
faces = ["Face%d" % (i + 1) for i, f in enumerate(obj.Shape.Faces) if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f)]
|
||||
faces = [
|
||||
"Face%d" % (i + 1)
|
||||
for i, f in enumerate(obj.Shape.Faces)
|
||||
if any(e.hashCode() in hashes for e in f.Edges) and PathGeom.isVertical(f)
|
||||
]
|
||||
|
||||
if faceList and not all(f in faces for f in faceList):
|
||||
continue
|
||||
@@ -231,13 +277,19 @@ def horizontalFaceLoop(obj, face, faceList=None):
|
||||
# wire is still closed and it still has the same footprint
|
||||
bb1 = comp.BoundBox
|
||||
bb2 = w.BoundBox
|
||||
if w.isClosed() and PathGeom.isRoughly(bb1.XMin, bb2.XMin) and PathGeom.isRoughly(bb1.XMax, bb2.XMax) and PathGeom.isRoughly(bb1.YMin, bb2.YMin) and PathGeom.isRoughly(bb1.YMax, bb2.YMax):
|
||||
if (
|
||||
w.isClosed()
|
||||
and PathGeom.isRoughly(bb1.XMin, bb2.XMin)
|
||||
and PathGeom.isRoughly(bb1.XMax, bb2.XMax)
|
||||
and PathGeom.isRoughly(bb1.YMin, bb2.YMin)
|
||||
and PathGeom.isRoughly(bb1.YMax, bb2.YMax)
|
||||
):
|
||||
return faces
|
||||
return None
|
||||
|
||||
|
||||
def filterArcs(arcEdge):
|
||||
'''filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list '''
|
||||
"""filterArcs(Edge) -used to split arcs that over 180 degrees. Returns list"""
|
||||
PathLog.track()
|
||||
s = arcEdge
|
||||
if isinstance(s.Curve, Part.Circle):
|
||||
@@ -245,7 +297,7 @@ def filterArcs(arcEdge):
|
||||
angle = abs(s.LastParameter - s.FirstParameter)
|
||||
# overhalfcircle = False
|
||||
goodarc = False
|
||||
if (angle > math.pi):
|
||||
if angle > math.pi:
|
||||
pass
|
||||
# overhalfcircle = True
|
||||
else:
|
||||
@@ -253,9 +305,14 @@ def filterArcs(arcEdge):
|
||||
if not goodarc:
|
||||
arcstpt = s.valueAt(s.FirstParameter)
|
||||
arcmid = s.valueAt(
|
||||
(s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter)
|
||||
arcquad1 = s.valueAt((s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter) # future midpt for arc1
|
||||
arcquad2 = s.valueAt((s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter) # future midpt for arc2
|
||||
(s.LastParameter - s.FirstParameter) * 0.5 + s.FirstParameter
|
||||
)
|
||||
arcquad1 = s.valueAt(
|
||||
(s.LastParameter - s.FirstParameter) * 0.25 + s.FirstParameter
|
||||
) # future midpt for arc1
|
||||
arcquad2 = s.valueAt(
|
||||
(s.LastParameter - s.FirstParameter) * 0.75 + s.FirstParameter
|
||||
) # future midpt for arc2
|
||||
arcendpt = s.valueAt(s.LastParameter)
|
||||
# reconstruct with 2 arcs
|
||||
arcseg1 = Part.ArcOfCircle(arcstpt, arcquad1, arcmid)
|
||||
@@ -277,28 +334,28 @@ def makeWorkplane(shape):
|
||||
Creates a workplane circle at the ZMin level.
|
||||
"""
|
||||
PathLog.track()
|
||||
loc = FreeCAD.Vector(shape.BoundBox.Center.x,
|
||||
shape.BoundBox.Center.y,
|
||||
shape.BoundBox.ZMin)
|
||||
loc = FreeCAD.Vector(
|
||||
shape.BoundBox.Center.x, shape.BoundBox.Center.y, shape.BoundBox.ZMin
|
||||
)
|
||||
c = Part.makeCircle(10, loc)
|
||||
return c
|
||||
|
||||
|
||||
def getEnvelope(partshape, subshape=None, depthparams=None):
|
||||
'''
|
||||
"""
|
||||
getEnvelope(partshape, stockheight=None)
|
||||
returns a shape corresponding to the partshape silhouette extruded to height.
|
||||
if stockheight is given, the returned shape is extruded to that height otherwise the returned shape
|
||||
is the height of the original shape boundbox
|
||||
partshape = solid object
|
||||
stockheight = float - Absolute Z height of the top of material before cutting.
|
||||
'''
|
||||
"""
|
||||
PathLog.track(partshape, subshape, depthparams)
|
||||
|
||||
zShift = 0
|
||||
if subshape is not None:
|
||||
if isinstance(subshape, Part.Face):
|
||||
PathLog.debug('processing a face')
|
||||
PathLog.debug("processing a face")
|
||||
sec = Part.makeCompound([subshape])
|
||||
else:
|
||||
area = Path.Area(Fill=2, Coplanar=0).add(subshape)
|
||||
@@ -306,7 +363,11 @@ def getEnvelope(partshape, subshape=None, depthparams=None):
|
||||
PathLog.debug("About to section with params: {}".format(area.getParams()))
|
||||
sec = area.makeSections(heights=[0.0], project=True)[0].getShape()
|
||||
|
||||
PathLog.debug('partshapeZmin: {}, subshapeZMin: {}, zShift: {}'.format(partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift))
|
||||
PathLog.debug(
|
||||
"partshapeZmin: {}, subshapeZMin: {}, zShift: {}".format(
|
||||
partshape.BoundBox.ZMin, subshape.BoundBox.ZMin, zShift
|
||||
)
|
||||
)
|
||||
|
||||
else:
|
||||
area = Path.Area(Fill=2, Coplanar=0).add(partshape)
|
||||
@@ -318,7 +379,11 @@ def getEnvelope(partshape, subshape=None, depthparams=None):
|
||||
if depthparams is not None:
|
||||
eLength = depthparams.safe_height - depthparams.final_depth
|
||||
zShift = depthparams.final_depth - sec.BoundBox.ZMin
|
||||
PathLog.debug('boundbox zMIN: {} elength: {} zShift {}'.format(partshape.BoundBox.ZMin, eLength, zShift))
|
||||
PathLog.debug(
|
||||
"boundbox zMIN: {} elength: {} zShift {}".format(
|
||||
partshape.BoundBox.ZMin, eLength, zShift
|
||||
)
|
||||
)
|
||||
else:
|
||||
eLength = partshape.BoundBox.ZLength - sec.BoundBox.ZMin
|
||||
|
||||
@@ -335,35 +400,37 @@ def getEnvelope(partshape, subshape=None, depthparams=None):
|
||||
|
||||
|
||||
# Function to extract offset face from shape
|
||||
def getOffsetArea(fcShape,
|
||||
offset,
|
||||
removeHoles=False,
|
||||
# Default: XY plane
|
||||
plane=Part.makeCircle(10),
|
||||
tolerance=1e-4):
|
||||
'''Make an offset area of a shape, projected onto a plane.
|
||||
def getOffsetArea(
|
||||
fcShape,
|
||||
offset,
|
||||
removeHoles=False,
|
||||
# Default: XY plane
|
||||
plane=Part.makeCircle(10),
|
||||
tolerance=1e-4,
|
||||
):
|
||||
"""Make an offset area of a shape, projected onto a plane.
|
||||
Positive offsets expand the area, negative offsets shrink it.
|
||||
Inspired by _buildPathArea() from PathAreaOp.py module. Adjustments made
|
||||
based on notes by @sliptonic at this webpage:
|
||||
https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes.'''
|
||||
PathLog.debug('getOffsetArea()')
|
||||
https://github.com/sliptonic/FreeCAD/wiki/PathArea-notes."""
|
||||
PathLog.debug("getOffsetArea()")
|
||||
|
||||
areaParams = {}
|
||||
areaParams['Offset'] = offset
|
||||
areaParams['Fill'] = 1 # 1
|
||||
areaParams['Outline'] = removeHoles
|
||||
areaParams['Coplanar'] = 0
|
||||
areaParams['SectionCount'] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams['Reorient'] = True
|
||||
areaParams['OpenMode'] = 0
|
||||
areaParams['MaxArcPoints'] = 400 # 400
|
||||
areaParams['Project'] = True
|
||||
areaParams['FitArcs'] = False # Can be buggy & expensive
|
||||
areaParams['Deflection'] = tolerance
|
||||
areaParams['Accuracy'] = tolerance
|
||||
areaParams['Tolerance'] = 1e-5 # Equal point tolerance
|
||||
areaParams['Simplify'] = True
|
||||
areaParams['CleanDistance'] = tolerance / 5
|
||||
areaParams["Offset"] = offset
|
||||
areaParams["Fill"] = 1 # 1
|
||||
areaParams["Outline"] = removeHoles
|
||||
areaParams["Coplanar"] = 0
|
||||
areaParams["SectionCount"] = 1 # -1 = full(all per depthparams??) sections
|
||||
areaParams["Reorient"] = True
|
||||
areaParams["OpenMode"] = 0
|
||||
areaParams["MaxArcPoints"] = 400 # 400
|
||||
areaParams["Project"] = True
|
||||
areaParams["FitArcs"] = False # Can be buggy & expensive
|
||||
areaParams["Deflection"] = tolerance
|
||||
areaParams["Accuracy"] = tolerance
|
||||
areaParams["Tolerance"] = 1e-5 # Equal point tolerance
|
||||
areaParams["Simplify"] = True
|
||||
areaParams["CleanDistance"] = tolerance / 5
|
||||
|
||||
area = Path.Area() # Create instance of Area() class object
|
||||
# Set working plane normal to Z=1
|
||||
@@ -380,11 +447,16 @@ def getOffsetArea(fcShape,
|
||||
def reverseEdge(e):
|
||||
if DraftGeomUtils.geomType(e) == "Circle":
|
||||
arcstpt = e.valueAt(e.FirstParameter)
|
||||
arcmid = e.valueAt((e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter)
|
||||
arcmid = e.valueAt(
|
||||
(e.LastParameter - e.FirstParameter) * 0.5 + e.FirstParameter
|
||||
)
|
||||
arcendpt = e.valueAt(e.LastParameter)
|
||||
arcofCirc = Part.ArcOfCircle(arcendpt, arcmid, arcstpt)
|
||||
newedge = arcofCirc.toShape()
|
||||
elif DraftGeomUtils.geomType(e) == "LineSegment" or DraftGeomUtils.geomType(e) == "Line":
|
||||
elif (
|
||||
DraftGeomUtils.geomType(e) == "LineSegment"
|
||||
or DraftGeomUtils.geomType(e) == "Line"
|
||||
):
|
||||
stpt = e.valueAt(e.FirstParameter)
|
||||
endpt = e.valueAt(e.LastParameter)
|
||||
newedge = Part.makeLine(endpt, stpt)
|
||||
@@ -393,7 +465,7 @@ def reverseEdge(e):
|
||||
|
||||
|
||||
def getToolControllers(obj, proxy=None):
|
||||
'''returns all the tool controllers'''
|
||||
"""returns all the tool controllers"""
|
||||
if proxy is None:
|
||||
proxy = obj.Proxy
|
||||
try:
|
||||
@@ -408,11 +480,11 @@ def getToolControllers(obj, proxy=None):
|
||||
|
||||
|
||||
def findToolController(obj, proxy, name=None):
|
||||
'''returns a tool controller with a given name.
|
||||
"""returns a tool controller with a given name.
|
||||
If no name is specified, returns the first controller.
|
||||
if no controller is found, returns None'''
|
||||
if no controller is found, returns None"""
|
||||
|
||||
PathLog.track('name: {}'.format(name))
|
||||
PathLog.track("name: {}".format(name))
|
||||
c = None
|
||||
if UserInput:
|
||||
c = UserInput.selectedToolController()
|
||||
@@ -438,12 +510,16 @@ def findToolController(obj, proxy, name=None):
|
||||
|
||||
|
||||
def findParentJob(obj):
|
||||
'''retrieves a parent job object for an operation or other Path object'''
|
||||
"""retrieves a parent job object for an operation or other Path object"""
|
||||
PathLog.track()
|
||||
for i in obj.InList:
|
||||
if hasattr(i, 'Proxy') and isinstance(i.Proxy, PathJob.ObjectJob):
|
||||
if hasattr(i, "Proxy") and isinstance(i.Proxy, PathJob.ObjectJob):
|
||||
return i
|
||||
if i.TypeId == "Path::FeaturePython" or i.TypeId == "Path::FeatureCompoundPython" or i.TypeId == "App::DocumentObjectGroup":
|
||||
if (
|
||||
i.TypeId == "Path::FeaturePython"
|
||||
or i.TypeId == "Path::FeatureCompoundPython"
|
||||
or i.TypeId == "App::DocumentObjectGroup"
|
||||
):
|
||||
grandParent = findParentJob(i)
|
||||
if grandParent is not None:
|
||||
return grandParent
|
||||
@@ -451,16 +527,16 @@ def findParentJob(obj):
|
||||
|
||||
|
||||
def GetJobs(jobname=None):
|
||||
'''returns all jobs in the current document. If name is given, returns that job'''
|
||||
"""returns all jobs in the current document. If name is given, returns that job"""
|
||||
if jobname:
|
||||
return [job for job in PathJob.Instances() if job.Name == jobname]
|
||||
return PathJob.Instances()
|
||||
|
||||
|
||||
def addToJob(obj, jobname=None):
|
||||
'''adds a path object to a job
|
||||
"""adds a path object to a job
|
||||
obj = obj
|
||||
jobname = None'''
|
||||
jobname = None"""
|
||||
PathLog.track(jobname)
|
||||
|
||||
job = None
|
||||
@@ -486,14 +562,14 @@ def addToJob(obj, jobname=None):
|
||||
|
||||
|
||||
def rapid(x=None, y=None, z=None):
|
||||
""" Returns gcode string to perform a rapid move."""
|
||||
"""Returns gcode string to perform a rapid move."""
|
||||
retstr = "G00"
|
||||
if (x is not None) or (y is not None) or (z is not None):
|
||||
if (x is not None):
|
||||
if x is not None:
|
||||
retstr += " X" + str("%.4f" % x)
|
||||
if (y is not None):
|
||||
if y is not None:
|
||||
retstr += " Y" + str("%.4f" % y)
|
||||
if (z is not None):
|
||||
if z is not None:
|
||||
retstr += " Z" + str("%.4f" % z)
|
||||
else:
|
||||
return ""
|
||||
@@ -501,19 +577,19 @@ def rapid(x=None, y=None, z=None):
|
||||
|
||||
|
||||
def feed(x=None, y=None, z=None, horizFeed=0, vertFeed=0):
|
||||
""" Return gcode string to perform a linear feed."""
|
||||
"""Return gcode string to perform a linear feed."""
|
||||
retstr = "G01 F"
|
||||
if(x is None) and (y is None):
|
||||
if (x is None) and (y is None):
|
||||
retstr += str("%.4f" % horizFeed)
|
||||
else:
|
||||
retstr += str("%.4f" % vertFeed)
|
||||
|
||||
if (x is not None) or (y is not None) or (z is not None):
|
||||
if (x is not None):
|
||||
if x is not None:
|
||||
retstr += " X" + str("%.4f" % x)
|
||||
if (y is not None):
|
||||
if y is not None:
|
||||
retstr += " Y" + str("%.4f" % y)
|
||||
if (z is not None):
|
||||
if z is not None:
|
||||
retstr += " Z" + str("%.4f" % z)
|
||||
else:
|
||||
return ""
|
||||
@@ -537,7 +613,10 @@ def arc(cx, cy, sx, sy, ex, ey, horizFeed=0, ez=None, ccw=False):
|
||||
"""
|
||||
|
||||
eps = 0.01
|
||||
if (math.sqrt((cx - sx)**2 + (cy - sy)**2) - math.sqrt((cx - ex)**2 + (cy - ey)**2)) >= eps:
|
||||
if (
|
||||
math.sqrt((cx - sx) ** 2 + (cy - sy) ** 2)
|
||||
- math.sqrt((cx - ex) ** 2 + (cy - ey) ** 2)
|
||||
) >= eps:
|
||||
PathLog.error(translate("Path", "Illegal arc: Start and end radii not equal"))
|
||||
return ""
|
||||
|
||||
@@ -578,7 +657,7 @@ def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed
|
||||
helixY = plungePos.y
|
||||
|
||||
helixCirc = math.pi * toold * plungeR
|
||||
dzPerRev = math.sin(rampangle / 180. * math.pi) * helixCirc
|
||||
dzPerRev = math.sin(rampangle / 180.0 * math.pi) * helixCirc
|
||||
|
||||
# Go to the start of the helix position
|
||||
helixCmds += rapid(helixX, helixY)
|
||||
@@ -589,14 +668,34 @@ def helicalPlunge(plungePos, rampangle, destZ, startZ, toold, plungeR, horizFeed
|
||||
curZ = max(startZ - dzPerRev, destZ)
|
||||
done = False
|
||||
while not done:
|
||||
done = (curZ == destZ)
|
||||
done = curZ == destZ
|
||||
# NOTE: FreeCAD doesn't render this, but at least LinuxCNC considers it valid
|
||||
# helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX, helixY, ez = curZ, ccw=True)
|
||||
|
||||
# Use two half-helixes; FreeCAD renders that correctly,
|
||||
# and it fits with the other code breaking up 360-degree arcs
|
||||
helixCmds += arc(plungePos.x, plungePos.y, helixX, helixY, helixX - toold * plungeR, helixY, horizFeed, ez=(curZ + lastZ) / 2., ccw=True)
|
||||
helixCmds += arc(plungePos.x, plungePos.y, helixX - toold * plungeR, helixY, helixX, helixY, horizFeed, ez=curZ, ccw=True)
|
||||
helixCmds += arc(
|
||||
plungePos.x,
|
||||
plungePos.y,
|
||||
helixX,
|
||||
helixY,
|
||||
helixX - toold * plungeR,
|
||||
helixY,
|
||||
horizFeed,
|
||||
ez=(curZ + lastZ) / 2.0,
|
||||
ccw=True,
|
||||
)
|
||||
helixCmds += arc(
|
||||
plungePos.x,
|
||||
plungePos.y,
|
||||
helixX - toold * plungeR,
|
||||
helixY,
|
||||
helixX,
|
||||
helixY,
|
||||
horizFeed,
|
||||
ez=curZ,
|
||||
ccw=True,
|
||||
)
|
||||
lastZ = curZ
|
||||
curZ = max(curZ - dzPerRev, destZ)
|
||||
|
||||
@@ -619,7 +718,7 @@ def rampPlunge(edge, rampangle, destZ, startZ):
|
||||
"""
|
||||
|
||||
rampCmds = "(START RAMP PLUNGE)\n"
|
||||
if(edge is None):
|
||||
if edge is None:
|
||||
raise Exception("Ramp plunging requires an edge!")
|
||||
|
||||
sPoint = edge.Vertexes[0].Point
|
||||
@@ -630,7 +729,7 @@ def rampPlunge(edge, rampangle, destZ, startZ):
|
||||
ePoint = edge.Vertexes[-1].Point
|
||||
|
||||
rampDist = edge.Length
|
||||
rampDZ = math.sin(rampangle / 180. * math.pi) * rampDist
|
||||
rampDZ = math.sin(rampangle / 180.0 * math.pi) * rampDist
|
||||
|
||||
rampCmds += rapid(sPoint.x, sPoint.y)
|
||||
rampCmds += rapid(z=startZ)
|
||||
@@ -640,7 +739,7 @@ def rampPlunge(edge, rampangle, destZ, startZ):
|
||||
curZ = max(startZ - rampDZ, destZ)
|
||||
done = False
|
||||
while not done:
|
||||
done = (curZ == destZ)
|
||||
done = curZ == destZ
|
||||
|
||||
# If it's an arc, handle it!
|
||||
if isinstance(edge.Curve, Part.Circle):
|
||||
@@ -656,9 +755,9 @@ def rampPlunge(edge, rampangle, destZ, startZ):
|
||||
|
||||
|
||||
def sort_jobs(locations, keys, attractors=None):
|
||||
""" sort holes by the nearest neighbor method
|
||||
keys: two-element list of keys for X and Y coordinates. for example ['x','y']
|
||||
originally written by m0n5t3r for PathHelix
|
||||
"""sort holes by the nearest neighbor method
|
||||
keys: two-element list of keys for X and Y coordinates. for example ['x','y']
|
||||
originally written by m0n5t3r for PathHelix
|
||||
"""
|
||||
if attractors is None:
|
||||
attractors = []
|
||||
@@ -671,7 +770,7 @@ def sort_jobs(locations, keys, attractors=None):
|
||||
attractors = attractors or [keys[0]]
|
||||
|
||||
def sqdist(a, b):
|
||||
""" square Euclidean distance """
|
||||
"""square Euclidean distance"""
|
||||
d = 0
|
||||
for k in keys:
|
||||
d += (a[k] - b[k]) ** 2
|
||||
@@ -740,7 +839,9 @@ def guessDepths(objshape, subs=None):
|
||||
elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf
|
||||
final = fbb.ZMin
|
||||
|
||||
return depth_params(clearance, safe, start, 1.0, 0.0, final, user_depths=None, equalstep=False)
|
||||
return depth_params(
|
||||
clearance, safe, start, 1.0, 0.0, final, user_depths=None, equalstep=False
|
||||
)
|
||||
|
||||
|
||||
def drillTipLength(tool):
|
||||
@@ -750,28 +851,36 @@ def drillTipLength(tool):
|
||||
PathLog.error(translate("Path", "Legacy Tools not supported"))
|
||||
return 0.0
|
||||
|
||||
if not hasattr(tool, 'TipAngle'):
|
||||
if not hasattr(tool, "TipAngle"):
|
||||
PathLog.error(translate("Path", "Selected tool is not a drill"))
|
||||
return 0.0
|
||||
|
||||
angle = tool.TipAngle
|
||||
|
||||
if angle <= 0 or angle >= 180:
|
||||
PathLog.error(translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°") % angle)
|
||||
PathLog.error(
|
||||
translate("Path", "Invalid Cutting Edge Angle %.2f, must be >0° and <=180°")
|
||||
% angle
|
||||
)
|
||||
return 0.0
|
||||
|
||||
theta = math.radians(angle)
|
||||
length = (float(tool.Diameter) / 2) / math.tan(theta / 2)
|
||||
|
||||
if length < 0:
|
||||
PathLog.error(translate("Path", "Cutting Edge Angle (%.2f) results in negative tool tip length") % angle)
|
||||
PathLog.error(
|
||||
translate(
|
||||
"Path", "Cutting Edge Angle (%.2f) results in negative tool tip length"
|
||||
)
|
||||
% angle
|
||||
)
|
||||
return 0.0
|
||||
|
||||
return length
|
||||
|
||||
|
||||
class depth_params(object):
|
||||
'''calculates the intermediate depth values for various operations given the starting, ending, and stepdown parameters
|
||||
"""calculates the intermediate depth values for various operations given the starting, ending, and stepdown parameters
|
||||
(self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False)
|
||||
|
||||
Note: if user_depths are supplied, only user_depths will be used.
|
||||
@@ -784,10 +893,20 @@ class depth_params(object):
|
||||
final_depth: Lowest point of the cutting operation
|
||||
user_depths: List of specified depths
|
||||
equalstep: Boolean. If True, steps down except Z_finish_depth will be balanced.
|
||||
'''
|
||||
"""
|
||||
|
||||
def __init__(self, clearance_height, safe_height, start_depth, step_down, z_finish_step, final_depth, user_depths=None, equalstep=False):
|
||||
'''self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False'''
|
||||
def __init__(
|
||||
self,
|
||||
clearance_height,
|
||||
safe_height,
|
||||
start_depth,
|
||||
step_down,
|
||||
z_finish_step,
|
||||
final_depth,
|
||||
user_depths=None,
|
||||
equalstep=False,
|
||||
):
|
||||
"""self, clearance_height, safe_height, start_depth, step_down, z_finish_depth, final_depth, [user_depths=None], equalstep=False"""
|
||||
|
||||
self.__clearance_height = clearance_height
|
||||
self.__safe_height = safe_height
|
||||
@@ -800,7 +919,7 @@ class depth_params(object):
|
||||
self.index = 0
|
||||
|
||||
if self.__z_finish_step > self.__step_down:
|
||||
raise ValueError('z_finish_step must be less than step_down')
|
||||
raise ValueError("z_finish_step must be less than step_down")
|
||||
|
||||
def __iter__(self):
|
||||
self.index = 0
|
||||
@@ -874,8 +993,8 @@ class depth_params(object):
|
||||
return self.__user_depths
|
||||
|
||||
def __get_depths(self, equalstep=False):
|
||||
'''returns a list of depths to be used in order from first to last.
|
||||
equalstep=True: all steps down before the finish pass will be equalized.'''
|
||||
"""returns a list of depths to be used in order from first to last.
|
||||
equalstep=True: all steps down before the finish pass will be equalized."""
|
||||
|
||||
if self.user_depths is not None:
|
||||
return self.__user_depths
|
||||
@@ -895,18 +1014,22 @@ class depth_params(object):
|
||||
return depths
|
||||
|
||||
if equalstep:
|
||||
depths += self.__equal_steps(self.__start_depth, depths[-1], self.__step_down)[1:]
|
||||
depths += self.__equal_steps(
|
||||
self.__start_depth, depths[-1], self.__step_down
|
||||
)[1:]
|
||||
else:
|
||||
depths += self.__fixed_steps(self.__start_depth, depths[-1], self.__step_down)[1:]
|
||||
depths += self.__fixed_steps(
|
||||
self.__start_depth, depths[-1], self.__step_down
|
||||
)[1:]
|
||||
|
||||
depths.reverse()
|
||||
return depths
|
||||
|
||||
def __equal_steps(self, start, stop, max_size):
|
||||
'''returns a list of depths beginning with the bottom (included), ending
|
||||
"""returns a list of depths beginning with the bottom (included), ending
|
||||
with the top (not included).
|
||||
all steps are of equal size, which is as big as possible but not bigger
|
||||
than max_size.'''
|
||||
than max_size."""
|
||||
|
||||
steps_needed = math.ceil((start - stop) / max_size)
|
||||
depths = list(numpy.linspace(stop, start, steps_needed, endpoint=False))
|
||||
@@ -914,10 +1037,10 @@ class depth_params(object):
|
||||
return depths
|
||||
|
||||
def __fixed_steps(self, start, stop, size):
|
||||
'''returns a list of depths beginning with the bottom (included), ending
|
||||
"""returns a list of depths beginning with the bottom (included), ending
|
||||
with the top (not included).
|
||||
all steps are of size 'size' except the one at the bottom which can be
|
||||
smaller.'''
|
||||
smaller."""
|
||||
|
||||
fullsteps = int((start - stop) / size)
|
||||
last_step = start - (fullsteps * size)
|
||||
@@ -968,17 +1091,17 @@ def simplify3dLine(line, tolerance=1e-4):
|
||||
|
||||
|
||||
def RtoIJ(startpoint, command):
|
||||
'''
|
||||
"""
|
||||
This function takes a startpoint and an arc command in radius mode and
|
||||
returns an arc command in IJ mode. Useful for preprocessor scripts
|
||||
'''
|
||||
if 'R' not in command.Parameters:
|
||||
raise ValueError('No R parameter in command')
|
||||
if command.Name not in ['G2', 'G02', 'G03', 'G3']:
|
||||
raise ValueError('Not an arc command')
|
||||
"""
|
||||
if "R" not in command.Parameters:
|
||||
raise ValueError("No R parameter in command")
|
||||
if command.Name not in ["G2", "G02", "G03", "G3"]:
|
||||
raise ValueError("Not an arc command")
|
||||
|
||||
endpoint = command.Placement.Base
|
||||
radius = command.Parameters['R']
|
||||
radius = command.Parameters["R"]
|
||||
|
||||
# calculate the IJ
|
||||
# we take a vector between the start and endpoints
|
||||
@@ -988,7 +1111,7 @@ def RtoIJ(startpoint, command):
|
||||
perp = chord.cross(FreeCAD.Vector(0, 0, 1))
|
||||
|
||||
# use pythagoras to get the perp length
|
||||
plength = math.sqrt(radius**2 - (chord.Length / 2)**2)
|
||||
plength = math.sqrt(radius ** 2 - (chord.Length / 2) ** 2)
|
||||
perp.normalize()
|
||||
perp.scale(plength, plength, plength)
|
||||
|
||||
@@ -996,9 +1119,9 @@ def RtoIJ(startpoint, command):
|
||||
relativecenter = chord.scale(0.5, 0.5, 0.5).add(perp)
|
||||
|
||||
# build new command
|
||||
params = { c: command.Parameters[c] for c in 'XYZF' if c in command.Parameters}
|
||||
params['I'] = relativecenter.x
|
||||
params['J'] = relativecenter.y
|
||||
params = {c: command.Parameters[c] for c in "XYZF" if c in command.Parameters}
|
||||
params["I"] = relativecenter.x
|
||||
params["J"] = relativecenter.y
|
||||
|
||||
newcommand = Path.Command(command.Name)
|
||||
newcommand.Parameters = params
|
||||
|
||||
Reference in New Issue
Block a user