Merge branch 'master' of github.com:FreeCAD/FreeCAD
This commit is contained in:
@@ -33,6 +33,7 @@ SET(PathScripts_SRCS
|
||||
PathScripts/PathCircularHoleBaseGui.py
|
||||
PathScripts/PathComment.py
|
||||
PathScripts/PathCopy.py
|
||||
PathScripts/PathCamoticsGui.py
|
||||
PathScripts/PathCustom.py
|
||||
PathScripts/PathCustomGui.py
|
||||
PathScripts/PathDeburr.py
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<file>icons/Path_BStep.svg</file>
|
||||
<file>icons/Path_BStop.svg</file>
|
||||
<file>icons/Path_BaseGeometry.svg</file>
|
||||
<file>icons/Path_Camotics.svg</file>
|
||||
<file>icons/Path_Comment.svg</file>
|
||||
<file>icons/Path_Compound.svg</file>
|
||||
<file>icons/Path_Contour.svg</file>
|
||||
@@ -130,6 +131,7 @@
|
||||
<file>panels/ToolBitSelector.ui</file>
|
||||
<file>panels/ToolEditor.ui</file>
|
||||
<file>panels/ToolLibraryEditor.ui</file>
|
||||
<file>panels/TaskPathCamoticsSim.ui</file>
|
||||
<file>panels/TaskPathSimulator.ui</file>
|
||||
<file>panels/ZCorrectEdit.ui</file>
|
||||
<file>preferences/Advanced.ui</file>
|
||||
|
||||
644
src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg
Normal file
644
src/Mod/Path/Gui/Resources/icons/Path_Camotics.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 21 KiB |
BIN
src/Mod/Path/Gui/Resources/icons/camotics-logo.png
Normal file
BIN
src/Mod/Path/Gui/Resources/icons/camotics-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
103
src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui
Normal file
103
src/Mod/Path/Gui/Resources/panels/TaskPathCamoticsSim.ui
Normal file
@@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TaskPathSimulator</class>
|
||||
<widget class="QDialog" name="TaskPathSimulator">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>404</width>
|
||||
<height>364</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Path Simulator</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="lstPathObjects">
|
||||
<property name="selectionMode">
|
||||
<enum>QAbstractItemView::ContiguousSelection</enum>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectRows</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QLabel" name="txtStatus">
|
||||
<property name="toolTip">
|
||||
<string>Estimated time to run to the selected point in the job</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Estimated run time:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="txtRunEstimate">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QProgressBar" name="progressBar">
|
||||
<property name="value">
|
||||
<number>23</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnRun">
|
||||
<property name="text">
|
||||
<string>Stop</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnLaunchCamotics">
|
||||
<property name="text">
|
||||
<string>Launch Camotics</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../Path.qrc">
|
||||
<normaloff>:/icons/Path_Camotics.svg</normaloff>:/icons/Path_Camotics.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources>
|
||||
<include location="../Path.qrc"/>
|
||||
</resources>
|
||||
<connections/>
|
||||
<slots>
|
||||
<slot>SimStop()</slot>
|
||||
<slot>SimPlay()</slot>
|
||||
<slot>SimPause()</slot>
|
||||
<slot>SimStep()</slot>
|
||||
<slot>SimFF()</slot>
|
||||
</slots>
|
||||
</ui>
|
||||
@@ -78,6 +78,7 @@ class PathWorkbench (Workbench):
|
||||
from PathScripts import PathToolBitLibraryCmd
|
||||
|
||||
import PathCommands
|
||||
|
||||
PathGuiInit.Startup()
|
||||
|
||||
# build commands list
|
||||
@@ -96,8 +97,6 @@ class PathWorkbench (Workbench):
|
||||
"Path_DressupLeadInOut", "Path_DressupRampEntry",
|
||||
"Path_DressupTag", "Path_DressupZCorrect"]
|
||||
extracmdlist = []
|
||||
# modcmdmore = ["Path_Hop",]
|
||||
# remotecmdlist = ["Path_Remote"]
|
||||
specialcmdlist = []
|
||||
|
||||
|
||||
@@ -122,6 +121,12 @@ class PathWorkbench (Workbench):
|
||||
twodopcmdlist.append("Path_Slot")
|
||||
|
||||
if PathPreferences.advancedOCLFeaturesEnabled():
|
||||
try:
|
||||
import camotics
|
||||
toolcmdlist.append("Path_Camotics")
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
try:
|
||||
import ocl # pylint: disable=unused-variable
|
||||
from PathScripts import PathSurfaceGui
|
||||
|
||||
@@ -24,8 +24,10 @@ import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
import PathScripts
|
||||
from PathScripts import PathLog
|
||||
from PySide import QtCore
|
||||
import math
|
||||
import random
|
||||
|
||||
__doc__ = """Path Array object and FreeCAD command"""
|
||||
|
||||
@@ -36,22 +38,28 @@ def translate(context, text, disambig=None):
|
||||
class ObjectArray:
|
||||
|
||||
def __init__(self, obj):
|
||||
obj.addProperty("App::PropertyLink", "Base",
|
||||
"Path", "The path to array")
|
||||
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", "The spacing between the array copies in Linear pattern")
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The spacing between the array copies in Linear pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "CopiesX",
|
||||
"Path", "The number of copies in X direction in Linear pattern")
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in X direction in Linear pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "CopiesY",
|
||||
"Path", "The number of copies in Y direction in Linear pattern")
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Y direction in Linear pattern"))
|
||||
obj.addProperty("App::PropertyAngle", "Angle",
|
||||
"Path", "Total angle in Polar pattern")
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","Total angle in Polar pattern"))
|
||||
obj.addProperty("App::PropertyInteger", "Copies",
|
||||
"Path", "The number of copies in Linear 1D and Polar pattern")
|
||||
"Path", QtCore.QT_TRANSLATE_NOOP("App::Property","The number of copies in Linear 1D and Polar pattern"))
|
||||
obj.addProperty("App::PropertyVector", "Centre",
|
||||
"Path", "The centre of rotation in Polar pattern")
|
||||
"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"))
|
||||
|
||||
@@ -67,6 +75,9 @@ class ObjectArray:
|
||||
return None
|
||||
|
||||
def setEditorProperties(self, obj):
|
||||
obj.setEditorMode('JitterPercent', 0)
|
||||
obj.setEditorMode('JitterMagnitude', 0)
|
||||
obj.setEditorMode('ToolController', 2)
|
||||
if obj.Type == 'Linear2D':
|
||||
obj.setEditorMode('Angle', 2)
|
||||
obj.setEditorMode('Copies', 2)
|
||||
@@ -75,6 +86,7 @@ class ObjectArray:
|
||||
obj.setEditorMode('CopiesX', 0)
|
||||
obj.setEditorMode('CopiesY', 0)
|
||||
obj.setEditorMode('Offset', 0)
|
||||
obj.setEditorMode('SwapDirection', False)
|
||||
elif obj.Type == 'Polar':
|
||||
obj.setEditorMode('Angle', 0)
|
||||
obj.setEditorMode('Copies', 0)
|
||||
@@ -155,57 +167,99 @@ class ObjectArray:
|
||||
|
||||
return newPath
|
||||
|
||||
def calculateJitter(self, obj, pos):
|
||||
if random.randint(0,100) < obj.JitterPercent:
|
||||
pos.x = pos.x + random.uniform(-obj.JitterMagnitude.x, obj.JitterMagnitude.y)
|
||||
pos.y = pos.y + random.uniform(-obj.JitterMagnitude.y, obj.JitterMagnitude.y)
|
||||
pos.z = pos.z + random.uniform(-obj.JitterMagnitude.z, obj.JitterMagnitude.z)
|
||||
return pos
|
||||
|
||||
|
||||
def execute(self, obj):
|
||||
if obj.Base:
|
||||
if not obj.Base.isDerivedFrom("Path::Feature"):
|
||||
return
|
||||
if not obj.Base.Path:
|
||||
return
|
||||
if not obj.Base.ToolController:
|
||||
return
|
||||
|
||||
obj.ToolController = obj.Base.ToolController
|
||||
# backwards compatibility for PathArrays created before support for multiple bases
|
||||
if isinstance(obj.Base, list):
|
||||
base = obj.Base
|
||||
else:
|
||||
base = [obj.Base]
|
||||
|
||||
# build copies
|
||||
basepath = obj.Base.Path
|
||||
output = ""
|
||||
if obj.Type == 'Linear1D':
|
||||
for i in range(obj.Copies):
|
||||
if len(base)==0:
|
||||
return
|
||||
|
||||
obj.ToolController = base[0].ToolController
|
||||
for b in base:
|
||||
if not b.isDerivedFrom("Path::Feature"):
|
||||
return
|
||||
if not b.Path:
|
||||
return
|
||||
if not b.ToolController:
|
||||
return
|
||||
if b.ToolController != obj.ToolController:
|
||||
# this may be important if Job output is split by tool controller
|
||||
PathLog.warning(QtCore.QT_TRANSLATE_NOOP("App::Property",'Arrays of paths having different tool controllers are handled according to the tool controller of the first path.'))
|
||||
|
||||
# build copies
|
||||
output = ""
|
||||
random.seed(obj.Name)
|
||||
if obj.Type == 'Linear1D':
|
||||
for i in range(obj.Copies):
|
||||
pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
pos = FreeCAD.Vector(obj.Offset.x * (i + 1), obj.Offset.y * (i + 1), 0)
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl)
|
||||
for cm in basepath.Commands])
|
||||
for cm in b.Path.Commands])
|
||||
output += np.toGCode()
|
||||
|
||||
elif obj.Type == 'Linear2D':
|
||||
elif obj.Type == 'Linear2D':
|
||||
if obj.SwapDirection:
|
||||
for i in range(obj.CopiesY + 1):
|
||||
for j in range(obj.CopiesX + 1):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * j, obj.Offset.y * i, 0)
|
||||
else:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * (obj.CopiesX - j), obj.Offset.y * i, 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
# 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])
|
||||
output += np.toGCode()
|
||||
else:
|
||||
for i in range(obj.CopiesX + 1):
|
||||
for j in range(obj.CopiesY + 1):
|
||||
pl = FreeCAD.Placement()
|
||||
# do not process the index 0,0. It will be processed at basepath
|
||||
if not (i == 0 and j == 0):
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0)
|
||||
else:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0)
|
||||
if (i % 2) == 0:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * j, 0)
|
||||
else:
|
||||
pos = FreeCAD.Vector(obj.Offset.x * i, obj.Offset.y * (obj.CopiesY - j), 0)
|
||||
pos = self.calculateJitter(obj, pos)
|
||||
|
||||
pl.move(pos)
|
||||
np = Path.Path([cm.transform(pl)
|
||||
for cm in basepath.Commands])
|
||||
output += np.toGCode()
|
||||
for b in base:
|
||||
pl = FreeCAD.Placement()
|
||||
# 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])
|
||||
output += np.toGCode()
|
||||
|
||||
else:
|
||||
for i in range(obj.Copies):
|
||||
|
||||
else:
|
||||
for i in range(obj.Copies):
|
||||
for b in base:
|
||||
ang = 360
|
||||
if obj.Copies > 0:
|
||||
ang = obj.Angle / obj.Copies * (1 + i)
|
||||
|
||||
np = self.rotatePath(basepath, ang, obj.Centre)
|
||||
np = self.rotatePath(b.Path.Commands, ang, obj.Centre)
|
||||
output += np.toGCode()
|
||||
# print output
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
|
||||
# print output
|
||||
path = Path.Path(output)
|
||||
obj.Path = path
|
||||
|
||||
|
||||
class ViewProviderArray:
|
||||
@@ -237,7 +291,7 @@ 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 a selected path")}
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Array", "Creates an array from selected path(s)")}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
@@ -252,26 +306,26 @@ class CommandPathArray:
|
||||
|
||||
# check that the selection contains exactly what we want
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if len(selection) != 1:
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Array", "Please select exactly one path object")+"\n")
|
||||
return
|
||||
if not(selection[0].isDerivedFrom("Path::Feature")):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Array", "Please select exactly one path object")+"\n")
|
||||
return
|
||||
|
||||
for sel in selection:
|
||||
if not(sel.isDerivedFrom("Path::Feature")):
|
||||
FreeCAD.Console.PrintError(
|
||||
translate("Path_Array", "Arrays can be created only from Path operations.")+"\n")
|
||||
return
|
||||
|
||||
# if everything is ok, execute and register the transaction in the
|
||||
# undo/redo stack
|
||||
FreeCAD.ActiveDocument.openTransaction("Create Array")
|
||||
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(
|
||||
'obj.Base = (FreeCAD.ActiveDocument.' + selection[0].Name + ')')
|
||||
# FreeCADGui.doCommand('PathScripts.PathArray.ViewProviderArray(obj.ViewObject)')
|
||||
|
||||
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)')
|
||||
FreeCAD.ActiveDocument.commitTransaction()
|
||||
|
||||
400
src/Mod/Path/PathScripts/PathCamoticsGui.py
Normal file
400
src/Mod/Path/PathScripts/PathCamoticsGui.py
Normal file
@@ -0,0 +1,400 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2020 sliptonic <shopinthewoods@gmail.com> *
|
||||
# * *
|
||||
# * This program is free software; you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation; either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * This program is distributed in the hope that it will be useful, *
|
||||
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
# * GNU Library General Public License for more details. *
|
||||
# * *
|
||||
# * You should have received a copy of the GNU Library General Public *
|
||||
# * License along with this program; if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
import PathScripts.PathLog as PathLog
|
||||
# from pivy import coin
|
||||
# from itertools import cycle
|
||||
import json
|
||||
import Mesh
|
||||
import time
|
||||
import camotics
|
||||
import PathScripts.PathPost as PathPost
|
||||
import io
|
||||
import PathScripts
|
||||
import queue
|
||||
from threading import Thread, Lock
|
||||
import subprocess
|
||||
import PySide
|
||||
|
||||
from PySide import QtCore, QtGui
|
||||
|
||||
__title__ = "Camotics Simulator"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "https://www.freecadweb.org"
|
||||
__doc__ = "Task panel for Camotics Simulation"
|
||||
|
||||
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
|
||||
PathLog.trackModule(PathLog.thisModule())
|
||||
|
||||
# Qt translation handling
|
||||
def translate(context, text, disambig=None):
|
||||
return QtCore.QCoreApplication.translate(context, text, disambig)
|
||||
|
||||
|
||||
class CAMoticsUI:
|
||||
def __init__(self, simulation):
|
||||
# this will create a Qt widget from our ui file
|
||||
self.form = FreeCADGui.PySideUic.loadUi(":/panels/TaskPathCamoticsSim.ui")
|
||||
self.simulation = simulation
|
||||
self.opModel = PySide.QtGui.QStandardItemModel(0, 0)#len(self.columnNames()))
|
||||
self.initializeUI()
|
||||
self.lock = False
|
||||
|
||||
def columnNames(self):
|
||||
return ['Operation']
|
||||
|
||||
def loadData(self):
|
||||
self.opModel.clear()
|
||||
self.opModel.setHorizontalHeaderLabels(self.columnNames())
|
||||
ops = self.simulation.finalpostlist
|
||||
for i, op in enumerate(ops):
|
||||
libItem = PySide.QtGui.QStandardItem(op.Label)
|
||||
libItem.setToolTip('op')
|
||||
libItem.setData(op)
|
||||
#libItem.setIcon(PySide.QtGui.QPixmap(':/icons/Path_ToolTable.svg'))
|
||||
self.opModel.appendRow(libItem)
|
||||
|
||||
|
||||
def initializeUI(self):
|
||||
self.form.progressBar.reset()
|
||||
self.updateEstimate("00:00:00")
|
||||
self.form.btnRun.setText("Run")
|
||||
self.form.btnLaunchCamotics.clicked.connect(self.launchCamotics)
|
||||
self.simulation.progressUpdate.connect(self.calculating)
|
||||
self.simulation.estimateChanged.connect(self.updateEstimate)
|
||||
self.form.btnRun.clicked.connect(self.runButton)
|
||||
self.form.lstPathObjects.setModel(self.opModel)
|
||||
self.form.lstPathObjects.selectionModel().selectionChanged.connect(self.reSelect)
|
||||
self.loadData()
|
||||
self.selectAll()
|
||||
|
||||
def selectAll(self):
|
||||
selmodel = self.form.lstPathObjects.selectionModel()
|
||||
index0 = self.opModel.index(0, 0)
|
||||
index1 = self.opModel.index(self.opModel.rowCount()-1,0)
|
||||
itemSelection = QtCore.QItemSelection(index0, index1)
|
||||
selmodel.blockSignals(True)
|
||||
selmodel.select(itemSelection, QtCore.QItemSelectionModel.Rows | QtCore.QItemSelectionModel.Select)
|
||||
selmodel.blockSignals(False)
|
||||
|
||||
|
||||
def reSelect(self):
|
||||
selmodel = self.form.lstPathObjects.selectionModel()
|
||||
item = selmodel.selection().indexes()[0]
|
||||
|
||||
index0 = self.opModel.index(0, 0)
|
||||
itemSelection = QtCore.QItemSelection(index0, item)
|
||||
|
||||
selmodel.blockSignals(True)
|
||||
selmodel.select(itemSelection,
|
||||
QtCore.QItemSelectionModel.Rows | QtCore.QItemSelectionModel.Select)
|
||||
selmodel.blockSignals(False)
|
||||
|
||||
selectedObjs = [self.opModel.itemFromIndex(i).data() for i in selmodel.selection().indexes()]
|
||||
|
||||
self.simulation.setSimulationPath(selectedObjs)
|
||||
|
||||
def runButton(self):
|
||||
if self.form.btnRun.text() == 'Run':
|
||||
self.simulation.run()
|
||||
else:
|
||||
self.simulation.stopSim()
|
||||
|
||||
|
||||
def updateEstimate(self, timestring):
|
||||
self.form.txtRunEstimate.setText(timestring)
|
||||
|
||||
def launchCamotics(self):
|
||||
projfile = self.simulation.buildproject()
|
||||
subprocess.Popen(["camotics", projfile])
|
||||
|
||||
def accept(self):
|
||||
self.simulation.accept()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
def reject(self):
|
||||
self.simulation.cancel()
|
||||
FreeCADGui.Control.closeDialog()
|
||||
|
||||
def calculating(self, progress=0.0):
|
||||
if progress < 1.0:
|
||||
self.form.btnRun.setText('Stop')
|
||||
else:
|
||||
self.form.btnRun.setText('Run')
|
||||
self.form.progressBar.setValue(int(progress*100))
|
||||
|
||||
|
||||
class CamoticsSimulation(QtCore.QObject):
|
||||
|
||||
SIM = camotics.Simulation()
|
||||
q = queue.Queue()
|
||||
progressUpdate = QtCore.Signal(object)
|
||||
estimateChanged = QtCore.Signal(str)
|
||||
|
||||
SHAPEMAP = {'ballend': 'Ballnose',
|
||||
'endmill': 'Cylindrical',
|
||||
'v-bit' : 'Conical',
|
||||
'chamfer': 'Snubnose'}
|
||||
|
||||
cutMaterial = None
|
||||
|
||||
def worker(self, lock):
|
||||
while True:
|
||||
item = self.q.get()
|
||||
with lock:
|
||||
if item['TYPE'] == 'STATUS':
|
||||
if item['VALUE'] == 'DONE':
|
||||
self.SIM.wait()
|
||||
surface = self.SIM.get_surface('binary')
|
||||
#surface = self.SIM.get_surface('python')
|
||||
self.SIM.wait()
|
||||
self.addMesh(surface)
|
||||
#self.makeCoinMesh(surface)
|
||||
elif item['TYPE'] == 'PROGRESS':
|
||||
#self.taskForm.calculating(item['VALUE'])
|
||||
msg = item['VALUE']
|
||||
self.progressUpdate.emit(msg)
|
||||
self.q.task_done()
|
||||
|
||||
|
||||
|
||||
def __init__(self):
|
||||
super().__init__() # needed for QT signals
|
||||
lock = Lock()
|
||||
Thread(target=self.worker, daemon=True, args=(lock,)).start()
|
||||
|
||||
def callback(self, status, progress):
|
||||
self.q.put({'TYPE': 'PROGRESS', 'VALUE': progress})
|
||||
self.q.put({'TYPE': 'STATUS' , 'VALUE': status })
|
||||
|
||||
def isDone(self, success):
|
||||
self.q.put({'TYPE': 'STATUS' , 'VALUE': 'DONE'})
|
||||
|
||||
def stopSim(self):
|
||||
if self.SIM.is_running():
|
||||
self.SIM.interrupt()
|
||||
self.SIM.wait()
|
||||
return True
|
||||
|
||||
|
||||
def addMesh(self, surface):
|
||||
'''takes a binary stl and adds a Mesh to the current docuemnt'''
|
||||
|
||||
if self.cutMaterial is None:
|
||||
self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::Feature", "SimulationOutput")
|
||||
buffer=io.BytesIO()
|
||||
buffer.write(surface)
|
||||
buffer.seek(0)
|
||||
mesh=Mesh.Mesh()
|
||||
mesh.read(buffer, "STL")
|
||||
self.cutMaterial.Mesh = mesh
|
||||
# Mesh.show(mesh)
|
||||
|
||||
def setSimulationPath(self, postlist):
|
||||
gcode, fname = PathPost.CommandPathPost().getGcodeSilently(postlist, self.job)
|
||||
self.SIM.compute_path(gcode)
|
||||
self.SIM.wait()
|
||||
|
||||
tot = sum([step['time'] for step in self.SIM.get_path()])
|
||||
self.estimateChanged.emit(time.strftime("%H:%M:%S", time.gmtime(tot)))
|
||||
|
||||
def Activate(self):
|
||||
self.job = FreeCADGui.Selection.getSelectionEx()[0].Object
|
||||
postlist = PathPost.buildPostList(self.job)
|
||||
self.finalpostlist = [item for slist in postlist for item in slist]
|
||||
|
||||
self.taskForm = CAMoticsUI(self)
|
||||
FreeCADGui.Control.showDialog(self.taskForm)
|
||||
|
||||
self.SIM.set_metric()
|
||||
self.SIM.set_resolution('high')
|
||||
|
||||
bb = self.job.Stock.Shape.BoundBox
|
||||
self.SIM.set_workpiece(min = (bb.XMin, bb.YMin, bb.ZMin), max = (bb.XMax, bb.YMax, bb.ZMax))
|
||||
|
||||
for t in self.job.Tools.Group:
|
||||
self.SIM.set_tool(t.ToolNumber,
|
||||
metric = True,
|
||||
shape = self.SHAPEMAP.get(t.Tool.ShapeName, 'Cylindrical'),
|
||||
length = t.Tool.Length.Value,
|
||||
diameter = t.Tool.Diameter.Value)
|
||||
|
||||
|
||||
self.setSimulationPath(self.finalpostlist)
|
||||
|
||||
def run(self):
|
||||
self.SIM.start(self.callback, done=self.isDone)
|
||||
|
||||
#def makeCoinMesh(self, surface):
|
||||
# # this doesn't work yet
|
||||
# #sg = FreeCADGui.ActiveDocument.ActiveView.getSceneGraph();
|
||||
# color = coin.SoBaseColor()
|
||||
# color.rgb = (1, 0, 1)
|
||||
# coords = coin.SoTransform()
|
||||
# node = coin.SoSeparator()
|
||||
# node.addChild(color)
|
||||
# node.addChild(coords)
|
||||
|
||||
# end = [-1]
|
||||
# vertices = list(zip(*[iter(surface['vertices'])] * 3))
|
||||
# #polygons = list(zip(*[iter(vertices)] * 3, cycle(end)))
|
||||
# polygons = list(zip(*[iter(range(len(vertices)))] * 3, cycle(end)))
|
||||
|
||||
# print('verts {}'.format(len(vertices)))
|
||||
# print('polygons {}'.format(len(polygons)))
|
||||
|
||||
# data=coin.SoCoordinate3()
|
||||
# face=coin.SoIndexedFaceSet()
|
||||
# node.addChild(data)
|
||||
# node.addChild(face)
|
||||
|
||||
# # i = 0
|
||||
# # for i, v in enumerate(vertices):
|
||||
# # print('i: {} v: {}'.format(i, v))
|
||||
# #data.point.set1Value(i, v[0], v[1], v[2])
|
||||
# # i += 1
|
||||
# # i = 0
|
||||
# # for p in polygons:
|
||||
# # try:
|
||||
# # face.coordIndex.set1Value(i, p)
|
||||
# # i += 1
|
||||
# # except Exception as e:
|
||||
# # print(e)
|
||||
# # print(i)
|
||||
# # print(p)
|
||||
|
||||
# # sg.addChild(node)
|
||||
|
||||
|
||||
def RemoveMaterial(self):
|
||||
if self.cutMaterial is not None:
|
||||
FreeCAD.ActiveDocument.removeObject(self.cutMaterial.Name)
|
||||
self.cutMaterial = None
|
||||
|
||||
def accept(self):
|
||||
pass
|
||||
|
||||
def cancel(self):
|
||||
self.RemoveMaterial()
|
||||
|
||||
def buildproject(self):
|
||||
|
||||
job = self.job
|
||||
gcode, fname = PathPost.CommandPathPost().getGcodeSilently(self.finalpostlist, self.job, temp=False)
|
||||
|
||||
tooltemplate = {
|
||||
"units": "metric",
|
||||
"shape": "cylindrical",
|
||||
"length": 10,
|
||||
"diameter": 3.125,
|
||||
"description": ""
|
||||
}
|
||||
|
||||
workpiecetemplate = {
|
||||
"automatic": False,
|
||||
"margin": 0,
|
||||
"bounds": {
|
||||
"min": [0, 0, 0],
|
||||
"max": [0, 0, 0]}
|
||||
}
|
||||
|
||||
camoticstemplate = {
|
||||
"units": "metric",
|
||||
"resolution-mode": "high",
|
||||
"resolution": 1,
|
||||
"tools": {},
|
||||
"workpiece": {},
|
||||
"files": []
|
||||
}
|
||||
|
||||
unitstring = "imperial" if FreeCAD.Units.getSchema() in [2,3,5,7] else "metric"
|
||||
|
||||
camoticstemplate["units"] = unitstring
|
||||
camoticstemplate["resolution-mode"] = "medium"
|
||||
camoticstemplate["resolution"] = 1
|
||||
|
||||
toollist = {}
|
||||
for t in job.Tools.Group:
|
||||
tooltemplate["units"] = unitstring
|
||||
if hasattr(t.Tool, 'Camotics'):
|
||||
tooltemplate["shape"] = t.Tool.Camotics
|
||||
else:
|
||||
tooltemplate["shape"] = self.SHAPEMAP.get(t.Tool.ShapeName, 'Cylindrical')
|
||||
|
||||
tooltemplate["length"] = t.Tool.Length.Value
|
||||
tooltemplate["diameter"] = t.Tool.Diameter.Value
|
||||
tooltemplate["description"] = t.Label
|
||||
toollist[t.ToolNumber] = tooltemplate
|
||||
|
||||
camoticstemplate['tools'] = toollist
|
||||
|
||||
bb = job.Stock.Shape.BoundBox
|
||||
|
||||
workpiecetemplate['bounds']['min'] = [bb.XMin, bb.YMin, bb.ZMin]
|
||||
workpiecetemplate['bounds']['max'] = [bb.XMax, bb.YMax, bb.ZMax]
|
||||
camoticstemplate['workpiece'] = workpiecetemplate
|
||||
|
||||
camoticstemplate['files'] = [fname]
|
||||
|
||||
foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Camotics Project File")
|
||||
if foo:
|
||||
tfile = foo[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
with open(tfile, 'w') as t:
|
||||
proj=json.dumps(camoticstemplate, indent=2)
|
||||
t.write(proj)
|
||||
|
||||
return tfile #json.dumps(camoticstemplate, indent=2)
|
||||
|
||||
|
||||
class CommandCamoticsSimulate:
|
||||
def GetResources(self):
|
||||
return {'Pixmap': 'Path_Camotics',
|
||||
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Camotics", "Camotics"),
|
||||
'Accel': "P, C",
|
||||
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Camotics", "Simulate using Camotics"),
|
||||
'CmdType': "ForEdit"}
|
||||
|
||||
def IsActive(self):
|
||||
if bool(FreeCADGui.Selection.getSelection()) is False:
|
||||
return False
|
||||
try:
|
||||
job = FreeCADGui.Selection.getSelectionEx()[0].Object
|
||||
return isinstance(job.Proxy, PathScripts.PathJob.ObjectJob)
|
||||
except:
|
||||
return False
|
||||
|
||||
def Activated(self):
|
||||
pathSimulation.Activate()
|
||||
|
||||
|
||||
pathSimulation = CamoticsSimulation()
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
FreeCADGui.addCommand('Path_Camotics', CommandCamoticsSimulate())
|
||||
|
||||
|
||||
FreeCAD.Console.PrintLog("Loading PathCamoticsSimulateGui ... done\n")
|
||||
@@ -39,17 +39,23 @@ def Startup():
|
||||
PathLog.debug('Initializing PathGui')
|
||||
from PathScripts import PathAdaptiveGui
|
||||
from PathScripts import PathArray
|
||||
try:
|
||||
import camotics
|
||||
except ImportError:
|
||||
import FreeCAD
|
||||
FreeCAD.Console.PrintError("Camotics is not available.\n")
|
||||
else:
|
||||
from PathScripts import PathCamoticsGui
|
||||
from PathScripts import PathComment
|
||||
# from PathScripts import PathCustom
|
||||
from PathScripts import PathCustomGui
|
||||
from PathScripts import PathDeburrGui
|
||||
from PathScripts import PathDressupAxisMap
|
||||
from PathScripts import PathDressupDogbone
|
||||
from PathScripts import PathDressupDragknife
|
||||
from PathScripts import PathDressupRampEntry
|
||||
from PathScripts import PathDressupPathBoundaryGui
|
||||
from PathScripts import PathDressupTagGui
|
||||
from PathScripts import PathDressupLeadInOut
|
||||
from PathScripts import PathDressupPathBoundaryGui
|
||||
from PathScripts import PathDressupRampEntry
|
||||
from PathScripts import PathDressupTagGui
|
||||
from PathScripts import PathDressupZCorrect
|
||||
from PathScripts import PathDrillingGui
|
||||
from PathScripts import PathEngraveGui
|
||||
@@ -62,9 +68,6 @@ def Startup():
|
||||
from PathScripts import PathPocketShapeGui
|
||||
from PathScripts import PathPost
|
||||
from PathScripts import PathProbeGui
|
||||
# from PathScripts import PathProfileContourGui
|
||||
# from PathScripts import PathProfileEdgesGui
|
||||
# from PathScripts import PathProfileFacesGui
|
||||
from PathScripts import PathProfileGui
|
||||
from PathScripts import PathPropertyBagGui
|
||||
from PathScripts import PathSanity
|
||||
@@ -73,14 +76,12 @@ def Startup():
|
||||
from PathScripts import PathSimulatorGui
|
||||
from PathScripts import PathSlotGui
|
||||
from PathScripts import PathStop
|
||||
# from PathScripts import PathSurfaceGui # Added in initGui.py due to OCL dependency
|
||||
from PathScripts import PathThreadMillingGui
|
||||
from PathScripts import PathToolController
|
||||
from PathScripts import PathToolControllerGui
|
||||
from PathScripts import PathToolLibraryManager
|
||||
from PathScripts import PathToolLibraryEditor
|
||||
from PathScripts import PathToolLibraryManager
|
||||
from PathScripts import PathUtilsGui
|
||||
# from PathScripts import PathWaterlineGui # Added in initGui.py due to OCL dependency
|
||||
from PathScripts import PathVcarveGui
|
||||
Processed = True
|
||||
else:
|
||||
|
||||
@@ -33,6 +33,7 @@ import PathScripts.PathPreferences as PathPreferences
|
||||
import PathScripts.PathUtil as PathUtil
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from PathScripts.PathPostProcessor import PostProcessor
|
||||
from PySide import QtCore, QtGui
|
||||
@@ -56,6 +57,187 @@ class _TempObject:
|
||||
Label = "Fixture"
|
||||
|
||||
|
||||
def resolveFileName(job):
|
||||
|
||||
path = PathPreferences.defaultOutputFile()
|
||||
if job.PostProcessorOutputFile:
|
||||
path = job.PostProcessorOutputFile
|
||||
filename = path
|
||||
|
||||
if '%D' in filename:
|
||||
D = FreeCAD.ActiveDocument.FileName
|
||||
if D:
|
||||
D = os.path.dirname(D)
|
||||
# in case the document is in the current working directory
|
||||
if not D:
|
||||
D = '.'
|
||||
else:
|
||||
FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
|
||||
return None
|
||||
filename = filename.replace('%D', D)
|
||||
|
||||
if '%d' in filename:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
filename = filename.replace('%d', d)
|
||||
|
||||
if '%j' in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace('%j', j)
|
||||
|
||||
if '%M' in filename:
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
|
||||
M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir())
|
||||
filename = filename.replace('%M', M)
|
||||
|
||||
|
||||
policy = PathPreferences.defaultOutputPolicy()
|
||||
|
||||
openDialog = policy == 'Open File Dialog'
|
||||
if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
|
||||
# Either the entire filename resolves into a directory or the parent directory doesn't exist.
|
||||
# Either way I don't know what to do - ask for help
|
||||
openDialog = True
|
||||
|
||||
if os.path.isfile(filename) and not openDialog:
|
||||
if policy == 'Open File Dialog on conflict':
|
||||
openDialog = True
|
||||
elif policy == 'Append Unique ID on conflict':
|
||||
fn, ext = os.path.splitext(filename)
|
||||
nr = fn[-3:]
|
||||
n = 1
|
||||
if nr.isdigit():
|
||||
n = int(nr)
|
||||
while os.path.isfile("%s%03d%s" % (fn, n, ext)):
|
||||
n = n + 1
|
||||
filename = "%s%03d%s" % (fn, n, ext)
|
||||
|
||||
if openDialog:
|
||||
foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename)
|
||||
if foo[0]:
|
||||
filename = foo[0]
|
||||
else:
|
||||
filename = None
|
||||
|
||||
return filename
|
||||
|
||||
def buildPostList(job):
|
||||
''' Takes the job and determines the specific objects and order to
|
||||
postprocess Returns a list of objects which can be passed to
|
||||
exportObjectsWith() for final posting'''
|
||||
wcslist = job.Fixtures
|
||||
orderby = job.OrderOutputBy
|
||||
|
||||
postlist = []
|
||||
|
||||
if orderby == 'Fixture':
|
||||
PathLog.debug("Ordering by Fixture")
|
||||
# Order by fixture means all operations and tool changes will be completed in one
|
||||
# fixture before moving to the next.
|
||||
|
||||
currTool = None
|
||||
for index, f in enumerate(wcslist):
|
||||
# create an object to serve as the fixture path
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
fobj.Path = Path.Path([c1])
|
||||
if index != 0:
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
sublist = [fobj]
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in job.Operations.Group:
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None and PathUtil.opProperty(obj, 'Active'):
|
||||
if tc.ToolNumber != currTool:
|
||||
sublist.append(tc)
|
||||
PathLog.debug("Appending TC: {}".format(tc.Name))
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append(sublist)
|
||||
|
||||
elif orderby == 'Tool':
|
||||
PathLog.debug("Ordering by Tool")
|
||||
# Order by tool means tool changes are minimized.
|
||||
# all operations with the current tool are processed in the current
|
||||
# fixture before moving to the next fixture.
|
||||
|
||||
currTool = None
|
||||
fixturelist = []
|
||||
for f in wcslist:
|
||||
# create an object to serve as the fixture path
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path = Path.Path([c1, c2])
|
||||
fobj.InList.append(job)
|
||||
fixturelist.append(fobj)
|
||||
|
||||
# Now generate the gcode
|
||||
curlist = [] # list of ops for tool, will repeat for each fixture
|
||||
sublist = [] # list of ops for output splitting
|
||||
|
||||
for idx, obj in enumerate(job.Operations.Group):
|
||||
|
||||
# check if the operation is active
|
||||
active = PathUtil.opProperty(obj, 'Active')
|
||||
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is None or tc.ToolNumber == currTool and active:
|
||||
curlist.append(obj)
|
||||
elif tc.ToolNumber != currTool and currTool is None and active: # first TC
|
||||
sublist.append(tc)
|
||||
curlist.append(obj)
|
||||
currTool = tc.ToolNumber
|
||||
elif tc.ToolNumber != currTool and currTool is not None and active: # TC
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append(sublist)
|
||||
sublist = [tc]
|
||||
curlist = [obj]
|
||||
currTool = tc.ToolNumber
|
||||
|
||||
if idx == len(job.Operations.Group) - 1: # Last operation.
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append(sublist)
|
||||
|
||||
elif orderby == 'Operation':
|
||||
PathLog.debug("Ordering by Operation")
|
||||
# Order by operation means ops are done in each fixture in
|
||||
# sequence.
|
||||
currTool = None
|
||||
firstFixture = True
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in job.Operations.Group:
|
||||
if PathUtil.opProperty(obj, 'Active'):
|
||||
sublist = []
|
||||
PathLog.debug("obj: {}".format(obj.Name))
|
||||
for f in wcslist:
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
fobj.Path = Path.Path([c1])
|
||||
if not firstFixture:
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
sublist.append(fobj)
|
||||
firstFixture = False
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None:
|
||||
if job.SplitOutput or (tc.ToolNumber != currTool):
|
||||
sublist.append(tc)
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append(sublist)
|
||||
|
||||
return postlist
|
||||
|
||||
|
||||
class DlgSelectPostProcessor:
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@@ -97,73 +279,6 @@ class CommandPathPost:
|
||||
# pylint: disable=no-init
|
||||
subpart = 1
|
||||
|
||||
def resolveFileName(self, job):
|
||||
path = PathPreferences.defaultOutputFile()
|
||||
if job.PostProcessorOutputFile:
|
||||
path = job.PostProcessorOutputFile
|
||||
filename = path
|
||||
|
||||
if '%D' in filename:
|
||||
D = FreeCAD.ActiveDocument.FileName
|
||||
if D:
|
||||
D = os.path.dirname(D)
|
||||
# in case the document is in the current working directory
|
||||
if not D:
|
||||
D = '.'
|
||||
else:
|
||||
FreeCAD.Console.PrintError("Please save document in order to resolve output path!\n")
|
||||
return None
|
||||
filename = filename.replace('%D', D)
|
||||
|
||||
if '%d' in filename:
|
||||
d = FreeCAD.ActiveDocument.Label
|
||||
filename = filename.replace('%d', d)
|
||||
|
||||
if '%j' in filename:
|
||||
j = job.Label
|
||||
filename = filename.replace('%j', j)
|
||||
|
||||
if '%M' in filename:
|
||||
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Macro")
|
||||
M = pref.GetString("MacroPath", FreeCAD.getUserAppDataDir())
|
||||
filename = filename.replace('%M', M)
|
||||
|
||||
if '%s' in filename:
|
||||
if job.SplitOutput:
|
||||
filename = filename.replace('%s', '_'+str(self.subpart))
|
||||
self.subpart += 1
|
||||
else:
|
||||
filename = filename.replace('%s', '')
|
||||
|
||||
policy = PathPreferences.defaultOutputPolicy()
|
||||
|
||||
openDialog = policy == 'Open File Dialog'
|
||||
if os.path.isdir(filename) or not os.path.isdir(os.path.dirname(filename)):
|
||||
# Either the entire filename resolves into a directory or the parent directory doesn't exist.
|
||||
# Either way I don't know what to do - ask for help
|
||||
openDialog = True
|
||||
|
||||
if os.path.isfile(filename) and not openDialog:
|
||||
if policy == 'Open File Dialog on conflict':
|
||||
openDialog = True
|
||||
elif policy == 'Append Unique ID on conflict':
|
||||
fn, ext = os.path.splitext(filename)
|
||||
nr = fn[-3:]
|
||||
n = 1
|
||||
if nr.isdigit():
|
||||
n = int(nr)
|
||||
while os.path.isfile("%s%03d%s" % (fn, n, ext)):
|
||||
n = n + 1
|
||||
filename = "%s%03d%s" % (fn, n, ext)
|
||||
|
||||
if openDialog:
|
||||
foo = QtGui.QFileDialog.getSaveFileName(QtGui.QApplication.activeWindow(), "Output File", filename)
|
||||
if foo:
|
||||
filename = foo[0]
|
||||
else:
|
||||
filename = None
|
||||
|
||||
return filename
|
||||
|
||||
def resolvePostProcessor(self, job):
|
||||
if hasattr(job, "PostProcessor"):
|
||||
@@ -190,6 +305,37 @@ class CommandPathPost:
|
||||
|
||||
return False
|
||||
|
||||
def getGcodeSilently(self, objs, job, temp=True):
|
||||
'''This method will postprocess the given objects without opening dialogs
|
||||
or prompting the user for any input. gcode is returned as a list of lists
|
||||
suitable for splitting'''
|
||||
PathLog.track('objs: {}'.format(objs))
|
||||
|
||||
postArgs = PathPreferences.defaultPostProcessorArgs()
|
||||
if hasattr(job, "PostProcessorArgs") and job.PostProcessorArgs:
|
||||
postArgs = job.PostProcessorArgs
|
||||
elif hasattr(job, "PostProcessor") and job.PostProcessor:
|
||||
postArgs = ''
|
||||
|
||||
if not '--no-show-editor' in postArgs:
|
||||
postArgs += ' --no-show-editor'
|
||||
|
||||
PathLog.track('postArgs: {}'.format(postArgs))
|
||||
|
||||
if temp:
|
||||
tfile = tempfile.NamedTemporaryFile()
|
||||
filename = tfile.name
|
||||
else:
|
||||
filename = resolveFileName(job)
|
||||
tfile = open(filename, "w")
|
||||
|
||||
with tfile:
|
||||
postname = self.resolvePostProcessor(job)
|
||||
processor = PostProcessor.load(postname)
|
||||
gcode = processor.export(objs, filename, postArgs)
|
||||
return gcode, filename
|
||||
|
||||
|
||||
def exportObjectsWith(self, objs, job, needFilename=True):
|
||||
PathLog.track()
|
||||
# check if the user has a project and has set the default post and
|
||||
@@ -203,7 +349,7 @@ class CommandPathPost:
|
||||
postname = self.resolvePostProcessor(job)
|
||||
filename = '-'
|
||||
if postname and needFilename:
|
||||
filename = self.resolveFileName(job)
|
||||
filename = resolveFileName(job)
|
||||
|
||||
if postname and filename:
|
||||
print("post: %s(%s, %s)" % (postname, filename, postArgs))
|
||||
@@ -261,123 +407,17 @@ class CommandPathPost:
|
||||
|
||||
PathLog.debug("about to postprocess job: {}".format(job.Name))
|
||||
|
||||
wcslist = job.Fixtures
|
||||
orderby = job.OrderOutputBy
|
||||
split = job.SplitOutput
|
||||
postlist = buildPostList(job)
|
||||
filename = resolveFileName(job)
|
||||
|
||||
postlist = []
|
||||
|
||||
if orderby == 'Fixture':
|
||||
PathLog.debug("Ordering by Fixture")
|
||||
# Order by fixture means all operations and tool changes will be completed in one
|
||||
# fixture before moving to the next.
|
||||
|
||||
currTool = None
|
||||
for index, f in enumerate(wcslist):
|
||||
# create an object to serve as the fixture path
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
fobj.Path = Path.Path([c1])
|
||||
if index != 0:
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
sublist = [fobj]
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in job.Operations.Group:
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None and PathUtil.opProperty(obj, 'Active'):
|
||||
if tc.ToolNumber != currTool:
|
||||
sublist.append(tc)
|
||||
PathLog.debug("Appending TC: {}".format(tc.Name))
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append(sublist)
|
||||
|
||||
elif orderby == 'Tool':
|
||||
PathLog.debug("Ordering by Tool")
|
||||
# Order by tool means tool changes are minimized.
|
||||
# all operations with the current tool are processed in the current
|
||||
# fixture before moving to the next fixture.
|
||||
|
||||
currTool = None
|
||||
fixturelist = []
|
||||
for f in wcslist:
|
||||
# create an object to serve as the fixture path
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path = Path.Path([c1, c2])
|
||||
fobj.InList.append(job)
|
||||
fixturelist.append(fobj)
|
||||
|
||||
# Now generate the gcode
|
||||
curlist = [] # list of ops for tool, will repeat for each fixture
|
||||
sublist = [] # list of ops for output splitting
|
||||
|
||||
for idx, obj in enumerate(job.Operations.Group):
|
||||
|
||||
# check if the operation is active
|
||||
active = PathUtil.opProperty(obj, 'Active')
|
||||
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is None or tc.ToolNumber == currTool and active:
|
||||
curlist.append(obj)
|
||||
elif tc.ToolNumber != currTool and currTool is None and active: # first TC
|
||||
sublist.append(tc)
|
||||
curlist.append(obj)
|
||||
currTool = tc.ToolNumber
|
||||
elif tc.ToolNumber != currTool and currTool is not None and active: # TC
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append(sublist)
|
||||
sublist = [tc]
|
||||
curlist = [obj]
|
||||
currTool = tc.ToolNumber
|
||||
|
||||
if idx == len(job.Operations.Group) - 1: # Last operation.
|
||||
for fixture in fixturelist:
|
||||
sublist.append(fixture)
|
||||
sublist.extend(curlist)
|
||||
postlist.append(sublist)
|
||||
|
||||
elif orderby == 'Operation':
|
||||
PathLog.debug("Ordering by Operation")
|
||||
# Order by operation means ops are done in each fixture in
|
||||
# sequence.
|
||||
currTool = None
|
||||
firstFixture = True
|
||||
|
||||
# Now generate the gcode
|
||||
for obj in job.Operations.Group:
|
||||
if PathUtil.opProperty(obj, 'Active'):
|
||||
sublist = []
|
||||
PathLog.debug("obj: {}".format(obj.Name))
|
||||
for f in wcslist:
|
||||
fobj = _TempObject()
|
||||
c1 = Path.Command(f)
|
||||
fobj.Path = Path.Path([c1])
|
||||
if not firstFixture:
|
||||
c2 = Path.Command("G0 Z" + str(job.Stock.Shape.BoundBox.ZMax + job.SetupSheet.ClearanceHeightOffset.Value))
|
||||
fobj.Path.addCommands(c2)
|
||||
fobj.InList.append(job)
|
||||
sublist.append(fobj)
|
||||
firstFixture = False
|
||||
tc = PathUtil.toolControllerForOp(obj)
|
||||
if tc is not None:
|
||||
if job.SplitOutput or (tc.ToolNumber != currTool):
|
||||
sublist.append(tc)
|
||||
currTool = tc.ToolNumber
|
||||
sublist.append(obj)
|
||||
postlist.append(sublist)
|
||||
|
||||
fail = True
|
||||
rc = ''
|
||||
if split:
|
||||
for slist in postlist:
|
||||
(fail, rc, filename) = self.exportObjectsWith(slist, job)
|
||||
if fail:
|
||||
break
|
||||
else:
|
||||
finalpostlist = [item for slist in postlist for item in slist]
|
||||
(fail, rc, filename) = self.exportObjectsWith(finalpostlist, job)
|
||||
|
||||
@@ -30,7 +30,6 @@ import datetime
|
||||
import shlex
|
||||
import os.path
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
|
||||
TOOLTIP = '''
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
@@ -154,7 +153,7 @@ def processArguments(argstring):
|
||||
if args.no_tlo:
|
||||
USE_TLO = False
|
||||
if args.no_axis_modal:
|
||||
OUTPUT_DOUBLES = true
|
||||
OUTPUT_DOUBLES = True
|
||||
|
||||
except Exception: # pylint: disable=broad-except
|
||||
return False
|
||||
@@ -171,7 +170,7 @@ def export(objectslist, filename, argstring):
|
||||
global UNIT_SPEED_FORMAT
|
||||
global HORIZRAPID
|
||||
global VERTRAPID
|
||||
|
||||
|
||||
for obj in objectslist:
|
||||
if not hasattr(obj, "Path"):
|
||||
print("the object " + obj.Name + " is not a path. Please select only path and Compounds.")
|
||||
@@ -206,34 +205,10 @@ def export(objectslist, filename, argstring):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# fetch machine details
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
myMachine = 'not set'
|
||||
|
||||
if hasattr(job, "MachineName"):
|
||||
myMachine = job.MachineName
|
||||
|
||||
if hasattr(job, "MachineUnits"):
|
||||
if job.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
UNIT_FORMAT = 'mm'
|
||||
UNIT_SPEED_FORMAT = 'mm/min'
|
||||
else:
|
||||
UNITS = "G20"
|
||||
UNIT_FORMAT = 'in'
|
||||
UNIT_SPEED_FORMAT = 'in/min'
|
||||
|
||||
if hasattr(job, "SetupSheet"):
|
||||
if hasattr(job.SetupSheet, "HorizRapid"):
|
||||
HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
if hasattr(job.SetupSheet, "VertRapid"):
|
||||
VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(BEGIN OPERATION: %s)\n" % obj.Label.upper()
|
||||
gcode += linenumber() + "(MACHINE: %s, %s)\n" % (myMachine.upper(), UNIT_SPEED_FORMAT.upper())
|
||||
gcode += linenumber() + "(MACHINE UNITS: %s)\n" % (UNIT_SPEED_FORMAT.upper())
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
@@ -459,7 +434,6 @@ def parse(pathobj):
|
||||
outstring.append(param + str(int(c.Parameters['D'])))
|
||||
elif param == 'S':
|
||||
outstring.append(param + str(int(c.Parameters['S'])))
|
||||
currentSpeed = int(c.Parameters['S'])
|
||||
else:
|
||||
if (not OUTPUT_DOUBLES) and (param in currLocation) and (currLocation[param] == c.Parameters[param]):
|
||||
continue
|
||||
@@ -482,7 +456,6 @@ def parse(pathobj):
|
||||
# Check for Tool Change:
|
||||
if command == 'M6':
|
||||
# stop the spindle
|
||||
currentSpeed = 0
|
||||
out += linenumber() + "M5\n"
|
||||
for line in TOOL_CHANGE.splitlines(True):
|
||||
out += linenumber() + line
|
||||
|
||||
@@ -20,14 +20,10 @@
|
||||
|
||||
# HEDENHAIN Post-Processor for FreeCAD
|
||||
|
||||
import FreeCAD
|
||||
from FreeCAD import Units
|
||||
import argparse
|
||||
import time
|
||||
import shlex
|
||||
import PathScripts
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
import math
|
||||
|
||||
#**************************************************************************#
|
||||
@@ -261,11 +257,8 @@ def export(objectslist, filename, argstring):
|
||||
global LBLIZE_STAUS
|
||||
|
||||
Object_Kind = None
|
||||
Feed_Rapid = False
|
||||
Feed = 0
|
||||
Spindle_RPM = 0
|
||||
Spindle_Active = False
|
||||
ToolId = ""
|
||||
Compensation = "0"
|
||||
params = [
|
||||
'X', 'Y', 'Z',
|
||||
@@ -360,22 +353,14 @@ def export(objectslist, filename, argstring):
|
||||
|
||||
for c in obj.Path.Commands:
|
||||
Cmd_Count += 1
|
||||
outstring = []
|
||||
command = c.Name
|
||||
if command != 'G0':
|
||||
command = command.replace('G0','G') # normalize: G01 -> G1
|
||||
Feed_Rapid = False
|
||||
else:
|
||||
Feed_Rapid = True
|
||||
|
||||
for param in params:
|
||||
if param in c.Parameters:
|
||||
if param == 'F':
|
||||
Feed = c.Parameters['F']
|
||||
elif param == 'S':
|
||||
Spindle_RPM = c.Parameters['S'] # Could be deleted if tool it's OK
|
||||
elif param == 'T':
|
||||
ToolId = c.Parameters['T'] # Could be deleted if tool it's OK
|
||||
|
||||
if command == 'G90':
|
||||
G_FUNCTION_STORE['G90'] = True
|
||||
@@ -474,21 +459,21 @@ def export(objectslist, filename, argstring):
|
||||
|
||||
def HEIDEN_Begin(ActualJob): #use Label for program name
|
||||
global UNITS
|
||||
JobParent = PathUtils.findParentJob(ActualJob[0])
|
||||
if hasattr(JobParent, "Label"):
|
||||
program_id = JobParent.Label
|
||||
else:
|
||||
program_id = "NEW"
|
||||
return "BEGIN PGM " + str(program_id) + " " + UNITS
|
||||
# JobParent = PathUtils.findParentJob(ActualJob[0])
|
||||
# if hasattr(JobParent, "Label"):
|
||||
# program_id = JobParent.Label
|
||||
# else:
|
||||
# program_id = "NEW"
|
||||
return "BEGIN PGM {}".format(UNITS)
|
||||
|
||||
def HEIDEN_End(ActualJob): #use Label for program name
|
||||
global UNITS
|
||||
JobParent = PathUtils.findParentJob(ActualJob[0])
|
||||
if hasattr(JobParent, "Label"):
|
||||
program_id = JobParent.Label
|
||||
else:
|
||||
program_id = "NEW"
|
||||
return "END PGM " + program_id + " " + UNITS
|
||||
# JobParent = PathUtils.findParentJob(ActualJob[0])
|
||||
# if hasattr(JobParent, "Label"):
|
||||
# program_id = JobParent.Label
|
||||
# else:
|
||||
# program_id = "NEW"
|
||||
return "END PGM {}".format(UNITS)
|
||||
|
||||
#def HEIDEN_ToolDef(tool_id, tool_length, tool_radius): # old machines don't have tool table, need tooldef list
|
||||
# return "TOOL DEF " + tool_id + " R" + "{:.3f}".format(tool_length) + " L" + "{:.3f}".format(tool_radius)
|
||||
|
||||
@@ -29,7 +29,6 @@ import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
|
||||
TOOLTIP = '''
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
@@ -193,28 +192,10 @@ def export(objectslist, filename, argstring):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# fetch machine details
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
myMachine = 'not set'
|
||||
|
||||
if hasattr(job, "MachineName"):
|
||||
myMachine = job.MachineName
|
||||
|
||||
if hasattr(job, "MachineUnits"):
|
||||
if job.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
UNIT_FORMAT = 'mm'
|
||||
UNIT_SPEED_FORMAT = 'mm/min'
|
||||
else:
|
||||
UNITS = "G20"
|
||||
UNIT_FORMAT = 'in'
|
||||
UNIT_SPEED_FORMAT = 'in/min'
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
|
||||
gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT)
|
||||
gcode += linenumber() + "(machine units: %s)\n" % (UNIT_SPEED_FORMAT)
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ import argparse
|
||||
import datetime
|
||||
import shlex
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
|
||||
TOOLTIP = '''
|
||||
This is a postprocessor file for the Path workbench. It is used to
|
||||
@@ -161,8 +160,6 @@ def export(objectslist, filename, argstring):
|
||||
global UNITS
|
||||
global UNIT_FORMAT
|
||||
global UNIT_SPEED_FORMAT
|
||||
global HORIZRAPID
|
||||
global VERTRAPID
|
||||
|
||||
for obj in objectslist:
|
||||
if not hasattr(obj, "Path"):
|
||||
@@ -195,34 +192,10 @@ def export(objectslist, filename, argstring):
|
||||
if not obj.Base.Active:
|
||||
continue
|
||||
|
||||
# fetch machine details
|
||||
job = PathUtils.findParentJob(obj)
|
||||
|
||||
myMachine = 'not set'
|
||||
|
||||
if hasattr(job, "MachineName"):
|
||||
myMachine = job.MachineName
|
||||
|
||||
if hasattr(job, "MachineUnits"):
|
||||
if job.MachineUnits == "Metric":
|
||||
UNITS = "G21"
|
||||
UNIT_FORMAT = 'mm'
|
||||
UNIT_SPEED_FORMAT = 'mm/min'
|
||||
else:
|
||||
UNITS = "G20"
|
||||
UNIT_FORMAT = 'in'
|
||||
UNIT_SPEED_FORMAT = 'in/min'
|
||||
|
||||
if hasattr(job, "SetupSheet"):
|
||||
if hasattr(job.SetupSheet, "HorizRapid"):
|
||||
HORIZRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
if hasattr(job.SetupSheet, "VertRapid"):
|
||||
VERTRAPID = Units.Quantity(job.SetupSheet.HorizRapid, FreeCAD.Units.Velocity)
|
||||
|
||||
# do the pre_op
|
||||
if OUTPUT_COMMENTS:
|
||||
gcode += linenumber() + "(begin operation: %s)\n" % obj.Label
|
||||
gcode += linenumber() + "(machine: %s, %s)\n" % (myMachine, UNIT_SPEED_FORMAT)
|
||||
gcode += linenumber() + "(machine: %s, %s)\n" % (MACHINE_NAME, UNIT_SPEED_FORMAT)
|
||||
for line in PRE_OPERATION.splitlines(True):
|
||||
gcode += linenumber() + line
|
||||
|
||||
|
||||
@@ -20,15 +20,14 @@
|
||||
#* *
|
||||
#***************************************************************************
|
||||
|
||||
# reload in python console:
|
||||
# import generic_post
|
||||
# reload(generic_post)
|
||||
# 03-24-2021 Sliptonic: I've removed teh PathUtils import and job lookup
|
||||
# post processors shouldn't be reaching back to the job. This can cause a
|
||||
# proxy error.
|
||||
|
||||
import FreeCAD
|
||||
import argparse
|
||||
import time
|
||||
from PathScripts import PostUtils
|
||||
from PathScripts import PathUtils
|
||||
import math
|
||||
|
||||
TOOLTIP = '''Post processor for Maho M 600E mill
|
||||
@@ -232,16 +231,17 @@ def processArguments(argstring):
|
||||
SHOW_EDITOR = False
|
||||
|
||||
def mkHeader(selection):
|
||||
job = PathUtils.findParentJob(selection[0])
|
||||
# job = PathUtils.findParentJob(selection[0])
|
||||
# this is within a function, because otherwise filename and time don't change when changing the FreeCAD project
|
||||
# now = datetime.datetime.now()
|
||||
now = time.strftime("%Y-%m-%d %H:%M")
|
||||
originfile = FreeCAD.ActiveDocument.FileName
|
||||
headerNoNumber = "%PM\n" # this line gets no linenumber
|
||||
if hasattr(job, "Description"):
|
||||
description = job.Description
|
||||
else:
|
||||
description = ""
|
||||
# if hasattr(job, "Description"):
|
||||
# description = job.Description
|
||||
# else:
|
||||
# description = ""
|
||||
description = ""
|
||||
# this line gets no linenumber, it is already a specially numbered
|
||||
headerNoNumber += "N9XXX (" + description + ", " + now + ")\n"
|
||||
header = ""
|
||||
|
||||
Reference in New Issue
Block a user