Files
create/src/Mod/Path/PathScripts/PathSimulatorGui.py

488 lines
17 KiB
Python

import os
_filePath = os.path.dirname(os.path.abspath(__file__))
import FreeCAD
import Path
import Part
import PathSimulator
import math
from FreeCAD import Vector, Base
from PathScripts.PathGeom import PathGeom
if FreeCAD.GuiUp:
import FreeCADGui
from PySide import QtGui, QtCore
#compiled with pyrcc4 -py3 Resources\CAM_Sim.qrc -o CAM_Sim_rc.py
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()
#for cmd in obj.Path.Commands:
# if cmd.Name[0] == 'G':
# e1 =
# print cmd.Name
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;
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.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.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.voxSim = PathSimulator.PathSim()
self.SimulateMill()
def SetupSimulation(self):
form = self.taskForm.form
self.activeOps = []
self.numCommands = 0
for i in range(form.listOperations.count()):
if form.listOperations.item(i).checkState() == QtCore.Qt.CheckState.Checked:
self.activeOps.append(self.operations[i])
self.numCommands += len(self.operations[i].Path.Commands)
if len(self.activeOps) == 0:
return 0
self.stock = self.job.Stock.Shape
if (self.isVoxel):
self.voxSim.BeginSimulation(self.stock,0.5)
self.cutMaterial.Mesh = self.voxSim.GetResultMesh()
else:
self.cutMaterial.Shape = self.stock
self.busy = False
self.SetupOperation(0)
self.iprogress = 0
self.UpdateProgress()
def SetupOperation(self, itool):
self.operation = self.activeOps[itool]
self.tool = self.operation.ToolController.Tool
toolProf = self.CreateToolProfile(self.tool, Vector(0,1,0), Vector(0,0,0), self.tool.Diameter / 2.0)
self.cutTool.Shape = Part.makeSolid(toolProf.revolve(Vector(0,0,0), Vector(0,0,1)))
self.cutTool.ViewObject.show()
self.voxSim.SetCurrentTool(self.tool)
self.icmd = 0
self.curpos = FreeCAD.Placement(self.initialPos, self.stdrot)
#self.cutTool.Placement = FreeCAD.Placement(self.curpos, self.stdrot)
self.cutTool.Placement = self.curpos
def SimulateMill(self):
self.job = self.jobs[self.taskForm.form.comboJobs.currentIndex()]
self.busy = False
#self.timer.start(100)
self.ioperation = 0
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")
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()
# 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 = False
FreeCAD.ActiveDocument.recompute()
#self.dialog.show()
def SkipStep(self):
self.skipStep = True
self.PerformCut()
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]
#for cmd in job.Path.Commands:
pathSolid = None
if cmd.Name in ['G0']:
self.curpos = self.RapidMove(cmd, self.curpos)
if cmd.Name in ['G1', 'G2', 'G3']:
if self.skipStep:
self.curpos = self.RapidMove(cmd, self.curpos)
else:
(pathSolid, self.curpos) = self.GetPathSolid(self.tool, cmd, 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:
if self.debug:
print "invalid cut at cmd #" + str(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.cutMaterial.Shape = self.stock.removeSplitter()
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.operation.Path.Commands[self.icmd]
#for cmd in job.Path.Commands:
if cmd.Name in ['G0', 'G1', 'G2', 'G3']:
self.curpos = self.voxSim.ApplyCommand(self.curpos, cmd)
if not self.disableAnim:
self.cutTool.Placement = self.curpos #FreeCAD.Placement(self.curpos, self.stdrot)
self.cutMaterial.Mesh = self.voxSim.GetResultMesh()
self.icmd += 1
self.iprogress += 1
self.UpdateProgress()
if self.icmd >= len(self.operation.Path.Commands):
#self.cutMaterial.Shape = self.stock.removeSplitter()
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)
def GetPathSolidOld(self, tool, cmd, curpos):
e1 = PathGeom.edgeForCmd(cmd, curpos)
#curpos = e1.valueAt(e1.LastParameter)
n1 = e1.tangentAt(0)
n1[2] = 0.0
try:
n1.normalize()
except:
return (None, e1.valueAt(e1.LastParameter))
height = self.height
rad = tool.Diameter / 2.0 - 0.001 * curpos[2] # hack to overcome occ bug
if type(e1.Curve) is Part.Circle and e1.Curve.Radius <= rad: # hack to overcome occ bug
rad = e1.Curve.Radius - 0.001
#return (None, e1.valueAt(e1.LastParameter))
xf = n1[0] * rad
yf = n1[1] * rad
xp = curpos[0]
yp = curpos[1]
zp = curpos[2]
v1 = Vector(yf + xp, -xf + yp, zp)
v2 = Vector(yf + xp, -xf + yp, zp + height)
v3 = Vector(-yf + xp, xf + yp, zp + height)
v4 = Vector(-yf + xp, xf + yp, zp)
vc1 = Vector(xf + xp, yf + yp, zp)
vc2 = Vector(xf + xp, yf + yp, zp + height)
l1 = Part.makeLine(v1, v2)
l2 = Part.makeLine(v2, v3)
#l2 = Part.Edge(Part.Arc(v2, vc2, v3))
l3 = Part.makeLine(v3, v4)
l4 = Part.makeLine(v4, v1)
#l4 = Part.Edge(Part.Arc(v4, vc1, v1))
w1 = Part.Wire([l1, l2, l3, l4])
w2 = Part.Wire(e1)
try:
ex1 = w2.makePipeShell([w1],True, True)
except:
#Part.show(w1)
#Part.show(w2)
return (None, e1.valueAt(e1.LastParameter))
cyl1 = Part.makeCylinder(rad, height, curpos)
curpos = e1.valueAt(e1.LastParameter)
cyl2 = Part.makeCylinder(rad, height, curpos)
ex1s = Part.Solid(ex1)
f1 = ex1s.fuse([cyl1,cyl2]).removeSplitter()
return (f1, curpos)
# get a solid representation of a tool going along path
def GetPathSolid(self, tool, cmd, pos):
toolPath = PathGeom.edgeForCmd(cmd, pos)
#curpos = e1.valueAt(e1.LastParameter)
startDir = toolPath.tangentAt(0)
startDir[2] = 0.0
endPos = toolPath.valueAt(toolPath.LastParameter)
endDir = toolPath.tangentAt(toolPath.LastParameter)
try:
startDir.normalize()
endDir.normalize()
except:
return (None, endPos)
height = self.height
# hack to overcome occ bugs
rad = tool.Diameter / 2.0 - 0.001 * pos[2]
#rad = rad + 0.001 * self.icmd
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:
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
#rad = tool.Diameter / 2.0 - 0.001 * pos[2] # hack to overcome occ bug
xf = dir[0] * rad
yf = dir[1] * rad
xp = pos[0]
yp = pos[1]
zp = pos[2]
h = tool.CuttingEdgeHeight
# 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()]
form.listOperations.clear()
self.operations = []
for op in j.Operations.OutList:
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)
def GuiBusy(self, isBusy):
form = self.taskForm.form
#form.toolButtonStop.setEnabled()
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 SimFF(self):
self.GuiBusy(True)
self.timer.start(10)
self.disableAnim = True
def SimStep(self):
self.disableAnim = False
self.PerformCut()
def SimPlay(self):
self.disableAnim = False
self.GuiBusy(True)
self.timer.start(20)
def ViewShape(self):
if self.isVoxel:
self.cutMaterial.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 RemoveMaterial(self):
if self.cutMaterial is None:
return
FreeCAD.ActiveDocument.removeObject(self.cutMaterial.Name)
self.cutMaterial = None
def accept(self):
self.EndSimulation()
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")