diff --git a/src/Mod/CAM/CMakeLists.txt b/src/Mod/CAM/CMakeLists.txt
index 1dacd878c8..1fe39d9e2e 100644
--- a/src/Mod/CAM/CMakeLists.txt
+++ b/src/Mod/CAM/CMakeLists.txt
@@ -96,6 +96,7 @@ SET(PathPythonMainGui_SRCS
Path/Main/Gui/PreferencesJob.py
Path/Main/Gui/SanityCmd.py
Path/Main/Gui/Simulator.py
+ Path/Main/Gui/SimulatorGL.py
)
SET(PathPythonMainSanity_SRCS
diff --git a/src/Mod/CAM/Gui/Resources/Path.qrc b/src/Mod/CAM/Gui/Resources/Path.qrc
index 1f57b53a6e..18c1ed1234 100644
--- a/src/Mod/CAM/Gui/Resources/Path.qrc
+++ b/src/Mod/CAM/Gui/Resources/Path.qrc
@@ -49,6 +49,7 @@
icons/CAM_Shape.svg
icons/CAM_SimpleCopy.svg
icons/CAM_Simulator.svg
+ icons/CAM_SimulatorGL.svg
icons/CAM_Slot.svg
icons/CAM_Stop.svg
icons/CAM_ThreadMilling.svg
@@ -121,7 +122,19 @@
panels/ToolBitSelector.ui
panels/TaskPathCamoticsSim.ui
panels/TaskPathSimulator.ui
+ panels/TaskCAMSimulator.ui
panels/ZCorrectEdit.ui
+ gl_simulator/0.png
+ gl_simulator/1.png
+ gl_simulator/4.png
+ gl_simulator/X.png
+ gl_simulator/Faster.png
+ gl_simulator/Pause.png
+ gl_simulator/Play.png
+ gl_simulator/Rotate.png
+ gl_simulator/SingleStep.png
+ gl_simulator/Slider.png
+ gl_simulator/Thumb.png
preferences/Advanced.ui
preferences/PathDressupHoldingTags.ui
preferences/PathJob.ui
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/0.png b/src/Mod/CAM/Gui/Resources/gl_simulator/0.png
new file mode 100644
index 0000000000..4ce5680469
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/0.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/1.png b/src/Mod/CAM/Gui/Resources/gl_simulator/1.png
new file mode 100644
index 0000000000..b9e3deeb29
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/1.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/4.png b/src/Mod/CAM/Gui/Resources/gl_simulator/4.png
new file mode 100644
index 0000000000..976c6cb86d
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/4.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Faster.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Faster.png
new file mode 100644
index 0000000000..ef68900620
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Faster.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Pause.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Pause.png
new file mode 100644
index 0000000000..c178265b40
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Pause.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Play.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Play.png
new file mode 100644
index 0000000000..ce1c9c1560
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Play.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Rotate.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Rotate.png
new file mode 100644
index 0000000000..69aaa95130
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Rotate.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/SingleStep.png b/src/Mod/CAM/Gui/Resources/gl_simulator/SingleStep.png
new file mode 100644
index 0000000000..2f5e48f913
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/SingleStep.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Slider.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Slider.png
new file mode 100644
index 0000000000..2f8c34233f
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Slider.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/Thumb.png b/src/Mod/CAM/Gui/Resources/gl_simulator/Thumb.png
new file mode 100644
index 0000000000..84fca01b5b
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/Thumb.png differ
diff --git a/src/Mod/CAM/Gui/Resources/gl_simulator/X.png b/src/Mod/CAM/Gui/Resources/gl_simulator/X.png
new file mode 100644
index 0000000000..c983441030
Binary files /dev/null and b/src/Mod/CAM/Gui/Resources/gl_simulator/X.png differ
diff --git a/src/Mod/CAM/Gui/Resources/icons/CAM_SimulatorGL.svg b/src/Mod/CAM/Gui/Resources/icons/CAM_SimulatorGL.svg
new file mode 100644
index 0000000000..fb0669f91d
--- /dev/null
+++ b/src/Mod/CAM/Gui/Resources/icons/CAM_SimulatorGL.svg
@@ -0,0 +1,928 @@
+
+
+
+
diff --git a/src/Mod/CAM/Gui/Resources/panels/TaskCAMSimulator.ui b/src/Mod/CAM/Gui/Resources/panels/TaskCAMSimulator.ui
new file mode 100644
index 0000000000..ab70e91cd7
--- /dev/null
+++ b/src/Mod/CAM/Gui/Resources/panels/TaskCAMSimulator.ui
@@ -0,0 +1,154 @@
+
+
+ TaskPathSimulator
+
+
+
+ 0
+ 0
+ 288
+ 335
+
+
+
+ Path Simulator
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 5
+
+
+
+ false
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+
+ 50
+ 0
+
+
+
+ Accuracy:
+
+
+
+ -
+
+
+ 1
+
+
+ 10
+
+
+ 2
+
+
+ 10
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 50
+ 0
+
+
+
+
+ 40
+ 16777215
+
+
+
+ %
+
+
+
+
+
+ -
+
+
-
+
+
+
+ 24
+ 16777215
+
+
+
+ Job:
+
+
+
+ -
+
+
+ -
+
+
+ Activate / resume simulation
+
+
+ Play
+
+
+
+ :/icons/CAM_BPlay.svg:/icons/CAM_BPlay.svg
+
+
+
+ 32
+ 32
+
+
+
+
+
+
+ -
+
+
+ QAbstractItemView::NoSelection
+
+
+
+
+
+
+
+
+
+
+ SimStop()
+ SimPlay()
+ SimPause()
+ SimStep()
+ SimFF()
+
+
diff --git a/src/Mod/CAM/InitGui.py b/src/Mod/CAM/InitGui.py
index 515ac7adec..7c6b36a123 100644
--- a/src/Mod/CAM/InitGui.py
+++ b/src/Mod/CAM/InitGui.py
@@ -97,6 +97,7 @@ class CAMWorkbench(Workbench):
toolcmdlist = [
"CAM_Inspect",
"CAM_Simulator",
+ "CAM_SimulatorGL",
"CAM_SelectLoop",
"CAM_OpActiveToggle",
]
diff --git a/src/Mod/CAM/Path/GuiInit.py b/src/Mod/CAM/Path/GuiInit.py
index 2655ef5818..533a0da7cd 100644
--- a/src/Mod/CAM/Path/GuiInit.py
+++ b/src/Mod/CAM/Path/GuiInit.py
@@ -53,6 +53,7 @@ def Startup():
from Path.Main.Gui import Fixture
from Path.Main.Gui import Inspect
from Path.Main.Gui import Simulator
+ from Path.Main.Gui import SimulatorGL
from Path.Main.Sanity import Sanity
diff --git a/src/Mod/CAM/Path/Main/Gui/SimulatorGL.py b/src/Mod/CAM/Path/Main/Gui/SimulatorGL.py
new file mode 100644
index 0000000000..f3867148e3
--- /dev/null
+++ b/src/Mod/CAM/Path/Main/Gui/SimulatorGL.py
@@ -0,0 +1,309 @@
+# -*- coding: utf-8 -*-
+# ***************************************************************************
+# * Copyright (c) 2017 Shai Seger *
+# * *
+# * 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 Path
+import Path.Base.Util as PathUtil
+import Path.Dressup.Utils as PathDressup
+import PathScripts.PathUtils as PathUtils
+import Path.Main.Job as PathJob
+import PathGui
+import CAMSimulator
+import math
+import os
+
+from FreeCAD import Vector, Base
+from PySide2.QtWidgets import QDialogButtonBox
+
+# 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__))
+
+def IsSame(x, y):
+ return abs(x - y) < 0.0001
+
+def RadiusAt(edge, p):
+ x = edge.valueAt(p).x
+ y = edge.valueAt(p).y
+ return math.sqrt(x * x + y * y)
+
+
+class CAMSimTaskUi:
+ def __init__(self, parent):
+ # this will create a Qt widget from our ui file
+ self.form = FreeCADGui.PySideUic.loadUi(":/panels/TaskCAMSimulator.ui")
+ self.parent = parent
+
+ def getStandardButtons(self, *args):
+ return QDialogButtonBox.Close
+
+ def reject(self):
+ self.parent.cancel()
+ FreeCADGui.Control.closeDialog()
+
+
+def TSError(msg):
+ QtGui.QMessageBox.information(None, "Path Simulation", msg)
+
+
+class CAMSimulation:
+ def __init__(self):
+ self.debug = False
+ self.stdrot = FreeCAD.Rotation(Vector(0, 0, 1), 0)
+ self.iprogress = 0
+ self.numCommands = 0
+ self.simperiod = 20
+ self.quality = 10
+ self.resetSimulation = False
+ self.jobs = []
+
+ def Connect(self, but, sig):
+ QtCore.QObject.connect(but, QtCore.SIGNAL("clicked()"), sig)
+
+ ## Convert tool shape to tool profile needed by GL simulator
+ def FindClosestEdge(self, edges, px, pz):
+ for edge in edges:
+ p1 = edge.FirstParameter
+ p2 = edge.LastParameter
+ rad = RadiusAt(edge, p1)
+ z = edge.valueAt(p1).z
+ if IsSame(px, rad) and IsSame(pz, z):
+ return edge, p1, p2
+ rad = RadiusAt(edge, p2)
+ z = edge.valueAt(p2).z
+ if IsSame(px, rad) and IsSame(pz, z):
+ return edge, p2, p1
+ return None, 0.0, 0.0
+
+ def FindTopMostEdge(self, edges):
+ maxz = 0.0
+ topedge = None
+ top_p1 = 0.0
+ top_p2 = 0.0
+ for edge in edges:
+ p1 = edge.FirstParameter
+ p2 = edge.LastParameter
+ z = edge.valueAt(p1).z
+ if z > maxz:
+ topedge = edge
+ top_p1 = p1
+ top_p2 = p2
+ maxz = z
+ z = edge.valueAt(p2).z
+ if z > maxz:
+ topedge = edge
+ top_p1 = p2
+ top_p2 = p1
+ maxz = z
+ return topedge, top_p1, top_p2
+
+ #the algo is based on locating the side edge that OCC creates on any revolved object
+ def GetToolProfile(self, tool, resolution):
+ shape = tool.Shape
+ sideEdgeList = []
+ for i in range(len(shape.Edges)):
+ edge = shape.Edges[i]
+ if not edge.isClosed():
+ v1 = edge.firstVertex()
+ v2 = edge.lastVertex()
+ tp = "arc" if type(edge.Curve) is Part.Circle else "line"
+ sideEdgeList.append(edge)
+
+ # sort edges as a single 3d line on the x-z plane
+ profile = [0.0, 0.0]
+
+ # first find the topmost edge
+ edge, p1, p2 = self.FindTopMostEdge(sideEdgeList)
+ profile = [RadiusAt(edge, p1), edge.valueAt(p1).z]
+ endrad = 0.0
+ # one by one find all connecting edges
+ while edge is not None:
+ sideEdgeList.remove(edge)
+ if type(edge.Curve) is Part.Circle:
+ # if edge is curved, aproximate it with lines based on resolution
+ nsegments = int(edge.Length / resolution) + 1
+ step = (p2 - p1) / nsegments
+ location = p1 + step
+ print (edge.Length, nsegments, step)
+ while nsegments > 0:
+ endrad = RadiusAt(edge, location)
+ endz = edge.valueAt(location).z
+ profile.append(endrad)
+ profile.append(endz)
+ location += step
+ nsegments -= 1
+ else:
+ endrad = RadiusAt(edge, p2)
+ endz = edge.valueAt(p2).z
+ profile.append(endrad)
+ profile.append(endz)
+ edge, p1, p2 = self.FindClosestEdge(sideEdgeList, endrad, endz)
+ if edge is None:
+ break
+ return profile
+
+ def Activate(self):
+ self.initdone = False
+ self.taskForm = CAMSimTaskUi(self)
+ form = self.taskForm.form
+ self.Connect(form.toolButtonPlay, self.SimPlay)
+ form.sliderAccuracy.valueChanged.connect(self.onAccuracyBarChange)
+ self.onAccuracyBarChange()
+ self._populateJobSelection(form)
+ form.comboJobs.currentIndexChanged.connect(self.onJobChange)
+ self.onJobChange()
+ FreeCADGui.Control.showDialog(self.taskForm)
+ self.disableAnim = False
+ self.firstDrill = True
+ self.millSim = CAMSimulator.PathSim()
+ self.initdone = True
+ self.job = self.jobs[self.taskForm.form.comboJobs.currentIndex()]
+ self.SetupSimulation()
+
+ def _populateJobSelection(self, form):
+ # Make Job selection combobox
+ setJobIdx = 0
+ jobName = ""
+ jIdx = 0
+ # Get list of Job objects in active document
+ jobList = FreeCAD.ActiveDocument.findObjects("Path::FeaturePython", "Job.*")
+ jCnt = len(jobList)
+
+ # Check if user has selected a specific job for simulation
+ guiSelection = FreeCADGui.Selection.getSelectionEx()
+ if guiSelection: # Identify job selected by user
+ sel = guiSelection[0]
+ if hasattr(sel.Object, "Proxy") and isinstance(
+ sel.Object.Proxy, PathJob.ObjectJob
+ ):
+ jobName = sel.Object.Name
+ FreeCADGui.Selection.clearSelection()
+
+ # populate the job selection combobox
+ form.comboJobs.blockSignals(True)
+ form.comboJobs.clear()
+ form.comboJobs.blockSignals(False)
+ for j in jobList:
+ form.comboJobs.addItem(j.ViewObject.Icon, j.Label)
+ self.jobs.append(j)
+ if j.Name == jobName or jCnt == 1:
+ setJobIdx = jIdx
+ jIdx += 1
+
+ # Pre-select GUI-selected job in the combobox
+ if jobName or jCnt == 1:
+ form.comboJobs.setCurrentIndex(setJobIdx)
+ else:
+ form.comboJobs.setCurrentIndex(0)
+
+ 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
+ self.busy = False
+
+ 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 onAccuracyBarChange(self):
+ form = self.taskForm.form
+ self.quality = form.sliderAccuracy.value()
+ qualText = QtCore.QT_TRANSLATE_NOOP("CAM_Simulator", "High")
+ if (self.quality < 4):
+ qualText = QtCore.QT_TRANSLATE_NOOP("CAM_Simulator", "Low")
+ elif (self.quality < 9):
+ qualText = QtCore.QT_TRANSLATE_NOOP("CAM_Simulator", "Medium")
+ form.labelAccuracy.setText(qualText)
+
+ def SimPlay(self):
+ self.millSim.ResetSimulation()
+ for op in self.activeOps:
+ tool = PathDressup.toolController(op).Tool
+ toolNumber = PathDressup.toolController(op).ToolNumber
+ toolProfile = self.GetToolProfile(tool, 0.5)
+ self.millSim.AddTool(toolProfile, toolNumber, tool.Diameter, 1)
+ opCommands = PathUtils.getPathWithPlacement(op).Commands
+ for cmd in opCommands:
+ self.millSim.AddCommand(cmd)
+ self.millSim.BeginSimulation(self.stock, self.quality)
+
+ def cancel(self):
+ #self.EndSimulation()
+ pass
+
+
+class CommandCAMSimulate:
+ def GetResources(self):
+ return {
+ "Pixmap": "CAM_SimulatorGL",
+ "MenuText": QtCore.QT_TRANSLATE_NOOP("CAM_Simulator", "New CAM Simulator"),
+ "Accel": "P, N",
+ "ToolTip": QtCore.QT_TRANSLATE_NOOP(
+ "CAM_Simulator", "Simulate 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):
+ CamSimulation = CAMSimulation()
+ CamSimulation.Activate()
+
+
+if FreeCAD.GuiUp:
+ # register the FreeCAD command
+ FreeCADGui.addCommand("CAM_SimulatorGL", CommandCAMSimulate())
+ FreeCAD.Console.PrintLog("Loading PathSimulator Gui... done\n")
diff --git a/src/Mod/CAM/PathGlobal.h b/src/Mod/CAM/PathGlobal.h
index 04e0505d95..22bd897467 100644
--- a/src/Mod/CAM/PathGlobal.h
+++ b/src/Mod/CAM/PathGlobal.h
@@ -47,9 +47,18 @@
// PathSimulator
#ifndef PathSimulatorExport
#ifdef PathSimulator_EXPORTS
-# define PathSimulatorExport FREECAD_DECL_EXPORT
+#define PathSimulatorExport FREECAD_DECL_EXPORT
#else
-# define PathSimulatorExport FREECAD_DECL_IMPORT
+#define PathSimulatorExport FREECAD_DECL_IMPORT
+#endif
+#endif
+
+// CAMSimulator (new GL simulator)
+#ifndef CAMSimulatorExport
+#ifdef CAMSimulator_EXPORTS
+#define CAMSimulatorExport FREECAD_DECL_EXPORT
+#else
+#define CAMSimulatorExport FREECAD_DECL_IMPORT
#endif
#endif
diff --git a/src/Mod/CAM/PathSimulator/AppGL/AppCAMSimulator.cpp b/src/Mod/CAM/PathSimulator/AppGL/AppCAMSimulator.cpp
new file mode 100644
index 0000000000..8f9ce1d39e
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/AppCAMSimulator.cpp
@@ -0,0 +1,85 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "PreCompiled.h"
+
+#include
+#include
+
+#include "CAMSim.h"
+#include "CAMSimPy.h"
+
+
+namespace CAMSimulator
+{
+class Module: public Py::ExtensionModule
+{
+public:
+ Module()
+ : Py::ExtensionModule("CAMSimulator")
+ {
+ initialize("This module is the CAMSimulator module."); // register with Python
+ }
+
+ ~Module() override
+ {}
+
+private:
+};
+
+PyObject* initModule()
+{
+ return Base::Interpreter().addModule(new Module);
+}
+
+
+} // namespace CAMSimulator
+
+
+/* Python entry */
+PyMOD_INIT_FUNC(CAMSimulator)
+{
+ // load dependent module
+ try {
+ Base::Interpreter().runString("import Part");
+ Base::Interpreter().runString("import Path");
+ Base::Interpreter().runString("import Mesh");
+ }
+ catch (const Base::Exception& e) {
+ PyErr_SetString(PyExc_ImportError, e.what());
+ PyMOD_Return(nullptr);
+ }
+
+ //
+ PyObject* mod = CAMSimulator::initModule();
+ Base::Console().Log("Loading CAMSimulator module.... done\n");
+
+ // Add Types to module
+ Base::Interpreter().addType(&CAMSimulator::CAMSimPy::Type, mod, "PathSim");
+
+ // NOTE: To finish the initialization of our own type objects we must
+ // call PyType_Ready, otherwise we run into a segmentation fault, later on.
+ // This function is responsible for adding inherited slots from a type's base class.
+ CAMSimulator::CAMSim::init();
+
+ PyMOD_Return(mod);
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/CAMSim.cpp b/src/Mod/CAM/PathSimulator/AppGL/CAMSim.cpp
new file mode 100644
index 0000000000..24f1ff2f83
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/CAMSim.cpp
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "PreCompiled.h"
+#include "CAMSim.h"
+#include "DlgCAMSimulator.h"
+#include
+
+
+using namespace Base;
+using namespace CAMSimulator;
+
+TYPESYSTEM_SOURCE(CAMSimulator::CAMSim, Base::BaseClass);
+
+#define MAX_GCODE_LINE_LEN 120
+
+CAMSim::CAMSim()
+{}
+
+CAMSim::~CAMSim()
+{}
+
+void CAMSim::BeginSimulation(Part::TopoShape* stock, float quality)
+{
+ Base::BoundBox3d bbox = stock->getBoundBox();
+ SimStock stk = {(float)bbox.MinX,
+ (float)bbox.MinY,
+ (float)bbox.MinZ,
+ (float)bbox.LengthX(),
+ (float)bbox.LengthY(),
+ (float)bbox.LengthZ(),
+ quality};
+ DlgCAMSimulator::GetInstance()->startSimulation(&stk, quality);
+}
+
+void CAMSimulator::CAMSim::resetSimulation()
+{
+ DlgCAMSimulator::GetInstance()->resetSimulation();
+}
+
+void CAMSim::addTool(const std::vector toolProfilePoints,
+ int toolNumber,
+ float diameter,
+ float resolution)
+{
+ DlgCAMSimulator::GetInstance()->addTool(toolProfilePoints, toolNumber, diameter, resolution);
+}
+
+void CAMSim::AddCommand(Command* cmd)
+{
+ std::string gline = cmd->toGCode();
+ DlgCAMSimulator::GetInstance()->addGcodeCommand(gline.c_str());
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/CAMSim.h b/src/Mod/CAM/PathSimulator/AppGL/CAMSim.h
new file mode 100644
index 0000000000..9fd1d67ab3
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/CAMSim.h
@@ -0,0 +1,77 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef CAMSimulator_CAMSim_H
+#define CAMSimulator_CAMSim_H
+
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include "DlgCAMSimulator.h"
+
+using namespace Path;
+
+namespace CAMSimulator
+{
+
+/** The representation of a CNC Toolpath Simulator */
+
+class CAMSimulatorExport CAMSim: public Base::BaseClass
+{
+ // TYPESYSTEM_HEADER();
+
+public:
+ static Base::Type getClassTypeId(void);
+ virtual Base::Type getTypeId(void) const;
+ static void init(void);
+ static void* create(void);
+
+private:
+ static Base::Type classTypeId;
+
+
+public:
+ CAMSim();
+ ~CAMSim();
+
+ void BeginSimulation(Part::TopoShape* stock, float resolution);
+ void resetSimulation();
+ void addTool(const std::vector toolProfilePoints,
+ int toolNumber,
+ float diameter,
+ float resolution);
+ void AddCommand(Command* cmd);
+
+public:
+ std::unique_ptr m_stock;
+};
+
+} // namespace CAMSimulator
+
+
+#endif // CAMSimulator_CAMSim_H
diff --git a/src/Mod/CAM/PathSimulator/AppGL/CAMSimPy.xml b/src/Mod/CAM/PathSimulator/AppGL/CAMSimPy.xml
new file mode 100644
index 0000000000..5010204ffe
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/CAMSimPy.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+ FreeCAD python wrapper of CAMSimulator
+
+ CAMSimulator.CAMSim():
+
+ Create a path simulator object
+
+
+
+
+
+ BeginSimulation(stock, resolution):
+
+ Start a simulation process on a box shape stock with given resolution
+
+
+
+
+
+
+ ResetSimulation():
+
+ Clear the simulation and all gcode commands
+
+
+
+
+
+
+ SetToolShape(shape, toolnumber, diameter, resolution):
+
+ Set the shape of the tool to be used for simulation
+
+
+
+
+
+
+ AddCommand(command):
+
+ Add a path command to the simulation.
+
+
+
+
+
diff --git a/src/Mod/CAM/PathSimulator/AppGL/CAMSimPyImp.cpp b/src/Mod/CAM/PathSimulator/AppGL/CAMSimPyImp.cpp
new file mode 100644
index 0000000000..7618fb13b1
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/CAMSimPyImp.cpp
@@ -0,0 +1,128 @@
+/**************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "PreCompiled.h"
+
+#include
+#include
+
+#include
+#include
+#include
+
+// inclusion of the generated files (generated out of CAMSimPy.xml)
+#include "CAMSimPy.h"
+#include "CAMSimPy.cpp"
+
+
+using namespace CAMSimulator;
+
+// returns a string which represents the object e.g. when printed in python
+std::string CAMSimPy::representation() const
+{
+ return std::string("");
+}
+
+PyObject* CAMSimPy::PyMake(struct _typeobject*, PyObject*, PyObject*) // Python wrapper
+{
+ // create a new instance of CAMSimPy and the Twin object
+ return new CAMSimPy(new CAMSim);
+}
+
+// constructor method
+int CAMSimPy::PyInit(PyObject* /*args*/, PyObject* /*kwd*/)
+{
+ return 0;
+}
+
+
+PyObject* CAMSimPy::ResetSimulation(PyObject* args)
+{
+ CAMSim* sim = getCAMSimPtr();
+ sim->resetSimulation();
+ Py_IncRef(Py_None);
+ return Py_None;
+}
+
+PyObject* CAMSimPy::BeginSimulation(PyObject* args, PyObject* kwds)
+{
+ static const std::array kwlist {"stock", "resolution", nullptr};
+ PyObject* pObjStock;
+ float resolution;
+ if (!Base::Wrapped_ParseTupleAndKeywords(args, kwds,"O!f",
+ kwlist, &(Part::TopoShapePy::Type), &pObjStock, &resolution)) {
+ return nullptr;
+ }
+ CAMSim* sim = getCAMSimPtr();
+ Part::TopoShape* stock = static_cast(pObjStock)->getTopoShapePtr();
+ sim->BeginSimulation(stock, resolution);
+ Py_IncRef(Py_None);
+ return Py_None;
+}
+
+PyObject* CAMSimPy::AddTool(PyObject* args, PyObject* kwds)
+{
+ static const std::array kwlist {
+ "shape", "toolnumber", "diameter", "resolution", nullptr};
+ PyObject* pObjToolShape;
+ int toolNumber;
+ float resolution;
+ float diameter;
+ if (!Base::Wrapped_ParseTupleAndKeywords(args, kwds, "Oiff", kwlist, &pObjToolShape,
+ &toolNumber, &diameter, &resolution)) {
+ return nullptr;
+ }
+ // The tool shape is defined by a list of 2d points that represents the tool revolving profile
+ Py_ssize_t num_floats = PyList_Size(pObjToolShape);
+ std::vector toolProfile;
+ for (Py_ssize_t i = 0; i < num_floats; ++i) {
+ PyObject* item = PyList_GetItem(pObjToolShape, i);
+ toolProfile.push_back(static_cast(PyFloat_AsDouble(item)));
+ }
+
+ CAMSim* sim = getCAMSimPtr();
+ sim->addTool(toolProfile, toolNumber, diameter, resolution);
+
+ return Py_None;
+}
+
+PyObject* CAMSimPy::AddCommand(PyObject* args)
+{
+ PyObject* pObjCmd;
+ if (!PyArg_ParseTuple(args, "O!", &(Path::CommandPy::Type), &pObjCmd)) {
+ return nullptr;
+ }
+ CAMSim* sim = getCAMSimPtr();
+ Path::Command* cmd = static_cast(pObjCmd)->getCommandPtr();
+ sim->AddCommand(cmd);
+ return Py_None;
+}
+
+PyObject* CAMSimPy::getCustomAttributes(const char* /*attr*/) const
+{
+ return nullptr;
+}
+
+int CAMSimPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/)
+{
+ return 0;
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/CMakeLists.txt b/src/Mod/CAM/PathSimulator/AppGL/CMakeLists.txt
new file mode 100644
index 0000000000..832db7ad15
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/CMakeLists.txt
@@ -0,0 +1,103 @@
+include_directories(
+ ${CMAKE_BINARY_DIR}
+ ${CMAKE_SOURCE_DIR}/src
+ ${CMAKE_BINARY_DIR}/src
+ ${CMAKE_CURRENT_BINARY_DIR}
+ ${Boost_INCLUDE_DIRS}
+ ${COIN3D_INCLUDE_DIRS}
+ ${ZLIB_INCLUDE_DIR}
+ ${OCC_INCLUDE_DIR}
+ ${EIGEN3_INCLUDE_DIR}
+ ${PYTHON_INCLUDE_DIRS}
+ ${XercesC_INCLUDE_DIRS}
+
+)
+
+link_directories(${OCC_LIBRARY_DIR})
+
+set(CAMSimulator_LIBS
+ Path
+ Part
+ Mesh
+ FreeCADApp
+ FreeCADGui
+ ${QtOpenGL_LIBRARIES}
+ ${OPENGL_gl_LIBRARY}
+)
+
+SET(CAMSimulator_SRCS_Python
+ CAMSimPy.xml
+ CAMSimPyImp.cpp
+)
+
+
+SET(CAMSimulator_SRCS_Module
+ AppCAMSimulator.cpp
+ CAMSim.cpp
+ CAMSim.h
+ CAMSimPyImp.cpp
+ DlgCAMSimulator.cpp
+ DlgCAMSimulator.h
+ PreCompiled.cpp
+ PreCompiled.h
+)
+
+SET(CAMSimulator_SRCS_Core
+ EndMill.cpp
+ EndMill.h
+ GCodeParser.cpp
+ GCodeParser.h
+ GlUtils.cpp
+ GlUtils.h
+ GuiDisplay.cpp
+ GuiDisplay.h
+ linmath.h
+ MillMotion.h
+ MillPathSegment.cpp
+ MillPathSegment.h
+ MillSimulation.cpp
+ MillSimulation.h
+ OpenGlWrapper.h
+ Shader.cpp
+ Shader.h
+ SimShapes.cpp
+ SimShapes.h
+ StockObject.cpp
+ StockObject.h
+ Texture.cpp
+ Texture.h
+ TextureLoader.cpp
+ TextureLoader.h
+)
+
+
+generate_from_xml(CAMSimPy)
+
+
+SOURCE_GROUP("Module" FILES ${CAMSimulator_SRCS_Module})
+SOURCE_GROUP("Python" FILES ${CAMSimulator_SRCS_Python})
+SOURCE_GROUP("Core" FILES ${CAMSimulator_SRCS_Core})
+
+SET(CAMSimulator_SRCS_precomp
+ ${CAMSimulator_SRCS_Module}
+ ${CAMSimulator_SRCS_Python}
+)
+
+SET(CAMSimulator_SRCS
+ ${CAMSimulator_SRCS_precomp}
+ ${CAMSimulator_SRCS_Core}
+)
+
+if(FREECAD_USE_PCH)
+ add_definitions(-D_PreComp_)
+ GET_MSVC_PRECOMPILED_SOURCE("PreCompiled.cpp" PCH_SRCS ${CAMSimulator_SRCS_precomp})
+ ADD_MSVC_PRECOMPILED_HEADER(CAMSimulator PreCompiled.h PreCompiled.cpp PCH_SRCS)
+endif(FREECAD_USE_PCH)
+
+add_library(CAMSimulator SHARED ${CAMSimulator_SRCS})
+target_link_libraries(CAMSimulator ${CAMSimulator_LIBS})
+
+SET_BIN_DIR(CAMSimulator CAMSimulator /Mod/CAM)
+SET_PYTHON_PREFIX_SUFFIX(CAMSimulator)
+
+INSTALL(TARGETS CAMSimulator DESTINATION ${CMAKE_INSTALL_LIBDIR})
diff --git a/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.cpp b/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.cpp
new file mode 100644
index 0000000000..4f3f0e2d66
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.cpp
@@ -0,0 +1,240 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "PreCompiled.h"
+
+#include "DlgCAMSimulator.h"
+#include "MillSimulation.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace CAMSimulator;
+using namespace MillSim;
+
+QOpenGLContext* gOpenGlContext;
+
+using namespace MillSim;
+
+namespace CAMSimulator
+{
+
+DlgCAMSimulator::DlgCAMSimulator(QWindow* parent)
+ : QWindow(parent)
+{
+ setSurfaceType(QWindow::OpenGLSurface);
+ mMillSimulator = new MillSimulation();
+}
+
+void DlgCAMSimulator::render(QPainter* painter)
+{
+ Q_UNUSED(painter);
+}
+
+void DlgCAMSimulator::render()
+{
+ mMillSimulator->ProcessSim((unsigned int)(QDateTime::currentMSecsSinceEpoch()));
+}
+
+void DlgCAMSimulator::renderLater()
+{
+ requestUpdate();
+}
+
+bool DlgCAMSimulator::event(QEvent* event)
+{
+ switch (event->type()) {
+ case QEvent::UpdateRequest:
+ renderNow();
+ return true;
+ default:
+ return QWindow::event(event);
+ }
+}
+
+void DlgCAMSimulator::exposeEvent(QExposeEvent* event)
+{
+ Q_UNUSED(event);
+
+ if (isExposed()) {
+ renderNow();
+ }
+}
+
+void DlgCAMSimulator::mouseMoveEvent(QMouseEvent* ev)
+{
+ mMillSimulator->MouseMove(ev->x(), ev->y());
+}
+
+void DlgCAMSimulator::mousePressEvent(QMouseEvent* ev)
+{
+ mMillSimulator->MousePress(ev->button(), true, ev->x(), ev->y());
+}
+
+void DlgCAMSimulator::mouseReleaseEvent(QMouseEvent* ev)
+{
+ mMillSimulator->MousePress(ev->button(), false, ev->x(), ev->y());
+}
+
+void DlgCAMSimulator::wheelEvent(QWheelEvent* ev)
+{
+ mMillSimulator->MouseScroll((float)ev->angleDelta().y() / 120.0f);
+}
+
+void DlgCAMSimulator::resetSimulation()
+{
+ mMillSimulator->Clear();
+}
+
+void DlgCAMSimulator::addGcodeCommand(const char* cmd)
+{
+ mMillSimulator->AddGcodeLine(cmd);
+}
+
+void DlgCAMSimulator::addTool(const std::vector toolProfilePoints,
+ int toolNumber,
+ float diameter,
+ float resolution)
+{
+ std::string toolCmd = "T" + std::to_string(toolNumber);
+ mMillSimulator->AddGcodeLine(toolCmd.c_str());
+ if (!mMillSimulator->ToolExists(toolNumber)) {
+ mMillSimulator->AddTool(toolProfilePoints, toolNumber, diameter);
+ }
+}
+
+void DlgCAMSimulator::hideEvent(QHideEvent* ev)
+{
+ mAnimating = false;
+}
+
+void DlgCAMSimulator::startSimulation(const SimStock* stock, float quality)
+{
+ mStock = *stock;
+ mQuality = quality;
+ mNeedsInitialize = true;
+ show();
+ setAnimating(true);
+}
+
+void DlgCAMSimulator::initialize()
+{
+ mMillSimulator
+ ->SetBoxStock(mStock.mPx, mStock.mPy, mStock.mPz, mStock.mLx, mStock.mLy, mStock.mLz);
+ mMillSimulator->InitSimulation(mQuality);
+
+ const qreal retinaScale = devicePixelRatio();
+ glViewport(0, 0, width() * retinaScale, height() * retinaScale);
+}
+
+void DlgCAMSimulator::checkInitialization()
+{
+ if (!mContext) {
+ mContext = new QOpenGLContext(this);
+ mContext->setFormat(requestedFormat());
+ mContext->create();
+ gOpenGlContext = mContext;
+ mNeedsInitialize = true;
+ }
+
+ mContext->makeCurrent(this);
+
+ if (mNeedsInitialize) {
+ initializeOpenGLFunctions();
+ initialize();
+ mNeedsInitialize = false;
+ }
+}
+
+void DlgCAMSimulator::renderNow()
+{
+ static unsigned int lastTime = 0;
+ static int frameCount = 0;
+ static int fps = 0;
+ if (!isExposed()) {
+ return;
+ }
+
+ checkInitialization();
+
+ frameCount++;
+ unsigned int curtime = QDateTime::currentMSecsSinceEpoch();
+ unsigned int timediff = curtime - lastTime;
+ if (timediff > 10000) {
+ fps = frameCount * 1000 / timediff; // for debug only. not used otherwise.
+ lastTime = curtime;
+ frameCount = 0;
+ }
+ render();
+ mContext->swapBuffers(this);
+
+ if (mAnimating) {
+ renderLater();
+ }
+}
+
+void DlgCAMSimulator::setAnimating(bool animating)
+{
+ mAnimating = animating;
+
+ if (animating) {
+ renderLater();
+ }
+}
+
+DlgCAMSimulator* DlgCAMSimulator::GetInstance()
+{
+ if (mInstance == nullptr) {
+ QSurfaceFormat format;
+ format.setSamples(16);
+ format.setSwapInterval(2);
+ mInstance = new DlgCAMSimulator();
+ mInstance->setFormat(format);
+ mInstance->resize(800, 600);
+ mInstance->setModality(Qt::ApplicationModal);
+ mInstance->show();
+ }
+ return mInstance;
+}
+
+DlgCAMSimulator* DlgCAMSimulator::mInstance = nullptr;
+
+//************************************************************************************************************
+// stock
+//************************************************************************************************************
+SimStock::SimStock(float px, float py, float pz, float lx, float ly, float lz, float res)
+ : mPx(px)
+ , mPy(py)
+ , mPz(pz + 0.005 * lz)
+ , mLx(lx)
+ , mLy(ly)
+ , mLz(1.01 * lz)
+{}
+
+SimStock::~SimStock()
+{}
+
+} // namespace CAMSimulator
\ No newline at end of file
diff --git a/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.h b/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.h
new file mode 100644
index 0000000000..d5c5bc5a65
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.h
@@ -0,0 +1,110 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef PATHSIMULATOR_CAMSimulatorGui_H
+#define PATHSIMULATOR_CAMSimulatorGui_H
+
+// #include
+// #include
+
+// #include
+// #include
+// #include
+#include
+#include
+#include
+#include
+
+
+namespace MillSim
+{
+class MillSimulation; // use short declaration as using 'include' causes a header loop
+}
+
+namespace CAMSimulator
+{
+
+struct SimStock
+{
+public:
+ SimStock(float px, float py, float pz, float lx, float ly, float lz, float res);
+ ~SimStock();
+
+public:
+ float mPx, mPy, mPz; // stock zero position
+ float mLx, mLy, mLz; // stock dimensions
+};
+
+class DlgCAMSimulator: public QWindow, public QOpenGLExtraFunctions
+{
+ Q_OBJECT
+public:
+ explicit DlgCAMSimulator(QWindow* parent = nullptr);
+ ~DlgCAMSimulator()
+ {}
+
+ virtual void render(QPainter* painter);
+ virtual void render();
+ virtual void initialize();
+
+ void setAnimating(bool animating);
+ static DlgCAMSimulator* GetInstance();
+
+public: // slots:
+ void renderLater();
+ void renderNow();
+ void startSimulation(const SimStock* stock, float quality);
+ void resetSimulation();
+ void addGcodeCommand(const char* cmd);
+ void addTool(const std::vector toolProfilePoints,
+ int toolNumber,
+ float diameter,
+ float resolution);
+
+protected:
+ bool event(QEvent* event) override;
+
+ void checkInitialization();
+ void exposeEvent(QExposeEvent* event) override;
+ void mouseMoveEvent(QMouseEvent* ev) override;
+ void mousePressEvent(QMouseEvent* ev) override;
+ void mouseReleaseEvent(QMouseEvent* ev) override;
+ void wheelEvent(QWheelEvent* ev) override;
+ void hideEvent(QHideEvent* ev) override;
+
+private:
+ bool mAnimating = false;
+ bool mNeedsInitialize = false;
+
+ QOpenGLContext* mContext = nullptr;
+ QOpenGLPaintDevice* mDevice = nullptr;
+ MillSim::MillSimulation* mMillSimulator = nullptr;
+ static DlgCAMSimulator* mInstance;
+ SimStock mStock = {0, 0, 0, 1, 1, 1, 1};
+ float mQuality = 10;
+};
+
+
+} // namespace CAMSimulator
+
+
+#endif // PATHSIMULATOR_PathSim_H
diff --git a/src/Mod/CAM/PathSimulator/AppGL/EndMill.cpp b/src/Mod/CAM/PathSimulator/AppGL/EndMill.cpp
new file mode 100644
index 0000000000..68f8f5a73b
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/EndMill.cpp
@@ -0,0 +1,124 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "EndMill.h"
+#include "OpenGlWrapper.h"
+#include "SimShapes.h"
+
+using namespace MillSim;
+
+EndMill::EndMill(int toolid, float diameter)
+{
+ radius = diameter / 2;
+ toolId = toolid;
+}
+
+EndMill::EndMill(const std::vector& toolProfile, int toolid, float diameter)
+ : EndMill(toolid, diameter)
+{
+ profilePoints = nullptr;
+ mHandleAllocation = false;
+
+ int srcBuffSize = toolProfile.size();
+ nPoints = srcBuffSize / 2;
+ if (nPoints < 2) {
+ return;
+ }
+
+ // make sure last point is at 0,0 else, add it
+ bool missingCenterPoint = fabs(toolProfile[nPoints * 2 - 2]) > 0.0001f;
+ if (missingCenterPoint) {
+ nPoints++;
+ }
+
+ int buffSize = PROFILE_BUFFER_SIZE(nPoints);
+ profilePoints = new float[buffSize];
+ if (profilePoints == nullptr) {
+ return;
+ }
+
+ // copy profile points
+ mHandleAllocation = true;
+ for (int i = 0; i < srcBuffSize; i++) {
+ profilePoints[i] = toolProfile[i] + 0.01f; // add some width to reduce simulation artifacts
+ }
+ if (missingCenterPoint) {
+ profilePoints[srcBuffSize] = profilePoints[srcBuffSize + 1] = 0.0f;
+ }
+
+ MirrorPointBuffer();
+}
+
+EndMill::~EndMill()
+{
+ toolShape.FreeResources();
+ halfToolShape.FreeResources();
+ pathShape.FreeResources();
+ if (mHandleAllocation) {
+ delete[] profilePoints;
+ }
+}
+
+void EndMill::GenerateDisplayLists(float quality)
+{
+ // calculate number of slices based on quality.
+ int nslices = 16;
+ if (quality < 3) {
+ nslices = 4;
+ }
+ else if (quality < 7) {
+ nslices = 8;
+ }
+
+ // full tool
+ toolShape.RotateProfile(profilePoints, nPoints, 0, 0, nslices, false);
+
+ // half tool
+ halfToolShape.RotateProfile(profilePoints, nPoints, 0, 0, nslices / 2, true);
+
+ // unit path
+ int nFullPoints = PROFILE_BUFFER_POINTS(nPoints);
+ pathShape.ExtrudeProfileLinear(profilePoints, nFullPoints, 0, 1, 0, 0, true, false);
+}
+
+unsigned int
+EndMill::GenerateArcSegmentDL(float radius, float angleRad, float zShift, Shape* retShape)
+{
+ int nFullPoints = PROFILE_BUFFER_POINTS(nPoints);
+ retShape->ExtrudeProfileRadial(profilePoints,
+ PROFILE_BUFFER_POINTS(nPoints),
+ radius,
+ angleRad,
+ zShift,
+ true,
+ true);
+ return 0;
+}
+
+void EndMill::MirrorPointBuffer()
+{
+ int endpoint = PROFILE_BUFFER_POINTS(nPoints) - 1;
+ for (int i = 0, j = endpoint * 2; i < (nPoints - 1) * 2; i += 2, j -= 2) {
+ profilePoints[j] = -profilePoints[i];
+ profilePoints[j + 1] = profilePoints[i + 1];
+ }
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/EndMill.h b/src/Mod/CAM/PathSimulator/AppGL/EndMill.h
new file mode 100644
index 0000000000..f60a2d81e4
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/EndMill.h
@@ -0,0 +1,62 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __end_mill_h__
+#define __end_mill_h__
+
+#include "SimShapes.h"
+#include
+
+#define PROFILE_BUFFER_POINTS(npoints) ((npoints) * 2 - 1)
+#define PROFILE_BUFFER_SIZE(npoints) (PROFILE_BUFFER_POINTS(npoints) * 2)
+#define MILL_HEIGHT 10
+
+namespace MillSim
+{
+class EndMill
+{
+public:
+ float* profilePoints = nullptr;
+ float radius;
+ int nPoints = 0;
+ int toolId = -1;
+
+ Shape pathShape;
+ Shape halfToolShape;
+ Shape toolShape;
+
+public:
+ EndMill(int toolid, float diameter);
+ EndMill(const std::vector& toolProfile, int toolid, float diameter);
+ virtual ~EndMill();
+ void GenerateDisplayLists(float quality);
+ unsigned int GenerateArcSegmentDL(float radius, float angleRad, float zShift, Shape* retShape);
+
+protected:
+ void MirrorPointBuffer();
+
+private:
+ bool mHandleAllocation = false;
+};
+} // namespace MillSim
+
+#endif
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.cpp b/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.cpp
new file mode 100644
index 0000000000..3868147cce
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.cpp
@@ -0,0 +1,234 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifdef _MSC_VER
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#endif
+
+#include "GCodeParser.h"
+#include
+#include
+
+using namespace MillSim;
+
+static char TokTypes[] = "GTXYZIJKR";
+
+GCodeParser::~GCodeParser()
+{
+ // Clear the vector
+ Operations.clear();
+}
+
+bool GCodeParser::Parse(const char* filename)
+{
+ Operations.clear();
+ lastState = {eNop, -1, 0, 0, 0, 0, 0, 0};
+ lastTool = -1;
+
+ FILE* fl;
+ if ((fl = fopen(filename, "rt")) == nullptr) {
+ return false;
+ }
+
+ char line[120];
+
+ while (!feof(fl)) {
+ if (fgets(line, 120, fl) != NULL) {
+ AddLine(line);
+ }
+ }
+ fclose(fl);
+ return false;
+}
+
+const char* GCodeParser::GetNextToken(const char* ptr, GCToken* token)
+{
+ float tokval;
+ token->letter = '*';
+ while (*ptr != 0) {
+ char letter = toupper(*ptr);
+ ptr++;
+
+ if (letter == ' ') {
+ continue;
+ }
+ if (letter == '(') {
+ break;
+ }
+
+ if (IsValidToken(letter)) {
+ ptr = ParseFloat(ptr, &tokval);
+ token->letter = letter;
+ token->fval = tokval;
+ token->ival = (int)(tokval + 0.5);
+ break;
+ }
+ }
+ return ptr;
+}
+
+bool GCodeParser::IsValidToken(char tok)
+{
+ int len = (int)strlen(TokTypes);
+ for (int i = 0; i < len; i++) {
+ if (tok == TokTypes[i]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const char* GCodeParser::ParseFloat(const char* ptr, float* retFloat)
+{
+ float decPos = 10;
+ float sign = 1;
+ bool decimalPointFound = false;
+ float res = 0;
+ while (*ptr != 0) {
+ char letter = toupper(*ptr);
+ ptr++;
+
+ if (letter == ' ') {
+ continue;
+ }
+
+ if (letter == '-') {
+ sign = -1;
+ }
+ else if (letter == '.') {
+ decimalPointFound = true;
+ }
+ else if (letter >= '0' && letter <= '9') {
+ float digitVal = (float)(letter - '0');
+ if (decimalPointFound) {
+ res = res + digitVal / decPos;
+ decPos *= 10;
+ }
+ else {
+ res = res * 10 + digitVal;
+ }
+ }
+ else {
+ ptr--;
+ break;
+ }
+ }
+ *retFloat = res * sign;
+ return ptr;
+}
+
+bool GCodeParser::ParseLine(const char* ptr)
+{
+ GCToken token;
+ bool validMotion = false;
+ bool exitLoop = false;
+ int cmd = 0;
+ while (*ptr != 0 && !exitLoop) {
+ ptr = GetNextToken(ptr, &token);
+ lastLastState = lastState;
+ switch (token.letter) {
+ case '*':
+ exitLoop = true;
+ break;
+
+ case 'G':
+ cmd = token.ival;
+ if (cmd == 0 || cmd == 1) {
+ lastState.cmd = eMoveLiner;
+ }
+ else if (cmd == 2) {
+ lastState.cmd = eRotateCW;
+ }
+ else if (cmd == 3) {
+ lastState.cmd = eRotateCCW;
+ }
+ else if (cmd == 73 || cmd == 81 || cmd == 82 || cmd == 83) {
+ lastState.cmd = eDril;
+ }
+ break;
+
+ case 'T':
+ lastState.tool = token.ival;
+ break;
+
+ case 'X':
+ lastState.x = token.fval;
+ validMotion = true;
+ break;
+
+ case 'Y':
+ lastState.y = token.fval;
+ validMotion = true;
+ break;
+
+ case 'Z':
+ lastState.z = token.fval;
+ validMotion = true;
+ break;
+
+ case 'I':
+ lastState.i = token.fval;
+ break;
+
+ case 'J':
+ lastState.j = token.fval;
+ break;
+
+ case 'K':
+ lastState.k = token.fval;
+ break;
+
+ case 'R':
+ lastState.r = token.fval;
+ break;
+ }
+ }
+ return validMotion;
+}
+
+bool GCodeParser::AddLine(const char* ptr)
+{
+ bool res = ParseLine(ptr);
+ if (res) {
+ if (lastState.cmd == eDril) {
+ // split to several motions
+ lastState.cmd = eMoveLiner;
+ float rPlane = lastState.r;
+ float finalDepth = lastState.z;
+ lastState.z = rPlane;
+ Operations.push_back(lastState);
+ lastState.z = finalDepth;
+ Operations.push_back(lastState);
+ lastState.z = rPlane;
+ Operations.push_back(lastState);
+ // restore original state
+ lastState.z = finalDepth;
+ lastState.cmd = eDril;
+ }
+ else {
+ Operations.push_back(lastState);
+ }
+ }
+ return res;
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.h b/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.h
new file mode 100644
index 0000000000..db494fb131
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GCodeParser.h
@@ -0,0 +1,59 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __csgcodeparser_h__
+#define __csgcodeparser_h__
+#include "MillMotion.h"
+#include
+
+namespace MillSim
+{
+struct GCToken
+{
+ char letter;
+ float fval;
+ int ival;
+};
+
+class GCodeParser
+{
+public:
+ GCodeParser()
+ {}
+ virtual ~GCodeParser();
+ bool Parse(const char* filename);
+ bool AddLine(const char* ptr);
+
+public:
+ std::vector Operations;
+ MillMotion lastState = {eNop};
+ MillMotion lastLastState = {eNop};
+
+protected:
+ const char* GetNextToken(const char* ptr, GCToken* token);
+ bool IsValidToken(char tok);
+ const char* ParseFloat(const char* ptr, float* retFloat);
+ bool ParseLine(const char* ptr);
+ int lastTool = -1;
+};
+} // namespace MillSim
+#endif
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GlUtils.cpp b/src/Mod/CAM/PathSimulator/AppGL/GlUtils.cpp
new file mode 100644
index 0000000000..50f91983f4
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GlUtils.cpp
@@ -0,0 +1,56 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "GlUtils.h"
+#include
+
+namespace MillSim
+{
+
+int gDebug = -1;
+
+mat4x4 identityMat = {{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}};
+
+void GLClearError()
+{
+ while (glGetError() != GL_NO_ERROR)
+ ;
+}
+
+bool GLLogError()
+{
+ bool isError = false;
+ while (GLenum err = glGetError()) {
+ std::cout << "[Opengl Error] (" << err << ")" << std::endl;
+ isError = true;
+ }
+ return isError;
+}
+
+
+typedef struct Vertex
+{
+ vec3 pos;
+ vec3 col;
+} Vertex;
+
+} // namespace MillSim
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GlUtils.h b/src/Mod/CAM/PathSimulator/AppGL/GlUtils.h
new file mode 100644
index 0000000000..255664b760
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GlUtils.h
@@ -0,0 +1,53 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __glutils_h__
+#define __glutils_h__
+#include "OpenGlWrapper.h"
+#include "linmath.h"
+
+#define PI 3.14159265f
+#define PI2 (PI * 2)
+
+constexpr auto EPSILON = 0.00001f;
+#define EQ_FLOAT(x, y) (fabs((x) - (y)) < EPSILON)
+
+#define MS_MOUSE_LEFT 1
+#define MS_MOUSE_RIGHT 2
+#define MS_MOUSE_MID 4
+#define GL(x) \
+ { \
+ GLClearError(); \
+ x; \
+ if (GLLogError()) \
+ __debugbreak(); \
+ }
+#define RadToDeg(x) (x * 180.0f / PI)
+
+namespace MillSim
+{
+void GLClearError();
+bool GLLogError();
+extern mat4x4 identityMat;
+extern int gDebug;
+} // namespace MillSim
+#endif // !__glutils_h__
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.cpp b/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.cpp
new file mode 100644
index 0000000000..aa730df6bd
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.cpp
@@ -0,0 +1,243 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "GuiDisplay.h"
+#include "OpenGlWrapper.h"
+#include "MillSimulation.h"
+#include
+#include "GlUtils.h"
+#include
+
+using namespace MillSim;
+
+GuiItem guiItems[] = {
+ {0, 0, 360, 554, 0},
+ {0, 0, 448, 540, 1},
+ {0, 0, 170, 540, 'P', true},
+ {0, 0, 170, 540, 'S', false},
+ {0, 0, 210, 540, 'T'},
+ {0, 0, 250, 540, 'F'},
+ {0, 0, 290, 540, ' '},
+ {0, 0, 620, 540, 0, false, 0},
+ {0, 0, 660, 540, 0, false, 0},
+ {0, 0, 645, 540, 0, false, 0},
+ {0, 0, 640, 540, 0, true, 0},
+};
+
+#define NUM_GUI_ITEMS (sizeof(guiItems) / sizeof(GuiItem))
+#define TEX_SIZE 256
+
+std::vector guiFileNames = {"Slider.png",
+ "Thumb.png",
+ "Pause.png",
+ "Play.png",
+ "SingleStep.png",
+ "Faster.png",
+ "Rotate.png",
+ "X.png",
+ "0.png",
+ "1.png",
+ "4.png"};
+
+bool GuiDisplay::GenerateGlItem(GuiItem* guiItem)
+{
+ Vertex2D verts[4];
+ int x = guiItem->texItem.tx;
+ int y = guiItem->texItem.ty;
+ int w = guiItem->texItem.w;
+ int h = guiItem->texItem.h;
+
+ verts[0] = {0, (float)h, mTexture.getTexX(x), mTexture.getTexY(y + h)};
+ verts[1] = {(float)w, (float)h, mTexture.getTexX(x + w), mTexture.getTexY(y + h)};
+ verts[2] = {0, 0, mTexture.getTexX(x), mTexture.getTexY(y)};
+ verts[3] = {(float)w, 0, mTexture.getTexX(x + w), mTexture.getTexY(y)};
+
+ // vertex buffer
+ glGenBuffers(1, &(guiItem->vbo));
+ glBindBuffer(GL_ARRAY_BUFFER, guiItem->vbo);
+ glBufferData(GL_ARRAY_BUFFER, 4 * sizeof(Vertex2D), verts, GL_STATIC_DRAW);
+
+ // glDrawElements(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, nullptr);
+ // vertex array
+ glGenVertexArrays(1, &(guiItem->vao));
+ glBindVertexArray(guiItem->vao);
+ glEnableVertexAttribArray(0);
+ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex2D), (void*)offsetof(Vertex2D, x));
+ glEnableVertexAttribArray(1);
+ glVertexAttribPointer(1,
+ 2,
+ GL_FLOAT,
+ GL_FALSE,
+ sizeof(Vertex2D),
+ (void*)offsetof(Vertex2D, tx));
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIbo);
+ glBindVertexArray(0);
+
+ return true;
+}
+
+bool GuiDisplay::InutGui()
+{
+ // index buffer
+ glGenBuffers(1, &mIbo);
+ GLshort indices[6] = {0, 2, 3, 0, 3, 1};
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIbo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(GLushort), indices, GL_STATIC_DRAW);
+ TextureLoader tLoader(":/gl_simulator/", guiFileNames, TEX_SIZE);
+ unsigned int* buffer = tLoader.GetRawData();
+ if (buffer == nullptr) {
+ return false;
+ }
+ mTexture.LoadImage(buffer, TEX_SIZE, TEX_SIZE);
+ for (int i = 0; i < NUM_GUI_ITEMS; i++) {
+ guiItems[i].texItem = *tLoader.GetTextureItem(i);
+ GenerateGlItem(&(guiItems[i]));
+ }
+
+ mThumbStartX = guiItems[eGuiItemSlider].sx - guiItems[eGuiItemThumb].texItem.w / 2;
+ mThumbMaxMotion = (float)guiItems[eGuiItemSlider].texItem.w;
+
+ UpdateSimSpeed(1);
+
+ // shader
+ mat4x4 projmat;
+ // mat4x4 viewmat;
+ mat4x4_ortho(projmat, 0, 800, 600, 0, -1, 1);
+ mShader.CompileShader((char*)VertShader2DTex, (char*)FragShader2dTex);
+ mShader.UpdateTextureSlot(0);
+ mShader.UpdateProjectionMat(projmat);
+ return true;
+}
+
+void GuiDisplay::RenderItem(int itemId)
+{
+ GuiItem* item = &(guiItems[itemId]);
+ if (item->hidden) {
+ return;
+ }
+ mat4x4 model;
+ mat4x4_translate(model, (float)item->sx, (float)item->sy, 0);
+ mShader.UpdateModelMat(model, nullptr);
+ if (itemId == mPressedItem) {
+ mShader.UpdateObjColor(mPressedColor);
+ }
+ else if (item->mouseOver) {
+ mShader.UpdateObjColor(mHighlightColor);
+ }
+ else if (itemId > 1 && item->actionKey == 0) {
+ mShader.UpdateObjColor(mTextColor);
+ }
+ else {
+ mShader.UpdateObjColor(mStdColor);
+ }
+
+ glBindVertexArray(item->vao);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIbo);
+ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, nullptr);
+}
+
+void GuiDisplay::MouseCursorPos(int x, int y)
+{
+ for (int i = 0; i < NUM_GUI_ITEMS; i++) {
+ GuiItem* g = &(guiItems[i]);
+ if (g->actionKey == 0) {
+ continue;
+ }
+ g->mouseOver =
+ (x > g->sx && y > g->sy && x < (g->sx + g->texItem.w) && y < (g->sy + g->texItem.h));
+ }
+}
+
+void GuiDisplay::MousePressed(int button, bool isPressed, bool isSimRunning)
+{
+ if (button == MS_MOUSE_LEFT) {
+ if (isPressed) {
+ mPressedItem = eGuiItemMax;
+ for (int i = 1; i < NUM_GUI_ITEMS; i++) {
+ GuiItem* g = &(guiItems[i]);
+ if (g->mouseOver && !g->hidden) {
+ mPressedItem = (eGuiItems)i;
+ break;
+ }
+ }
+ if (mPressedItem != eGuiItemMax) {
+ GuiItem* g = &(guiItems[mPressedItem]);
+ if (g->actionKey >= 32) {
+ mMillSim->HandleKeyPress(g->actionKey);
+ }
+ }
+ }
+ else // button released
+ {
+ UpdatePlayState(isSimRunning);
+ mPressedItem = eGuiItemMax;
+ }
+ }
+}
+
+void GuiDisplay::MouseDrag(int buttons, int dx, int dy)
+{
+ if (mPressedItem == eGuiItemThumb) {
+ GuiItem* g = &(guiItems[eGuiItemThumb]);
+ int newx = g->sx + dx;
+ if (newx < mThumbStartX) {
+ newx = mThumbStartX;
+ }
+ if (newx > ((int)mThumbMaxMotion + mThumbStartX)) {
+ newx = (int)mThumbMaxMotion + mThumbStartX;
+ }
+ if (newx != g->sx) {
+ mMillSim->SetSimulationStage((float)(newx - mThumbStartX) / mThumbMaxMotion);
+ g->sx = newx;
+ }
+ }
+}
+
+void GuiDisplay::UpdatePlayState(bool isRunning)
+{
+ guiItems[eGuiItemPause].hidden = !isRunning;
+ guiItems[eGuiItemPlay].hidden = isRunning;
+}
+
+void MillSim::GuiDisplay::UpdateSimSpeed(int speed)
+{
+ guiItems[eGuiItemChar0Img].hidden = speed == 1;
+ guiItems[eGuiItemChar1Img].hidden = speed == 40;
+ guiItems[eGuiItemChar4Img].hidden = speed != 40;
+}
+
+void GuiDisplay::Render(float progress)
+{
+ if (mPressedItem != eGuiItemThumb) {
+ guiItems[eGuiItemThumb].sx = (int)(mThumbMaxMotion * progress) + mThumbStartX;
+ }
+ glDisable(GL_CULL_FACE);
+ glDisable(GL_DEPTH_TEST);
+ mTexture.Activate();
+ mShader.Activate();
+ mShader.UpdateTextureSlot(0);
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+ for (int i = 0; i < NUM_GUI_ITEMS; i++) {
+ RenderItem(i);
+ }
+}
diff --git a/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.h b/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.h
new file mode 100644
index 0000000000..0d9574e4d2
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/GuiDisplay.h
@@ -0,0 +1,103 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __guidisplay_t__
+#define __guidisplay_t__
+#include "OpenGlWrapper.h"
+#include "Texture.h"
+#include "Shader.h"
+#include "TextureLoader.h"
+
+namespace MillSim
+{
+class MillSimulation;
+
+struct GuiItem
+{
+ unsigned int vbo, vao;
+ int sx, sy; // screen location
+ int actionKey; // action key when item pressed
+ bool hidden; // is item hidden
+ bool mouseOver;
+ TextureItem texItem;
+};
+
+struct Vertex2D
+{
+ float x, y;
+ float tx, ty;
+};
+
+enum eGuiItems
+{
+ eGuiItemSlider,
+ eGuiItemThumb,
+ eGuiItemPause,
+ eGuiItemPlay,
+ eGuiItemSingleStep,
+ eGuiItemFaster,
+ eGuiItemRotate,
+ eGuiItemCharXImg,
+ eGuiItemChar0Img,
+ eGuiItemChar1Img,
+ eGuiItemChar4Img,
+ eGuiItemMax
+};
+
+
+class GuiDisplay
+{
+public:
+ // GuiDisplay() {};
+ bool InutGui();
+ void Render(float progress);
+ void MouseCursorPos(int x, int y);
+ void MousePressed(int button, bool isPressed, bool isRunning);
+ void MouseDrag(int buttons, int dx, int dy);
+ void SetMillSimulator(MillSimulation* millSim)
+ {
+ mMillSim = millSim;
+ }
+ void UpdatePlayState(bool isRunning);
+ void UpdateSimSpeed(int speed);
+
+private:
+ bool GenerateGlItem(GuiItem* guiItem);
+ void RenderItem(int itemId);
+
+ vec3 mStdColor = {0.8f, 0.8f, 0.4f};
+ vec3 mHighlightColor = {1.0f, 1.0f, 0.9f};
+ vec3 mPressedColor = {1.0f, 0.5f, 0.0f};
+ vec3 mTextColor = {1.0f, 0.5f, 0.0f};
+
+ Shader mShader;
+ Texture mTexture;
+ eGuiItems mPressedItem = eGuiItemMax;
+ MillSimulation* mMillSim = nullptr;
+ unsigned int mIbo = 0;
+ int mThumbStartX = 0;
+ float mThumbMaxMotion = 0;
+};
+
+} // namespace MillSim
+
+#endif // __guidisplay_t__
diff --git a/src/Mod/CAM/PathSimulator/AppGL/MillMotion.h b/src/Mod/CAM/PathSimulator/AppGL/MillMotion.h
new file mode 100644
index 0000000000..6e0bc427a1
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/MillMotion.h
@@ -0,0 +1,65 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __mill_operation_h__
+#define __mill_operation_h__
+// #include
+#include "EndMill.h"
+#include "linmath.h"
+namespace MillSim
+{
+
+enum eEndMillType
+{
+ eEndmillFlat,
+ eEndmillV,
+ eEndmillBall,
+ eEndmillFillet
+};
+
+enum eCmdType
+{
+ eNop,
+ eMoveLiner,
+ eRotateCW,
+ eRotateCCW,
+ eDril,
+ eChangeTool
+};
+
+struct MillMotion
+{
+ eCmdType cmd;
+ int tool;
+ float x, y, z;
+ float i, j, k;
+ float r;
+};
+
+static inline void MotionPosToVec(vec3 vec, const MillMotion* motion)
+{
+ vec[0] = motion->x;
+ vec[1] = motion->y;
+ vec[2] = motion->z;
+}
+} // namespace MillSim
+#endif
\ No newline at end of file
diff --git a/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.cpp b/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.cpp
new file mode 100644
index 0000000000..b1ea18ba4d
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.cpp
@@ -0,0 +1,242 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "OpenGlWrapper.h"
+#include "MillPathSegment.h"
+#include "SimShapes.h"
+#include "linmath.h"
+#include "GlUtils.h"
+#include
+
+#define N_MILL_SLICES 8
+#define MAX_SEG_DEG (PI / 2.0f) // 90 deg
+#define NIN_SEG_DEG (PI / 90.0f) // 2 deg
+#define SWEEP_ARC_PAD 1.05f
+#define PX 0
+#define PY 1
+#define PZ 2
+
+
+namespace MillSim
+{
+
+bool IsVerticalMotion(MillMotion* m1, MillMotion* m2)
+{
+ return (m1->z != m2->z && EQ_FLOAT(m1->x, m2->x) && EQ_FLOAT(m1->y, m2->y));
+}
+
+bool IsArcMotion(MillMotion* m)
+{
+ if (m->cmd != eRotateCCW && m->cmd != eRotateCW) {
+ return false;
+ }
+ return fabs(m->i > EPSILON) || fabs(m->j) > EPSILON;
+}
+
+float MillPathSegment::mResolution = 1;
+float MillPathSegment::mSmallRadStep = (PI / 8);
+
+MillPathSegment::MillPathSegment(EndMill* _endmill, MillMotion* from, MillMotion* to)
+ : mShearMat {1.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 0.0f,
+ 1.0f}
+{
+
+ MotionPosToVec(mStartPos, from);
+ MotionPosToVec(mDiff, to);
+ vec3_sub(mDiff, mDiff, mStartPos);
+ mXYDistance = sqrtf(mDiff[PX] * mDiff[PX] + mDiff[PY] * mDiff[PY]);
+ mZDistance = fabsf(mDiff[PY]);
+ mXYZDistance = sqrtf(mXYDistance * mXYDistance + mDiff[PZ] * mDiff[PZ]);
+ mXYAngle = atan2f(mDiff[PY], mDiff[PX]);
+ endmill = _endmill;
+ mStartAngRad = mStepAngRad = 0;
+ if (IsArcMotion(to)) {
+ mMotionType = MTCurved;
+ mRadius = sqrtf(to->j * to->j + to->i * to->i);
+ mSmallRad = mRadius <= endmill->radius;
+
+ if (mSmallRad) {
+ mStepAngRad = mSmallRadStep;
+ }
+ else {
+ mStepAngRad = asinf(mResolution / mRadius);
+ if (mStepAngRad > MAX_SEG_DEG) {
+ mStepAngRad = MAX_SEG_DEG;
+ }
+ else if (mStepAngRad < NIN_SEG_DEG) {
+ mStepAngRad = NIN_SEG_DEG;
+ }
+ }
+
+ MotionPosToVec(mCenter, from);
+ mCenter[PX] += to->i;
+ mCenter[PY] += to->j;
+ mArcDir = to->cmd == eRotateCCW ? -1.f : 1.f;
+ mStartAngRad = atan2f(mCenter[PX] - from->x, from->y - mCenter[PY]);
+ float endAng = atan2f(mCenter[PX] - to->x, to->y - mCenter[PY]);
+ mSweepAng = (mStartAngRad - endAng) * mArcDir;
+ if (mSweepAng < EPSILON) {
+ mSweepAng += PI * 2;
+ }
+ numSimSteps = (int)(mSweepAng / mStepAngRad) + 1;
+ mStepAngRad = mArcDir * mSweepAng / numSimSteps;
+ if (mSmallRad) {
+ // when the radius is too small, we just use the tool itself to carve the stock
+ mShape = endmill->toolShape;
+ }
+ else {
+ endmill->GenerateArcSegmentDL(mRadius,
+ mStepAngRad * SWEEP_ARC_PAD,
+ mDiff[PZ] / numSimSteps,
+ &mShape);
+ numSimSteps++;
+ }
+
+ isMultyPart = true;
+ }
+ else {
+ numSimSteps = (int)(mXYZDistance / mResolution);
+ if (numSimSteps == 0) {
+ numSimSteps = 1;
+ }
+ isMultyPart = false;
+ mStepDistance = mXYDistance / numSimSteps;
+ mStepLength[PX] = mDiff[PX];
+ mStepLength[PY] = mDiff[PY];
+ mStepLength[PZ] = mDiff[PZ];
+ vec3_scale(mStepLength, mStepLength, 1.f / (float)numSimSteps);
+
+ if (IsVerticalMotion(from, to)) {
+ mMotionType = MTVertical;
+ }
+ else {
+ mMotionType = MTHorizontal;
+ mShearMat[0][2] = mDiff[PZ] / mXYDistance;
+ }
+ }
+}
+
+MillPathSegment::~MillPathSegment()
+{
+ mShape.FreeResources();
+}
+
+
+void MillPathSegment::render(int step)
+{
+ mStepNumber = step;
+ mat4x4 mat, mat2, rmat;
+ mat4x4_identity(mat);
+ mat4x4_identity(rmat);
+ if (mMotionType == MTCurved) {
+ mat4x4_translate_in_place(mat,
+ mCenter[PX],
+ mCenter[PY],
+ mCenter[PZ] + mDiff[PZ] * (step - 1) / numSimSteps);
+ mat4x4_rotate_Z(mat, mat, mStartAngRad - (step - 1) * mStepAngRad);
+ mat4x4_rotate_Z(rmat, rmat, mStartAngRad - (step - 1) * mStepAngRad);
+
+ if (mSmallRad || step == numSimSteps) {
+ mat4x4_translate_in_place(mat, 0, mRadius, 0);
+ endmill->toolShape.Render(mat, rmat);
+ }
+ else {
+ mShape.Render(mat, rmat);
+ }
+ }
+ else {
+ if (mMotionType == MTVertical) {
+ if (mStepLength[PZ] > 0) {
+ mat4x4_translate_in_place_v(mat, mStartPos);
+ }
+ else {
+ mat4x4_translate_in_place(mat,
+ mStartPos[PX],
+ mStartPos[PY],
+ mStartPos[PZ] + mStepNumber * mStepLength[PZ]);
+ }
+ endmill->toolShape.Render(mat, rmat);
+ }
+ else {
+ float renderDist = step * mStepDistance;
+ mat4x4_translate_in_place_v(mat, mStartPos);
+ mat4x4_rotate_Z(mat, mat, mXYAngle);
+ mat4x4_rotate_Z(rmat, rmat, mXYAngle);
+ mat4x4_dup(mat2, mat);
+ if (mDiff[PZ] != 0.0) {
+ mat4x4_mul(mat2, mat2, mShearMat);
+ }
+ mat4x4_scale_aniso(mat2, mat2, renderDist, 1, 1);
+ endmill->pathShape.Render(mat2, rmat);
+ mat4x4_translate_in_place(mat, renderDist, 0, mDiff[PZ]);
+ endmill->halfToolShape.Render(mat, rmat);
+ }
+ }
+}
+
+void MillPathSegment::GetHeadPosition(vec3 headPos)
+{
+ if (mMotionType == MTCurved) {
+ float angRad = mStartAngRad - mStepNumber * mStepAngRad;
+ vec3_set(mHeadPos, -mRadius * sinf(angRad), mRadius * cosf(angRad), 0);
+ vec3_add(mHeadPos, mHeadPos, mCenter);
+ }
+ else {
+ vec3_dup(mHeadPos, mStepLength);
+ vec3_scale(mHeadPos, mHeadPos, (float)mStepNumber);
+ vec3_add(mHeadPos, mHeadPos, mStartPos);
+ }
+ vec3_dup(headPos, mHeadPos);
+}
+float MillPathSegment::SetQuality(float quality, float maxStockDimension)
+{
+ mResolution = maxStockDimension * 0.05 / quality;
+ if (mResolution > 4) {
+ mResolution = 4;
+ }
+ if (mResolution < 0.5) {
+ mResolution = 0.5;
+ }
+ mSmallRadStep = PI / 8;
+ if (quality < 4) {
+ mSmallRadStep = PI / 2;
+ }
+ else if (quality < 8) {
+ mSmallRadStep = PI / 4;
+ }
+ return mResolution;
+}
+} // namespace MillSim
\ No newline at end of file
diff --git a/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.h b/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.h
new file mode 100644
index 0000000000..43369cb5f7
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/MillPathSegment.h
@@ -0,0 +1,98 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __mill_path_segment_h__
+#define __mill_path_segment_h__
+
+
+#include "MillMotion.h"
+#include "EndMill.h"
+#include "linmath.h"
+
+namespace MillSim
+{
+
+enum MotionType
+{
+ MTVertical = 0,
+ MTHorizontal,
+ MTCurved
+};
+
+
+bool IsVerticalMotion(MillMotion* m1, MillMotion* m2);
+
+
+class MillPathSegment
+{
+public:
+ ///
+ /// Create a mill path segment primitive
+ ///
+ /// Mill object
+ /// Start point
+ /// End point
+ MillPathSegment(EndMill* endmill, MillMotion* from, MillMotion* to);
+ virtual ~MillPathSegment();
+
+
+ /// Calls the display list.
+ virtual void render(int substep);
+ virtual void GetHeadPosition(vec3 headPos);
+ static float SetQuality(float quality, float maxStockDimension); // 1 minimum, 10 maximum
+
+public:
+ EndMill* endmill = nullptr;
+ bool isMultyPart;
+ int numSimSteps;
+ int indexInArray;
+
+
+protected:
+ mat4x4 mShearMat;
+ Shape mShape;
+ float mXYDistance;
+ float mXYZDistance;
+ float mZDistance;
+ float mXYAngle;
+ float mStartAngRad;
+ float mStepAngRad;
+ float mStepDistance = 0;
+ float mSweepAng;
+ float mRadius = 0;
+ float mArcDir = 0;
+ bool mSmallRad = false;
+ int mStepNumber = 0;
+
+ static float mSmallRadStep;
+ static float mResolution;
+
+ vec3 mDiff;
+ vec3 mStepLength = {0};
+ vec3 mCenter = {0};
+ vec3 mStartPos;
+ vec3 mHeadPos = {0};
+ MotionType mMotionType;
+};
+} // namespace MillSim
+
+#endif
diff --git a/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.cpp b/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.cpp
new file mode 100644
index 0000000000..be822cc28b
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.cpp
@@ -0,0 +1,601 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "MillSimulation.h"
+#include "OpenGlWrapper.h"
+#include
+#include
+
+namespace MillSim {
+
+ MillSimulation::MillSimulation()
+ {
+ mCurMotion = { eNop, -1, 0, 0, 0, 0, 0, 0 };
+ guiDisplay.SetMillSimulator(this);
+ }
+
+ void MillSimulation::ClearMillPathSegments() {
+ //for (std::vector::const_iterator i = MillPathSegments.begin(); i != MillPathSegments.end(); ++i) {
+ // MillSim::MillPathSegment* p = *i;
+ // delete p;
+ //}
+ for (int i = 0; i < MillPathSegments.size(); i++)
+ delete MillPathSegments[i];
+ MillPathSegments.clear();
+ }
+
+ void MillSimulation::Clear()
+ {
+ mCodeParser.Operations.clear();
+ for (int i = 0; i < mToolTable.size(); i++)
+ delete mToolTable[i];
+ mToolTable.clear();
+ mCurStep = 0;
+ mPathStep = -1;
+ mNTotalSteps = 0;
+ }
+
+
+
+ void MillSimulation::SimNext()
+ {
+ static int simDecim = 0;
+
+ simDecim++;
+ if (simDecim < 1)
+ return;
+
+ simDecim = 0;
+
+ if (mCurStep < mNTotalSteps)
+ {
+ mCurStep += mSimSpeed;
+ CalcSegmentPositions();
+ }
+ }
+
+ void MillSimulation::InitSimulation(float quality)
+ {
+ ClearMillPathSegments();
+
+ mDestMotion = mZeroPos;
+ //gDestPos = curMillOperation->startPos;
+ mCurStep = 0;
+ mPathStep = -1;
+ mNTotalSteps = 0;
+ MillPathSegment::SetQuality(quality, mMaxFar);
+ int nOperations = (int)mCodeParser.Operations.size();;
+ for (int i = 0; i < nOperations; i++)
+ {
+ mCurMotion = mDestMotion;
+ mDestMotion = mCodeParser.Operations[i];
+ EndMill* tool = GetTool(mDestMotion.tool);
+ if (tool != nullptr)
+ {
+ MillSim::MillPathSegment* segment = new MillSim::MillPathSegment(tool, &mCurMotion, &mDestMotion);
+ segment->indexInArray = i;
+ mNTotalSteps += segment->numSimSteps;
+ MillPathSegments.push_back(segment);
+ }
+ }
+ mNPathSteps = (int)MillPathSegments.size();
+ InitDisplay(quality);
+ }
+
+ EndMill* MillSimulation::GetTool(int toolId)
+ {
+ for (int i = 0; i < mToolTable.size(); i++)
+ {
+ if (mToolTable[i]->toolId == toolId)
+ {
+ return mToolTable[i];
+ }
+ }
+ return nullptr;
+ }
+
+ void MillSimulation::RemoveTool(int toolId)
+ {
+ EndMill* tool;
+ if ((tool = GetTool(toolId)) != nullptr) {
+ auto it = std::find(mToolTable.begin(), mToolTable.end(), tool);
+ if (it != mToolTable.end()) {
+ mToolTable.erase(it);
+ }
+ delete tool;
+ }
+ }
+
+ void MillSimulation::AddTool(EndMill* tool)
+ {
+ // if we have another tool with same id, remove it
+ RemoveTool(tool->toolId);
+ mToolTable.push_back(tool);
+ }
+
+ void
+ MillSimulation::AddTool(const std::vector& toolProfile, int toolid, float diameter)
+ {
+ // if we have another tool with same id, remove it
+ RemoveTool(toolid);
+ EndMill* tool = new EndMill(toolProfile, toolid, diameter);
+ mToolTable.push_back(tool);
+ }
+
+ void MillSimulation::GlsimStart()
+ {
+ glEnable(GL_CULL_FACE);
+ glEnable(GL_DEPTH_TEST);
+ glEnable(GL_STENCIL_TEST);
+ glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
+ }
+
+ void MillSimulation::GlsimToolStep1(void)
+ {
+ glCullFace(GL_BACK);
+ glDepthFunc(GL_LESS);
+ glStencilFunc(GL_ALWAYS, 1, 0xFF);
+ glStencilOp(GL_ZERO, GL_ZERO, GL_REPLACE);
+ glDepthMask(GL_FALSE);
+ }
+
+
+ void MillSimulation::GlsimToolStep2(void)
+ {
+ glStencilFunc(GL_EQUAL, 1, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glDepthFunc(GL_GREATER);
+ glCullFace(GL_FRONT);
+ glDepthMask(GL_TRUE);
+ }
+
+ void MillSimulation::GlsimClipBack(void)
+ {
+ glStencilFunc(GL_ALWAYS, 1, 0xFF);
+ glStencilOp(GL_REPLACE, GL_REPLACE, GL_ZERO);
+ glDepthFunc(GL_LESS);
+ glCullFace(GL_FRONT);
+ glDepthMask(GL_FALSE);
+ }
+
+
+ void MillSimulation::GlsimRenderStock(void)
+ {
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glEnable(GL_STENCIL_TEST);
+ glStencilFunc(GL_EQUAL, 1, 0xFF);
+ glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
+ glDepthFunc(GL_EQUAL);
+ glCullFace(GL_BACK);
+ }
+
+ void MillSimulation::GlsimRenderTools(void)
+ {
+ glCullFace(GL_FRONT);
+ }
+
+ void MillSimulation::GlsimEnd(void)
+ {
+ glCullFace(GL_BACK);
+ glStencilFunc(GL_ALWAYS, 1, 0xFF);
+ glDisable(GL_STENCIL_TEST);
+ glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
+ glDepthMask(GL_TRUE);
+ glDepthFunc(GL_LESS);
+ }
+
+ void MillSimulation::renderSegmentForward(int iSeg)
+ {
+ MillSim::MillPathSegment* p = MillPathSegments.at(iSeg);
+ int step = iSeg == mPathStep ? mSubStep : p->numSimSteps;
+ int start = p->isMultyPart ? 1 : step;
+ for (int i = start; i <= step; i++)
+ {
+ GlsimToolStep1();
+ p->render(i);
+ GlsimToolStep2();
+ p->render(i);
+ }
+ }
+
+ void MillSimulation::renderSegmentReversed(int iSeg)
+ {
+ MillSim::MillPathSegment* p = MillPathSegments.at(iSeg);
+ int step = iSeg == mPathStep ? mSubStep : p->numSimSteps;
+ int end = p->isMultyPart ? 1 : step;
+ for (int i = step; i >= end; i--)
+ {
+ GlsimToolStep1();
+ p->render(i);
+ GlsimToolStep2();
+ p->render(i);
+ }
+ }
+
+ void MillSimulation::CalcSegmentPositions()
+ {
+ mSubStep = mCurStep;
+ for (mPathStep = 0; mPathStep < mNPathSteps; mPathStep++)
+ {
+ MillSim::MillPathSegment* p = MillPathSegments[mPathStep];
+ if (mSubStep < p->numSimSteps)
+ break;
+ mSubStep -= p->numSimSteps;
+ }
+ if (mPathStep >= mNPathSteps)
+ {
+ mPathStep = mNPathSteps - 1;
+ mSubStep = MillPathSegments[mPathStep]->numSimSteps;
+ }
+ else
+ mSubStep++;
+ }
+
+ void MillSimulation::Render()
+ {
+ mat4x4 matLookAt, model;
+ mat4x4_identity(model);
+ mat4x4_look_at(matLookAt, eye, target, upvec);
+
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+
+ mat4x4_translate_in_place(matLookAt, mEyeX * mEyeXZFactor, 0, mEyeZ * mEyeXZFactor);
+ mat4x4_rotate_X(matLookAt, matLookAt, mEyeInclination);
+ mat4x4_rotate_Z(matLookAt, matLookAt, mEyeRoration);
+ mat4x4_translate_in_place(matLookAt, -mStockObject.center[0], -mStockObject.center[1], -mStockObject.center[2]);
+
+ shaderFlat.Activate();
+ shaderFlat.UpdateViewMat(matLookAt);
+
+ GlsimStart();
+ mStockObject.render();
+
+ GlsimToolStep2();
+
+ for (int i = 0; i <= mPathStep; i++)
+ renderSegmentForward(i);
+
+ for (int i = mPathStep; i >= 0; i--)
+ renderSegmentForward(i);
+
+ for (int i = 0; i < mPathStep; i++)
+ renderSegmentReversed(i);
+
+ for (int i = mPathStep; i >= 0; i--)
+ renderSegmentReversed(i);
+
+ GlsimClipBack();
+ mStockObject.render();
+
+ // start coloring
+ shader3D.Activate();
+ shader3D.UpdateViewMat(matLookAt);
+ shader3D.UpdateObjColor(stockColor);
+ GlsimRenderStock();
+ mStockObject.render();
+ GlsimRenderTools();
+
+ // render cuts (back faces of tools)
+
+ shaderInv3D.Activate();
+ shaderInv3D.UpdateViewMat(matLookAt);
+ shaderInv3D.UpdateObjColor(cutColor);
+ for (int i = 0; i <= mPathStep; i++)
+ {
+ MillSim::MillPathSegment* p = MillPathSegments.at(i);
+ int step = (i == mPathStep) ? mSubStep : p->numSimSteps;
+ int start = p->isMultyPart ? 1 : step;
+ for (int j = start; j <= step; j++)
+ MillPathSegments.at(i)->render(j);
+ }
+
+ GlsimEnd();
+
+ glEnable(GL_CULL_FACE);
+
+ if (mPathStep >= 0)
+ {
+ vec3 toolPos;
+ MotionPosToVec(toolPos, &mDestMotion);
+ MillSim::MillPathSegment* p = MillPathSegments.at(mPathStep);
+ p->GetHeadPosition(toolPos);
+ mat4x4 tmat;
+ mat4x4_translate(tmat, toolPos[0], toolPos[1], toolPos[2]);
+ //mat4x4_translate(tmat, toolPos.x, toolPos.y, toolPos.z);
+ shader3D.Activate();
+ shader3D.UpdateObjColor(toolColor);
+ p->endmill->toolShape.Render(tmat, identityMat);
+ }
+
+ shaderFlat.Activate();
+ shaderFlat.UpdateObjColor(lightColor);
+ mlightObject.render();
+
+ if (mDebug > 0)
+ {
+ mat4x4 test;
+ mat4x4_dup(test, model);
+ mat4x4_translate_in_place(test, 20, 20, 3);
+ mat4x4_rotate_Z(test, test, 30.f * 3.14f / 180.f);
+ int dpos = mNPathSteps - mDebug2;
+ MillSim::MillPathSegment* p = MillPathSegments.at(dpos);
+ if (mDebug > p->numSimSteps)
+ mDebug = 1;
+ p->render(mDebug);
+ }
+ float progress = (float)mCurStep / mNTotalSteps;
+ guiDisplay.Render(progress);
+ }
+
+ void MillSimulation::ProcessSim(unsigned int time_ms) {
+
+ static int ancient = 0;
+ static int last = 0;
+ static int msec = 0;
+ static int fps = 0;
+ static int renderTime = 0;
+
+ last = msec;
+ msec = time_ms;
+ if (mIsRotate) {
+ mEyeRoration += (msec - last) / 4600.0f;
+ while (mEyeRoration >= PI2)
+ mEyeRoration -= PI2;
+ }
+
+ if (last / 1000 != msec / 1000) {
+ float calcFps = 1000.0f * fps / (msec - ancient);
+ mFpsStream.str("");
+ mFpsStream << "fps: " << calcFps << " rendertime:" << renderTime << " zpos:" << mDestMotion.z << std::ends;
+ ancient = msec;
+ fps = 0;
+ }
+
+ if (mSimPlaying || mSingleStep)
+ {
+ SimNext();
+ mSingleStep = false;
+ }
+
+ Render();
+
+ ++fps;
+ }
+
+ void MillSimulation::HandleKeyPress(int key)
+ {
+ switch (key) {
+ case ' ':
+ mIsRotate = !mIsRotate;
+ break;
+
+ case 'S':
+ mSimPlaying = true;
+ break;
+
+ case 'P':
+ mSimPlaying = false;
+ break;
+
+ case 'T':
+ mSimPlaying = false;
+ mSingleStep = true;
+ break;
+
+ case'D':
+ mDebug++;
+ break;
+
+ case'K':
+ mDebug2++;
+ gDebug = mNPathSteps - mDebug2;
+ break;
+
+ case 'F':
+ if (mSimSpeed == 1) mSimSpeed = 10;
+ else if (mSimSpeed == 10) mSimSpeed = 40;
+ else mSimSpeed = 1;
+ guiDisplay.UpdateSimSpeed(mSimSpeed);
+ break;
+
+ default:
+ if (key >= '1' && key <= '9')
+ mSimSpeed = key - '0';
+ break;
+ }
+ guiDisplay.UpdatePlayState(mSimPlaying);
+ }
+
+ void MillSimulation::UpdateEyeFactor(float factor)
+ {
+ mEyeDistFactor = factor;
+ mEyeXZFactor = factor * mMaxFar * 0.005f;
+ eye[1] = -factor * mMaxFar;
+ }
+
+ void MillSimulation::TiltEye(float tiltStep)
+ {
+ mEyeInclination += tiltStep;
+ if (mEyeInclination > PI / 2)
+ mEyeInclination = PI / 2;
+ else if (mEyeInclination < -PI / 2)
+ mEyeInclination = -PI / 2;
+ }
+
+ void MillSimulation::RotateEye(float rotStep)
+ {
+ mEyeRoration += rotStep;
+ if (mEyeRoration > PI2)
+ mEyeRoration = PI2;
+ else if (mEyeRoration < 0)
+ mEyeRoration = 0;
+ }
+
+ void MillSimulation::MoveEye(float x, float z)
+ {
+ mEyeX += x;
+ if (mEyeX > 100) mEyeX = 100;
+ else if (mEyeX < -100) mEyeX = -100;
+ mEyeZ += z;
+ if (mEyeZ > 100) mEyeZ = 100;
+ else if (mEyeZ < -100) mEyeZ = -100;
+ }
+
+ void MillSimulation::UpdateProjection()
+ {
+ // Setup projection
+ mat4x4 projmat;
+ mat4x4_perspective(projmat, 0.7f, 4.0f / 3.0f, 1.0f, mMaxFar);
+ //mat4x4_perspective(projmat, 0.7f, 4.0f / 3.0f, 1, 100);
+ shader3D.Activate();
+ shader3D.UpdateProjectionMat(projmat);
+ shaderInv3D.Activate();
+ shaderInv3D.UpdateProjectionMat(projmat);
+ shaderFlat.Activate();
+ shaderFlat.UpdateProjectionMat(projmat);
+ }
+
+ void MillSimulation::InitDisplay(float quality)
+ {
+ // gray background
+ glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
+
+
+ // use shaders
+ // standard diffuse shader
+ shader3D.CompileShader((char*)VertShader3DNorm, (char*)FragShaderNorm);
+ shader3D.UpdateEnvColor(lightPos, lightColor, ambientCol);
+
+ // invarted normal diffuse shader for inner mesh
+ shaderInv3D.CompileShader((char*)VertShader3DInvNorm, (char*)FragShaderNorm);
+ shaderInv3D.UpdateEnvColor(lightPos, lightColor, ambientCol);
+
+ // null shader to calculate meshes only (simulation stage)
+ shaderFlat.CompileShader((char*)VertShader3DNorm, (char*)FragShaderFlat);
+ UpdateProjection();
+
+ // setup light object and generate tools
+ mlightObject.GenerateBoxStock(-0.5f, -0.5f, -0.5f, 1, 1, 1);
+ for (int i = 0; i < mToolTable.size(); i++)
+ mToolTable[i]->GenerateDisplayLists(quality);
+
+ // init gui elements
+ guiDisplay.InutGui();
+
+ }
+
+ void MillSimulation::SetBoxStock(float x, float y, float z, float l, float w, float h)
+ {
+ mStockObject.GenerateBoxStock(x, y, z, l, w, h);
+ mMaxStockDim = fmaxf(w, l);
+ mMaxFar = mMaxStockDim * 4;
+ UpdateProjection();
+ vec3_set(eye, 0, 0, 0);
+ UpdateEyeFactor(0.4f);
+ vec3_set(lightPos, x, y, h + mMaxStockDim / 3);
+ mlightObject.SetPosition(lightPos);
+ }
+
+ void MillSimulation::MouseDrag(int buttons, int dx, int dy)
+ {
+ if (buttons == (MS_MOUSE_MID | MS_MOUSE_LEFT))
+ {
+ TiltEye((float)dy / 100.0f);
+ RotateEye((float)dx / 100.0f);
+ }
+ else if (buttons == MS_MOUSE_MID)
+ {
+ MoveEye(dx, -dy);
+ }
+ guiDisplay.MouseDrag(buttons, dx, dy);
+ }
+
+ void MillSimulation::MouseMove(int px, int py)
+ {
+ if (mMouseButtonState > 0)
+ {
+ int dx = px - mLastMouseX;
+ int dy = py - mLastMouseY;
+ if (dx != 0 || dy != 0)
+ {
+ MouseDrag(mMouseButtonState, dx, dy);
+ mLastMouseX = px;
+ mLastMouseY = py;
+ }
+ }
+ else
+ MouseHover(px, py);
+ }
+
+ void MillSimulation::MouseScroll(float dy)
+ {
+ float f = mEyeDistFactor;
+ f += 0.05f * dy;
+ if (f > 0.6f) f = 0.6f;
+ else if (f < 0.05f) f = 0.05f;
+ UpdateEyeFactor(f);
+ }
+
+
+ void MillSimulation::MouseHover(int px, int py)
+ {
+ guiDisplay.MouseCursorPos(px, py);
+ }
+
+ void MillSimulation::MousePress(int button, bool isPressed, int px, int py)
+ {
+ if (isPressed)
+ mMouseButtonState |= button;
+ else
+ mMouseButtonState &= ~button;
+
+ if (mMouseButtonState > 0)
+ {
+ mLastMouseX = px;
+ mLastMouseY = py;
+ }
+ guiDisplay.MousePressed(button, isPressed, mSimPlaying);
+ }
+
+
+ bool MillSimulation::LoadGCodeFile(const char* fileName)
+ {
+ if (mCodeParser.Parse(fileName))
+ {
+ std::cout << "GCode file loaded successfuly" << std::endl;
+ return true;
+ }
+ return false;
+ }
+
+ bool MillSimulation::AddGcodeLine(const char* line)
+ {
+ return mCodeParser.AddLine(line);
+ }
+
+ void MillSimulation::SetSimulationStage(float stage)
+ {
+ mCurStep = (int)((float)mNTotalSteps * stage);
+ CalcSegmentPositions();
+ }
+
+}
\ No newline at end of file
diff --git a/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.h b/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.h
new file mode 100644
index 0000000000..2744f97b7f
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/MillSimulation.h
@@ -0,0 +1,149 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __millsimulation__h__
+#define __millsimulation__h__
+
+#include "MillMotion.h"
+#include "GCodeParser.h"
+#include "Shader.h"
+#include "linmath.h"
+#include "GlUtils.h"
+#include "StockObject.h"
+#include "MillPathSegment.h"
+#include "GuiDisplay.h"
+#include
+#include
+
+namespace MillSim
+{
+
+class MillSimulation
+{
+public:
+ MillSimulation();
+ void ClearMillPathSegments();
+ void Clear();
+ void SimNext();
+ void InitSimulation(float quality);
+ void AddTool(EndMill* tool);
+ void AddTool(const std::vector& toolProfile, int toolid, float diameter);
+ bool ToolExists(int toolid)
+ {
+ return GetTool(toolid) != nullptr;
+ }
+ void Render();
+ void ProcessSim(unsigned int time_ms);
+ void HandleKeyPress(int key);
+ void UpdateEyeFactor(float factor);
+ void TiltEye(float tiltStep);
+ void RotateEye(float rotStep);
+ void MoveEye(float x, float y);
+ void UpdateProjection();
+ bool LoadGCodeFile(const char* fileName);
+ bool AddGcodeLine(const char* line);
+ void SetSimulationStage(float stage);
+ void SetBoxStock(float x, float y, float z, float l, float w, float h);
+ void MouseDrag(int buttons, int dx, int dy);
+ void MouseMove(int px, int py);
+ void MouseScroll(float dy);
+ void MouseHover(int px, int py);
+ void MousePress(int button, bool isPressed, int px, int py);
+
+
+protected:
+ void InitDisplay(float quality);
+ void GlsimStart();
+ void GlsimToolStep1(void);
+ void GlsimToolStep2(void);
+ void GlsimClipBack(void);
+ void GlsimRenderStock(void);
+ void GlsimRenderTools(void);
+ void GlsimEnd(void);
+ void renderSegmentForward(int iSeg);
+ void renderSegmentReversed(int iSeg);
+ void CalcSegmentPositions();
+ EndMill* GetTool(int tool);
+ void RemoveTool(int toolId);
+
+
+protected:
+ std::vector mToolTable;
+ Shader shader3D, shaderInv3D, shaderFlat;
+ GCodeParser mCodeParser;
+ GuiDisplay guiDisplay;
+ std::vector MillPathSegments;
+ std::ostringstream mFpsStream;
+
+ MillMotion mZeroPos = {eNop, -1, 0, 0, 100, 0, 0, 0};
+ MillMotion mCurMotion = {eNop, -1, 0, 0, 0, 0, 0, 0};
+ MillMotion mDestMotion = {eNop, -1, 0, 0, 0, 0, 0, 0};
+
+ StockObject mStockObject;
+ StockObject mlightObject;
+
+ vec3 lightColor = {0.8f, 0.9f, 1.0f};
+ vec3 lightPos = {20.0f, 20.0f, 10.0f};
+ vec3 ambientCol = {0.3f, 0.3f, 0.5f};
+
+ vec3 eye = {0, 100, 40};
+ vec3 target = {0, 0, -10};
+ vec3 upvec = {0, 0, 1};
+
+ vec3 stockColor = {0.7f, 0.7f, 0.7f};
+ vec3 cutColor = {0.4f, 0.7f, 0.4f};
+ vec3 toolColor = {0.4f, 0.4f, 0.7f};
+
+ float mEyeDistance = 30;
+ float mEyeRoration = 0;
+ float mEyeInclination = PI / 6; // 30 degree
+ float mEyeStep = PI / 36; // 5 degree
+
+ float mMaxStockDim = 100;
+ float mMaxFar = 100;
+ float mEyeDistFactor = 0.4f;
+ float mEyeXZFactor = 0.01f;
+ float mEyeXZScale = 0;
+ float mEyeX = 0.0f;
+ float mEyeZ = 0.0f;
+
+
+ int mCurStep = 0;
+ int mNTotalSteps = 0;
+ int mPathStep = 0;
+ int mSubStep = 0;
+ int mNPathSteps = 0;
+ int mDebug = 0;
+ int mDebug1 = 0;
+ int mDebug2 = 12;
+ int mSimSpeed = 1;
+
+ int mLastMouseX = 0, mLastMouseY = 0;
+ int mMouseButtonState = 0;
+
+ bool mIsInStock = false;
+ bool mIsRotate = true;
+ bool mSimPlaying = false;
+ bool mSingleStep = false;
+};
+} // namespace MillSim
+#endif
\ No newline at end of file
diff --git a/src/Mod/CAM/PathSimulator/AppGL/OpenGlWrapper.h b/src/Mod/CAM/PathSimulator/AppGL/OpenGlWrapper.h
new file mode 100644
index 0000000000..11c144fb6b
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/OpenGlWrapper.h
@@ -0,0 +1,71 @@
+/***************************************************************************
+ * Copyright (c) 2024 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef __openglwrapper_h__
+#define __openglwrapper_h__
+#ifdef CAM_SIM_USE_GLEW
+#include "GL/glew.h"
+#else
+#include "DlgCAMSimulator.h"
+extern QOpenGLContext* gOpenGlContext;
+#define gSimWindow CAMSimulator::DlgCAMSimulator::GetInstance()
+#define glGenBuffers gSimWindow->glGenBuffers
+#define glBindBuffer gSimWindow->glBindBuffer
+#define glBufferData gSimWindow->glBufferData
+#define glGenVertexArrays gSimWindow->glGenVertexArrays
+#define glBindVertexArray gSimWindow->glBindVertexArray
+#define glEnableVertexAttribArray gSimWindow->glEnableVertexAttribArray
+#define glVertexAttribPointer gSimWindow->glVertexAttribPointer
+#define glShaderSource gSimWindow->glShaderSource
+#define glCompileShader gSimWindow->glCompileShader
+#define glAttachShader gSimWindow->glAttachShader
+#define glLinkProgram gSimWindow->glLinkProgram
+#define glGetProgramiv gSimWindow->glGetProgramiv
+#define glGetUniformLocation gSimWindow->glGetUniformLocation
+#define glGetError gSimWindow->glGetError
+#define glEnable gSimWindow->glEnable
+#define glColorMask gSimWindow->glColorMask
+#define glCullFace gSimWindow->glCullFace
+#define glDepthFunc gSimWindow->glDepthFunc
+#define glStencilFunc gSimWindow->glStencilFunc
+#define glStencilOp gSimWindow->glStencilOp
+#define glDepthMask gSimWindow->glDepthMask
+#define glDisable gSimWindow->glDisable
+#define glMatrixMode gSimWindow->glMatrixMode
+#define glUseProgram gSimWindow->glUseProgram
+#define glDrawElements gSimWindow->glDrawElements
+#define glDeleteVertexArrays gSimWindow->glDeleteVertexArrays
+#define glUniformMatrix4fv gSimWindow->glUniformMatrix4fv
+#define glUniform3fv gSimWindow->glUniform3fv
+#define glUniform1i gSimWindow->glUniform1i
+#define glCreateShader gSimWindow->glCreateShader
+#define glCreateProgram gSimWindow->glCreateProgram
+#define glDeleteBuffers gSimWindow->glDeleteBuffers
+#define glActiveTexture gSimWindow->glActiveTexture
+#define glBindTexture gSimWindow->glBindTexture
+#define glGenTextures gSimWindow->glGenTextures
+#define glTexParameteri gSimWindow->glTexParameteri
+#define glTexImage2D gSimWindow->glTexImage2D
+#define glDeleteTextures gSimWindow->glDeleteTextures
+#endif // HAVE_OPENGL_EXT
+
+#endif // !__openglwrapper_h__
diff --git a/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.cpp b/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.cpp
new file mode 100644
index 0000000000..9dc04433fe
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.cpp
@@ -0,0 +1,23 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#include "PreCompiled.h"
diff --git a/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.h b/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.h
new file mode 100644
index 0000000000..14ea3ae0a3
--- /dev/null
+++ b/src/Mod/CAM/PathSimulator/AppGL/PreCompiled.h
@@ -0,0 +1,56 @@
+/***************************************************************************
+ * Copyright (c) 2017 Shai Seger *
+ * *
+ * This file is part of the FreeCAD CAx development system. *
+ * *
+ * This library is free software; you can redistribute it and/or *
+ * modify it under the terms of the GNU Library General Public *
+ * License as published by the Free Software Foundation; either *
+ * version 2 of the License, or (at your option) any later version. *
+ * *
+ * This library 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 library; see the file COPYING.LIB. If not, *
+ * write to the Free Software Foundation, Inc., 59 Temple Place, *
+ * Suite 330, Boston, MA 02111-1307, USA *
+ * *
+ ***************************************************************************/
+
+#ifndef APP_PRECOMPILED_H
+#define APP_PRECOMPILED_H
+
+#include
+
+#ifdef _PreComp_
+
+// standard
+#include
+#include
+#include
+
+// STL
+#include
+#include
+#include
+#include