// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * 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 #define DRAG_ZOOM_FACTOR 10 namespace MillSim { MillSimulation::MillSimulation() { mCurMotion = {eNop, -1, 0, 0, 0, 0, 0, 0, 0, '\0', 0.0}; guiDisplay.SetMillSimulator(this); } MillSimulation::~MillSimulation() { Clear(); } void MillSimulation::ClearMillPathSegments() { for (unsigned int i = 0; i < MillPathSegments.size(); i++) { delete MillPathSegments[i]; } MillPathSegments.clear(); } void MillSimulation::Clear() { mCodeParser.Operations.clear(); for (unsigned int i = 0; i < mToolTable.size(); i++) { delete mToolTable[i]; } ClearMillPathSegments(); mStockObject.~StockObject(); mToolTable.clear(); guiDisplay.ResetGui(); simDisplay.CleanGL(); 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; if (mCurStep > mNTotalSteps) { mCurStep = mNTotalSteps; } CalcSegmentPositions(); simDisplay.updateDisplay = true; } } void MillSimulation::InitSimulation(float quality, qreal devicePixelRatio) { ClearMillPathSegments(); millPathLine.Clear(); simDisplay.applySSAO = guiDisplay.IsChecked(eGuiItemAmbientOclusion); mDestMotion = mZeroPos; // gDestPos = curMillOperation->startPos; mCurStep = 0; mPathStep = -1; mNTotalSteps = 0; mSimPlaying = false; mSimSpeed = 1; MillPathSegment::SetQuality(quality, simDisplay.maxFar); int nOperations = (int)mCodeParser.Operations.size(); int segId = 0; 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; segment->segmentIndex = segId++; mNTotalSteps += segment->numSimSteps; MillPathSegments.push_back(segment); segment->AppendPathPoints(millPathLine.MillPathPointsBuffer); } } mNPathSteps = (int)MillPathSegments.size(); millPathLine.GenerateModel(); InitDisplay(quality, devicePixelRatio); } EndMill* MillSimulation::GetTool(int toolId) { for (unsigned int i = 0; i < mToolTable.size(); i++) { if (mToolTable[i]->toolId == toolId) { return mToolTable[i]; } } return nullptr; } void MillSimulation::RemoveTool(const int toolId) { EndMill* tool = GetTool(toolId); if (tool == nullptr) { return; } if (const auto it = std::ranges::find(mToolTable, tool); 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() { glDisable(GL_BLEND); 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::RenderSimulation() { if ((mViewItems & VIEWITEM_SIMULATION) == 0) { return; } simDisplay.StartDepthPass(); 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 simDisplay.StartGeometryPass(stockColor, false); GlsimRenderStock(); mStockObject.render(); // render cuts (back faces of tools) simDisplay.StartGeometryPass(cutColor, true); GlsimRenderTools(); 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(); } void MillSimulation::RenderTool() { if (mPathStep < 0) { return; } 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); simDisplay.StartGeometryPass(toolColor, false); p->endmill->toolShape.Render(tmat, identityMat); } void MillSimulation::RenderPath() { if (!guiDisplay.IsChecked(eGuiItemPath)) { return; } simDisplay.SetupLinePathPass(mPathStep, false); millPathLine.Render(); simDisplay.SetupLinePathPass(mPathStep, true); millPathLine.Render(); glDepthMask(GL_TRUE); } void MillSimulation::RenderBaseShape() { if ((mViewItems & VIEWITEM_BASE_SHAPE) == 0) { return; } simDisplay.StartDepthPass(); glPolygonOffset(0, -2); glEnable(GL_POLYGON_OFFSET_FILL); simDisplay.StartGeometryPass(baseShapeColor, false); mBaseShape.render(); glDisable(GL_POLYGON_OFFSET_FILL); } void MillSimulation::Render() { // set background glClearColor(bgndColor[0], bgndColor[1], bgndColor[2], 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); simDisplay.PrepareDisplay(mStockObject.center); // render the simulation offscreen in an FBO if (simDisplay.updateDisplay) { simDisplay.PrepareFrameBuffer(); RenderSimulation(); RenderTool(); RenderBaseShape(); RenderPath(); simDisplay.updateDisplay = false; simDisplay.RenderResult(true); } else { simDisplay.RenderResult(false); } /* if (mDebug > 0) { mat4x4 test; mat4x4_identity(test); 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; glBindFramebuffer(GL_FRAMEBUFFER, 0); guiDisplay.Render(progress); } void MillSimulation::ProcessSim(unsigned int time_ms) { static int ancient = 0; static unsigned int last = 0; static unsigned int msec = 0xFFFFFFFF; static int fps = 0; static int renderTime = 0; last = msec == 0xFFFFFFFF ? time_ms : msec; msec = time_ms; if (guiDisplay.IsChecked(eGuiItemRotate)) { simDisplay.RotateEye((msec - last) / 4600.0f); } 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) { if (key >= '1' && key <= '9') { mSimSpeed = key - '0'; } else if (key == 'D') { mDebug++; } else if (key == 'K') { mDebug2++; gDebug = mNPathSteps - mDebug2; } else { guiDisplay.HandleKeyPress(key); } } void MillSimulation::HandleGuiAction(eGuiItems actionItem, bool checked) { switch (actionItem) { case eGuiItemPlay: mSimPlaying = true; break; case eGuiItemPause: mSimPlaying = false; break; case eGuiItemSingleStep: mSimPlaying = false; mSingleStep = true; break; case eGuiItemSlower: if (mSimSpeed == 50) { mSimSpeed = 25; } else if (mSimSpeed == 25) { mSimSpeed = 10; } else if (mSimSpeed == 10) { mSimSpeed = 5; } else { mSimSpeed = 1; } guiDisplay.UpdateSimSpeed(mSimSpeed); break; case eGuiItemFaster: if (mSimSpeed == 1) { mSimSpeed = 5; } else if (mSimSpeed == 5) { mSimSpeed = 10; } else if (mSimSpeed == 10) { mSimSpeed = 25; } else { mSimSpeed = 50; } guiDisplay.UpdateSimSpeed(mSimSpeed); break; case eGuiItemPath: simDisplay.updateDisplay = true; break; case eGuiItemAmbientOclusion: simDisplay.applySSAO = checked; simDisplay.updateDisplay = true; break; case eGuiItemView: mViewItems++; if (mViewItems >= VIEWITEM_MAX) { mViewItems = VIEWITEM_SIMULATION; } simDisplay.updateDisplay = true; break; case eGuiItemHome: simDisplay.MoveEyeCenter(); break; default: break; } guiDisplay.UpdatePlayState(mSimPlaying); } void MillSimulation::InitDisplay(float quality, qreal devicePixelRatio) { // generate tools for (unsigned int i = 0; i < mToolTable.size(); i++) { mToolTable[i]->GenerateDisplayLists(quality); } // init 3d display simDisplay.InitGL(devicePixelRatio); // init gui elements guiDisplay.InitGui(); } void MillSimulation::SetBoxStock(float x, float y, float z, float l, float w, float h) { mStockObject.GenerateBoxStock(x, y, z, l, w, h); simDisplay.ScaleViewToStock(&mStockObject); } void MillSimulation::SetArbitraryStock(std::vector& verts, std::vector& indices) { mStockObject.GenerateSolid(verts, indices); simDisplay.ScaleViewToStock(&mStockObject); } void MillSimulation::SetBaseObject(std::vector& verts, std::vector& indices) { mBaseShape.GenerateSolid(verts, indices); } void MillSimulation::MouseDrag(int buttons, int dx, int dy) { if (buttons == (MS_MOUSE_MID | MS_MOUSE_LEFT) || buttons == MS_KBD_ALT) { simDisplay.TiltEye((float)dy / 100.0f); simDisplay.RotateEye((float)dx / 100.0f); } else if (buttons == MS_MOUSE_MID || buttons == MS_KBD_SHIFT) { simDisplay.MoveEye(dx, -dy); } else if (buttons == (MS_KBD_CONTROL | MS_KBD_SHIFT)) { Zoom(0.003 * dy); } guiDisplay.MouseDrag(buttons, dx, dy); } void MillSimulation::MouseMove(int px, int py, int modifiers) { if (modifiers != mLastModifiers) { mLastMouseX = px; mLastMouseY = py; mLastModifiers = modifiers; } int buttons = mMouseButtonState | modifiers; if (buttons > 0) { int dx = px - mLastMouseX; int dy = py - mLastMouseY; if (dx != 0 || dy != 0) { MouseDrag(buttons, dx, dy); mLastMouseX = px; mLastMouseY = py; } } else { MouseHover(px, py); } } void MillSimulation::MouseScroll(float dy) { Zoom(-0.02f * dy); } 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); } void MillSimulation::Zoom(float factor) { factor += simDisplay.GetEyeFactor(); if (factor > 0.6f) { factor = 0.6f; } else if (factor < 0.01f) { factor = 0.01f; } simDisplay.UpdateEyeFactor(factor); } void MillSimulation::UpdateWindowScale(int width, int height) { if (width == gWindowSizeW && height == gWindowSizeH) { return; } gWindowSizeW = width; gWindowSizeH = height; simDisplay.UpdateWindowScale(); guiDisplay.UpdateWindowScale(); simDisplay.updateDisplay = true; } bool MillSimulation::LoadGCodeFile(const char* fileName) { if (mCodeParser.Parse(fileName)) { std::cout << "GCode file loaded successfully" << std::endl; return true; } return false; } bool MillSimulation::AddGcodeLine(const char* line) { return mCodeParser.AddLine(line); } void MillSimulation::SetSimulationStage(float stage) { int newStep = (int)((float)mNTotalSteps * stage); if (newStep == mCurStep) { return; } mCurStep = newStep; simDisplay.updateDisplay = true; mSingleStep = true; CalcSegmentPositions(); } } // namespace MillSim