The Path Simulator was designed to handle canned cycle drilling operations, G81, G82, and G83, but it expects the industry standard continuous uninterrupted set of G8x commands until the operation is completed. A recent change to the Path Drilling operation adds commands that cancel each G8x command immediately after that command is used. This confuses the Path Simulation function and leads to visual artifacts when the simulation is performed. G-code standards say that canned cycle cancellation can be accomplished by a specific G80 command or by any motion command in the set G0, G1, G2, or G3. This PR modifies PathSimulationGui.py to add recognition for canned cycle cancellation and resets the simulator to treat the next G8x command as the first element of a new series of canned cycles operations rather than a continuation of the previous series.
542 lines
19 KiB
Python
542 lines
19 KiB
Python
import FreeCAD
|
|
import Path
|
|
import PathScripts.PathDressup as PathDressup
|
|
import PathScripts.PathGeom as PathGeom
|
|
import PathScripts.PathLog as PathLog
|
|
import PathScripts.PathUtil as PathUtil
|
|
import PathSimulator
|
|
import math
|
|
import os
|
|
|
|
from FreeCAD import Vector, Base
|
|
|
|
# lazily loaded modules
|
|
from lazy_loader.lazy_loader import LazyLoader
|
|
Mesh = LazyLoader('Mesh', globals(), 'Mesh')
|
|
Part = LazyLoader('Part', globals(), 'Part')
|
|
|
|
if FreeCAD.GuiUp:
|
|
import FreeCADGui
|
|
from PySide import QtGui, QtCore
|
|
|
|
_filePath = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
class CAMSimTaskUi:
|
|
def __init__(self, parent):
|
|
# this will create a Qt widget from our ui file
|
|
self.form = FreeCADGui.PySideUic.loadUi(":/panels/TaskPathSimulator.ui")
|
|
self.parent = parent
|
|
|
|
def accept(self):
|
|
self.parent.accept()
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
def reject(self):
|
|
self.parent.cancel()
|
|
FreeCADGui.Control.closeDialog()
|
|
|
|
|
|
def TSError(msg):
|
|
QtGui.QMessageBox.information(None, "Path Simulation", msg)
|
|
|
|
|
|
class PathSimulation:
|
|
def __init__(self):
|
|
self.debug = False
|
|
self.timer = QtCore.QTimer()
|
|
QtCore.QObject.connect(self.timer, QtCore.SIGNAL("timeout()"), self.PerformCut)
|
|
self.stdrot = FreeCAD.Rotation(Vector(0, 0, 1), 0)
|
|
self.iprogress = 0
|
|
self.numCommands = 0
|
|
self.simperiod = 20
|
|
self.accuracy = 0.1
|
|
self.resetSimulation = False
|
|
|
|
def Connect(self, but, sig):
|
|
QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), sig)
|
|
|
|
def UpdateProgress(self):
|
|
if self.numCommands > 0:
|
|
self.taskForm.form.progressBar.setValue(self.iprogress * 100 / self.numCommands)
|
|
|
|
def Activate(self):
|
|
self.initdone = False
|
|
self.taskForm = CAMSimTaskUi(self)
|
|
form = self.taskForm.form
|
|
self.Connect(form.toolButtonStop, self.SimStop)
|
|
self.Connect(form.toolButtonPlay, self.SimPlay)
|
|
self.Connect(form.toolButtonPause, self.SimPause)
|
|
self.Connect(form.toolButtonStep, self.SimStep)
|
|
self.Connect(form.toolButtonFF, self.SimFF)
|
|
form.sliderSpeed.valueChanged.connect(self.onSpeedBarChange)
|
|
self.onSpeedBarChange()
|
|
form.sliderAccuracy.valueChanged.connect(self.onAccuracyBarChange)
|
|
self.onAccuracyBarChange()
|
|
form.comboJobs.currentIndexChanged.connect(self.onJobChange)
|
|
jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*")
|
|
form.comboJobs.clear()
|
|
self.jobs = []
|
|
for j in jobList:
|
|
self.jobs.append(j)
|
|
form.comboJobs.addItem(j.ViewObject.Icon, j.Label)
|
|
FreeCADGui.Control.showDialog(self.taskForm)
|
|
self.disableAnim = False
|
|
self.isVoxel = True
|
|
self.firstDrill = True
|
|
self.voxSim = PathSimulator.PathSim()
|
|
self.SimulateMill()
|
|
self.initdone = True
|
|
|
|
def SetupSimulation(self):
|
|
form = self.taskForm.form
|
|
self.activeOps = []
|
|
self.numCommands = 0
|
|
self.ioperation = 0
|
|
for i in range(form.listOperations.count()):
|
|
if form.listOperations.item(i).checkState() == QtCore.Qt.CheckState.Checked:
|
|
self.firstDrill = True
|
|
self.activeOps.append(self.operations[i])
|
|
self.numCommands += len(self.operations[i].Path.Commands)
|
|
|
|
self.stock = self.job.Stock.Shape
|
|
if (self.isVoxel):
|
|
maxlen = self.stock.BoundBox.XLength
|
|
if (maxlen < self.stock.BoundBox.YLength):
|
|
maxlen = self.stock.BoundBox.YLength
|
|
self.voxSim.BeginSimulation(self.stock, 0.01 * self.accuracy * maxlen)
|
|
(self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh()
|
|
else:
|
|
self.cutMaterial.Shape = self.stock
|
|
self.busy = False
|
|
self.tool = None
|
|
for i in range(len(self.activeOps)):
|
|
self.SetupOperation(0)
|
|
if (self.tool is not None):
|
|
break
|
|
self.iprogress = 0
|
|
self.UpdateProgress()
|
|
|
|
def SetupOperation(self, itool):
|
|
self.operation = self.activeOps[itool]
|
|
try:
|
|
self.tool = PathDressup.toolController(self.operation).Tool
|
|
except Exception:
|
|
self.tool = None
|
|
|
|
if (self.tool is not None):
|
|
if isinstance(self.tool, Path.Tool):
|
|
# handle legacy tools
|
|
toolProf = self.CreateToolProfile(self.tool, Vector(0, 1, 0), Vector(0, 0, 0), float(self.tool.Diameter) / 2.0)
|
|
self.cutTool.Shape = Part.makeSolid(toolProf.revolve(Vector(0, 0, 0), Vector(0, 0, 1)))
|
|
else:
|
|
# handle tool bits
|
|
self.cutTool.Shape = self.tool.Shape
|
|
|
|
if not self.cutTool.Shape.isValid() or self.cutTool.Shape.isNull():
|
|
self.EndSimulation()
|
|
raise RuntimeError("Path Simulation: Error in tool geometry - {}".format(self.tool.Name))
|
|
|
|
self.cutTool.ViewObject.show()
|
|
self.voxSim.SetToolShape(self.cutTool.Shape, 0.05 * self.accuracy)
|
|
self.icmd = 0
|
|
self.curpos = FreeCAD.Placement(self.initialPos, self.stdrot)
|
|
self.cutTool.Placement = self.curpos
|
|
self.opCommands = self.operation.Path.Commands
|
|
|
|
def SimulateMill(self):
|
|
self.job = self.jobs[self.taskForm.form.comboJobs.currentIndex()]
|
|
self.busy = False
|
|
self.height = 10
|
|
self.skipStep = False
|
|
self.initialPos = Vector(0, 0, self.job.Stock.Shape.BoundBox.ZMax)
|
|
# Add cut tool
|
|
self.cutTool = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutTool")
|
|
self.cutTool.ViewObject.Proxy = 0
|
|
self.cutTool.ViewObject.hide()
|
|
|
|
# Add cut material
|
|
if self.isVoxel:
|
|
self.cutMaterial = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterial")
|
|
self.cutMaterialIn = FreeCAD.ActiveDocument.addObject("Mesh::FeaturePython", "CutMaterialIn")
|
|
self.cutMaterialIn.ViewObject.Proxy = 0
|
|
self.cutMaterialIn.ViewObject.show()
|
|
self.cutMaterialIn.ViewObject.ShapeColor = (1.0, 0.85, 0.45, 0.0)
|
|
else:
|
|
self.cutMaterial = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutMaterial")
|
|
self.cutMaterial.Shape = self.job.Stock.Shape
|
|
self.cutMaterial.ViewObject.Proxy = 0
|
|
self.cutMaterial.ViewObject.show()
|
|
self.cutMaterial.ViewObject.ShapeColor = (0.5, 0.25, 0.25, 0.0)
|
|
|
|
# Add cut path solid for debug
|
|
if self.debug:
|
|
self.cutSolid = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "CutDebug")
|
|
self.cutSolid.ViewObject.Proxy = 0
|
|
self.cutSolid.ViewObject.hide()
|
|
|
|
self.SetupSimulation()
|
|
self.resetSimulation = True
|
|
FreeCAD.ActiveDocument.recompute()
|
|
|
|
def PerformCutBoolean(self):
|
|
if self.resetSimulation:
|
|
self.resetSimulation = False
|
|
self.SetupSimulation()
|
|
|
|
if self.busy:
|
|
return
|
|
self.busy = True
|
|
|
|
cmd = self.operation.Path.Commands[self.icmd]
|
|
pathSolid = None
|
|
|
|
if cmd.Name in ['G0']:
|
|
self.firstDrill = True
|
|
self.curpos = self.RapidMove(cmd, self.curpos)
|
|
if cmd.Name in ['G1', 'G2', 'G3']:
|
|
self.firstDrill = True
|
|
if self.skipStep:
|
|
self.curpos = self.RapidMove(cmd, self.curpos)
|
|
else:
|
|
(pathSolid, self.curpos) = self.GetPathSolid(self.tool, cmd, self.curpos)
|
|
|
|
if cmd.Name in ['G80']:
|
|
self.firstDrill = True
|
|
if cmd.Name in ['G81', 'G82', 'G83']:
|
|
if self.firstDrill:
|
|
extendcommand = Path.Command('G0', {"Z": cmd.r})
|
|
self.curpos = self.RapidMove(extendcommand, self.curpos)
|
|
self.firstDrill = False
|
|
extendcommand = Path.Command('G0', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})
|
|
self.curpos = self.RapidMove(extendcommand, self.curpos)
|
|
extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z})
|
|
self.curpos = self.RapidMove(extendcommand, self.curpos)
|
|
extendcommand = Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r})
|
|
self.curpos = self.RapidMove(extendcommand, self.curpos)
|
|
self.skipStep = False
|
|
if pathSolid is not None:
|
|
if self.debug:
|
|
self.cutSolid.Shape = pathSolid
|
|
newStock = self.stock.cut([pathSolid], 1e-3)
|
|
try:
|
|
if newStock.isValid():
|
|
self.stock = newStock.removeSplitter()
|
|
except Exception:
|
|
if self.debug:
|
|
print("invalid cut at cmd #{}".format(self.icmd))
|
|
if not self.disableAnim:
|
|
self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot)
|
|
self.icmd += 1
|
|
self.iprogress += 1
|
|
self.UpdateProgress()
|
|
if self.icmd >= len(self.operation.Path.Commands):
|
|
self.ioperation += 1
|
|
if self.ioperation >= len(self.activeOps):
|
|
self.EndSimulation()
|
|
return
|
|
else:
|
|
self.SetupOperation(self.ioperation)
|
|
if not self.disableAnim:
|
|
self.cutMaterial.Shape = self.stock
|
|
self.busy = False
|
|
|
|
def PerformCutVoxel(self):
|
|
if self.resetSimulation:
|
|
self.resetSimulation = False
|
|
self.SetupSimulation()
|
|
|
|
if self.busy:
|
|
return
|
|
self.busy = True
|
|
|
|
cmd = self.opCommands[self.icmd]
|
|
# for cmd in job.Path.Commands:
|
|
if cmd.Name in ['G0', 'G1', 'G2', 'G3']:
|
|
self.firstDrill = True
|
|
self.curpos = self.voxSim.ApplyCommand(self.curpos, cmd)
|
|
if not self.disableAnim:
|
|
self.cutTool.Placement = self.curpos
|
|
(self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh()
|
|
if cmd.Name in ['G80']:
|
|
self.firstDrill = True
|
|
if cmd.Name in ['G81', 'G82', 'G83']:
|
|
extendcommands = []
|
|
if self.firstDrill:
|
|
extendcommands.append(Path.Command('G0', {"Z": cmd.r}))
|
|
self.firstDrill = False
|
|
extendcommands.append(Path.Command('G0', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}))
|
|
extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.z}))
|
|
extendcommands.append(Path.Command('G1', {"X": cmd.x, "Y": cmd.y, "Z": cmd.r}))
|
|
for ecmd in extendcommands:
|
|
self.curpos = self.voxSim.ApplyCommand(self.curpos, ecmd)
|
|
if not self.disableAnim:
|
|
self.cutTool.Placement = self.curpos
|
|
(self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh()
|
|
self.icmd += 1
|
|
self.iprogress += 1
|
|
self.UpdateProgress()
|
|
if self.icmd >= len(self.opCommands):
|
|
self.ioperation += 1
|
|
if self.ioperation >= len(self.activeOps):
|
|
self.EndSimulation()
|
|
return
|
|
else:
|
|
self.SetupOperation(self.ioperation)
|
|
self.busy = False
|
|
|
|
def PerformCut(self):
|
|
if (self.isVoxel):
|
|
self.PerformCutVoxel()
|
|
else:
|
|
self.PerformCutBoolean()
|
|
|
|
def RapidMove(self, cmd, curpos):
|
|
path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug
|
|
if path is None:
|
|
return curpos
|
|
return path.valueAt(path.LastParameter)
|
|
|
|
# get a solid representation of a tool going along path
|
|
def GetPathSolid(self, tool, cmd, pos):
|
|
toolPath = PathGeom.edgeForCmd(cmd, pos)
|
|
startDir = toolPath.tangentAt(0)
|
|
startDir[2] = 0.0
|
|
endPos = toolPath.valueAt(toolPath.LastParameter)
|
|
endDir = toolPath.tangentAt(toolPath.LastParameter)
|
|
try:
|
|
startDir.normalize()
|
|
endDir.normalize()
|
|
except Exception:
|
|
return (None, endPos)
|
|
|
|
# hack to overcome occ bugs
|
|
rad = float(tool.Diameter) / 2.0 - 0.001 * pos[2]
|
|
if type(toolPath.Curve) is Part.Circle and toolPath.Curve.Radius <= rad:
|
|
rad = toolPath.Curve.Radius - 0.01 * (pos[2] + 1)
|
|
return (None, endPos)
|
|
|
|
# create the path shell
|
|
toolProf = self.CreateToolProfile(tool, startDir, pos, rad)
|
|
rotmat = Base.Matrix()
|
|
rotmat.move(pos.negative())
|
|
rotmat.rotateZ(math.pi)
|
|
rotmat.move(pos)
|
|
mirroredProf = toolProf.transformGeometry(rotmat)
|
|
fullProf = Part.Wire([toolProf, mirroredProf])
|
|
pathWire = Part.Wire(toolPath)
|
|
try:
|
|
pathShell = pathWire.makePipeShell([fullProf], False, True)
|
|
except Exception:
|
|
if self.debug:
|
|
Part.show(pathWire)
|
|
Part.show(fullProf)
|
|
return (None, endPos)
|
|
|
|
# create the start cup
|
|
startCup = toolProf.revolve(pos, Vector(0, 0, 1), -180)
|
|
|
|
# create the end cup
|
|
endProf = self.CreateToolProfile(tool, endDir, endPos, rad)
|
|
endCup = endProf.revolve(endPos, Vector(0, 0, 1), 180)
|
|
|
|
fullShell = Part.makeShell(startCup.Faces + pathShell.Faces + endCup.Faces)
|
|
return (Part.makeSolid(fullShell).removeSplitter(), endPos)
|
|
|
|
# create radial profile of the tool (90 degrees to the direction of the path)
|
|
def CreateToolProfile(self, tool, dir, pos, rad):
|
|
type = tool.ToolType
|
|
xf = dir[0] * rad
|
|
yf = dir[1] * rad
|
|
xp = pos[0]
|
|
yp = pos[1]
|
|
zp = pos[2]
|
|
h = tool.CuttingEdgeHeight
|
|
if h <= 0.0: # set default if user fails to avoid freeze
|
|
h = 1.0
|
|
PathLog.error("SET Tool Length")
|
|
# common to all tools
|
|
vTR = Vector(xp + yf, yp - xf, zp + h)
|
|
vTC = Vector(xp, yp, zp + h)
|
|
vBC = Vector(xp, yp, zp)
|
|
lT = Part.makeLine(vTR, vTC)
|
|
res = None
|
|
if type == "ChamferMill":
|
|
ang = 90 - tool.CuttingEdgeAngle / 2.0
|
|
if ang > 80:
|
|
ang = 80
|
|
if ang < 0:
|
|
ang = 0
|
|
h1 = math.tan(ang * math.pi / 180) * rad
|
|
if h1 > (h - 0.1):
|
|
h1 = h - 0.1
|
|
vBR = Vector(xp + yf, yp - xf, zp + h1)
|
|
lR = Part.makeLine(vBR, vTR)
|
|
lB = Part.makeLine(vBC, vBR)
|
|
res = Part.Wire([lB, lR, lT])
|
|
|
|
elif type == "BallEndMill":
|
|
h1 = rad
|
|
if h1 >= h:
|
|
h1 = h - 0.1
|
|
vBR = Vector(xp + yf, yp - xf, zp + h1)
|
|
r2 = h1 / 2.0
|
|
h2 = rad - math.sqrt(rad * rad - r2 * r2)
|
|
vBCR = Vector(xp + yf / 2.0, yp - xf / 2.0, zp + h2)
|
|
cB = Part.Edge(Part.Arc(vBC, vBCR, vBR))
|
|
lR = Part.makeLine(vBR, vTR)
|
|
res = Part.Wire([cB, lR, lT])
|
|
|
|
else: # default: assume type == "EndMill"
|
|
vBR = Vector(xp + yf, yp - xf, zp)
|
|
lR = Part.makeLine(vBR, vTR)
|
|
lB = Part.makeLine(vBC, vBR)
|
|
res = Part.Wire([lB, lR, lT])
|
|
|
|
return res
|
|
|
|
def onJobChange(self):
|
|
form = self.taskForm.form
|
|
j = self.jobs[form.comboJobs.currentIndex()]
|
|
self.job = j
|
|
form.listOperations.clear()
|
|
self.operations = []
|
|
for op in j.Operations.OutList:
|
|
if PathUtil.opProperty(op, 'Active'):
|
|
listItem = QtGui.QListWidgetItem(op.ViewObject.Icon, op.Label)
|
|
listItem.setFlags(listItem.flags() | QtCore.Qt.ItemIsUserCheckable)
|
|
listItem.setCheckState(QtCore.Qt.CheckState.Checked)
|
|
self.operations.append(op)
|
|
form.listOperations.addItem(listItem)
|
|
if self.initdone:
|
|
self.SetupSimulation()
|
|
|
|
def onSpeedBarChange(self):
|
|
form = self.taskForm.form
|
|
self.simperiod = 1000 / form.sliderSpeed.value()
|
|
form.labelGPerSec.setText(str(form.sliderSpeed.value()) + " G/s")
|
|
self.timer.setInterval(self.simperiod)
|
|
|
|
def onAccuracyBarChange(self):
|
|
form = self.taskForm.form
|
|
self.accuracy = 1.1 - 0.1 * form.sliderAccuracy.value()
|
|
form.labelAccuracy.setText(str(round(self.accuracy, 1)) + "%")
|
|
|
|
def GuiBusy(self, isBusy):
|
|
form = self.taskForm.form
|
|
form.toolButtonPlay.setEnabled(not isBusy)
|
|
form.toolButtonPause.setEnabled(isBusy)
|
|
form.toolButtonStep.setEnabled(not isBusy)
|
|
form.toolButtonFF.setEnabled(not isBusy)
|
|
|
|
def EndSimulation(self):
|
|
self.UpdateProgress()
|
|
self.timer.stop()
|
|
self.GuiBusy(False)
|
|
self.ViewShape()
|
|
self.resetSimulation = True
|
|
|
|
def SimStop(self):
|
|
self.cutTool.ViewObject.hide()
|
|
self.iprogress = 0
|
|
self.EndSimulation()
|
|
|
|
def InvalidOperation(self):
|
|
if len(self.activeOps) == 0:
|
|
return True
|
|
if (self.tool is None):
|
|
TSError("No tool assigned for the operation")
|
|
return True
|
|
return False
|
|
|
|
def SimFF(self):
|
|
if self.InvalidOperation():
|
|
return
|
|
self.GuiBusy(True)
|
|
self.timer.start(1)
|
|
self.disableAnim = True
|
|
|
|
def SimStep(self):
|
|
if self.InvalidOperation():
|
|
return
|
|
self.disableAnim = False
|
|
self.PerformCut()
|
|
|
|
def SimPlay(self):
|
|
if self.InvalidOperation():
|
|
return
|
|
self.disableAnim = False
|
|
self.GuiBusy(True)
|
|
self.timer.start(self.simperiod)
|
|
|
|
def ViewShape(self):
|
|
if self.isVoxel:
|
|
(self.cutMaterial.Mesh, self.cutMaterialIn.Mesh) = self.voxSim.GetResultMesh()
|
|
else:
|
|
self.cutMaterial.Shape = self.stock
|
|
|
|
def SimPause(self):
|
|
if self.disableAnim:
|
|
self.ViewShape()
|
|
self.GuiBusy(False)
|
|
self.timer.stop()
|
|
|
|
def RemoveTool(self):
|
|
if self.cutTool is None:
|
|
return
|
|
FreeCAD.ActiveDocument.removeObject(self.cutTool.Name)
|
|
self.cutTool = None
|
|
|
|
def RemoveInnerMaterial(self):
|
|
if self.cutMaterialIn is not None:
|
|
if self.isVoxel and self.cutMaterial is not None:
|
|
mesh = Mesh.Mesh()
|
|
mesh.addMesh(self.cutMaterial.Mesh)
|
|
mesh.addMesh(self.cutMaterialIn.Mesh)
|
|
self.cutMaterial.Mesh = mesh
|
|
FreeCAD.ActiveDocument.removeObject(self.cutMaterialIn.Name)
|
|
self.cutMaterialIn = None
|
|
|
|
def RemoveMaterial(self):
|
|
if self.cutMaterial is not None:
|
|
FreeCAD.ActiveDocument.removeObject(self.cutMaterial.Name)
|
|
self.cutMaterial = None
|
|
self.RemoveInnerMaterial()
|
|
|
|
def accept(self):
|
|
self.EndSimulation()
|
|
self.RemoveInnerMaterial()
|
|
self.RemoveTool()
|
|
|
|
def cancel(self):
|
|
self.EndSimulation()
|
|
self.RemoveTool()
|
|
self.RemoveMaterial()
|
|
|
|
|
|
class CommandPathSimulate:
|
|
|
|
def GetResources(self):
|
|
return {'Pixmap': 'Path-Simulator',
|
|
'MenuText': QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "CAM Simulator"),
|
|
'Accel': "P, M",
|
|
'ToolTip': QtCore.QT_TRANSLATE_NOOP("Path_Simulator", "Simulate Path G-Code on stock")}
|
|
|
|
def IsActive(self):
|
|
if FreeCAD.ActiveDocument is not None:
|
|
for o in FreeCAD.ActiveDocument.Objects:
|
|
if o.Name[:3] == "Job":
|
|
return True
|
|
return False
|
|
|
|
def Activated(self):
|
|
pathSimulation.Activate()
|
|
|
|
|
|
pathSimulation = PathSimulation()
|
|
|
|
if FreeCAD.GuiUp:
|
|
# register the FreeCAD command
|
|
FreeCADGui.addCommand('Path_Simulator', CommandPathSimulate())
|
|
FreeCAD.Console.PrintLog("Loading PathSimulator Gui... done\n")
|