Files
create/src/Mod/CAM/PathSimulator/AppGL/DlgCAMSimulator.cpp
2026-02-07 11:03:23 +01:00

378 lines
10 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/***************************************************************************
* Copyright (c) 2024 Shai Seger <shaise at gmail> *
* *
* 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 "DlgCAMSimulator.h"
#include "ViewCAMSimulator.h"
#include "MillSimulation.h"
#include "Gui/View3DInventorViewer.h"
#include <Mod/Part/App/BRepMesh.h>
#include <QDateTime>
#include <QSurfaceFormat>
#include <QPoint>
#include <QTimerEvent>
#include <App/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/MDIView.h>
#include <QHBoxLayout>
#include <QPointer>
using namespace MillSim;
namespace CAMSimulator
{
static const float MouseScrollDelta = 120.0F;
static QPointer<ViewCAMSimulator> viewCAMSimulator;
DlgCAMSimulator::DlgCAMSimulator(ViewCAMSimulator& view, QWidget* parent)
: QOpenGLWidget(parent)
, mView(view)
{
// Under certain conditions, e.g. when docking/undocking the cam simulator, we need to create a
// new widget (due to some OpenGL bug). The new widget becomes THE cam simulator.
viewCAMSimulator = &view;
QSurfaceFormat format;
format.setVersion(4, 1); // Request OpenGL 4.1 - for MacOS
format.setProfile(QSurfaceFormat::CoreProfile); // Use the core profile = for MacOS
int samples = Gui::View3DInventorViewer::getNumSamples();
if (samples > 1) {
format.setSamples(samples);
}
format.setSwapInterval(2);
format.setDepthBufferSize(24);
format.setStencilBufferSize(8);
setFormat(format);
setMouseTracking(true);
mMillSimulator.reset(new MillSimulation);
mAnimatingTimer.setInterval(0);
connect(
&mAnimatingTimer,
&QTimer::timeout,
this,
static_cast<void (DlgCAMSimulator::*)()>(&DlgCAMSimulator::update)
);
}
DlgCAMSimulator::~DlgCAMSimulator()
{
makeCurrent();
mMillSimulator = nullptr;
}
void DlgCAMSimulator::cloneFrom(const DlgCAMSimulator& from)
{
mNeedsInitialize = true;
mNeedsClear = true;
setAnimating(from.mAnimating);
mQuality = from.mQuality;
mGCode = from.mGCode;
mTools = from.mTools;
mStock = from.mStock;
mStock.needsUpdate = true;
mBase = from.mBase;
mBase.needsUpdate = true;
const auto state = from.mMillSimulator->GetState();
mState = std::make_unique<MillSim::MillSimulationState>(state);
}
DlgCAMSimulator* DlgCAMSimulator::instance()
{
if (!viewCAMSimulator) {
auto view = new ViewCAMSimulator(nullptr, nullptr);
viewCAMSimulator = view;
Gui::getMainWindow()->addWindow(view);
}
return &viewCAMSimulator->dlg();
}
void DlgCAMSimulator::setAnimating(bool animating)
{
if (animating == mAnimating) {
return;
}
mAnimating = animating;
if (animating) {
mAnimatingTimer.start();
}
else {
mAnimatingTimer.stop();
}
}
void DlgCAMSimulator::startSimulation(const Part::TopoShape& stock, float quality)
{
App::Document* doc = App::GetApplication().getActiveDocument();
mView.setWindowTitle(tr("%1 - New CAM Simulator").arg(QString::fromUtf8(doc->getName())));
mQuality = quality;
mNeedsInitialize = true;
setStockShape(stock, 1);
setAnimating(true);
Gui::getMainWindow()->setActiveWindow(&mView);
mView.show();
}
void DlgCAMSimulator::resetSimulation()
{
mNeedsClear = true;
mGCode.clear();
mTools.clear();
mStock = {};
mBase = {};
}
void DlgCAMSimulator::addGcodeCommand(const char* cmd)
{
mGCode.push_back(cmd);
}
void DlgCAMSimulator::addTool(
const std::vector<float>& toolProfilePoints,
int toolNumber,
float diameter,
float resolution
)
{
Q_UNUSED(resolution)
std::string toolCmd = "T" + std::to_string(toolNumber);
addGcodeCommand(toolCmd.c_str());
mTools.emplace_back(toolProfilePoints, toolNumber, diameter);
}
static SimShape getMeshData(const Part::TopoShape& tshape, float resolution)
{
SimShape ret;
std::vector<int> normalCount;
int nVerts = 0;
for (auto& shape : tshape.getSubTopoShapes(TopAbs_FACE)) {
std::vector<Base::Vector3d> points;
std::vector<Data::ComplexGeoData::Facet> facets;
shape.getFaces(points, facets, resolution);
std::vector<Base::Vector3d> normals(points.size());
std::vector<int> normalCount(points.size());
// copy triangle indices and calculate normals
for (auto face : facets) {
ret.indices.push_back(face.I1 + nVerts);
ret.indices.push_back(face.I2 + nVerts);
ret.indices.push_back(face.I3 + nVerts);
// calculate normal
Base::Vector3d vAB = points[face.I2] - points[face.I1];
Base::Vector3d vAC = points[face.I3] - points[face.I1];
Base::Vector3d vNorm = vAB.Cross(vAC).Normalize();
normals[face.I1] += vNorm;
normals[face.I2] += vNorm;
normals[face.I3] += vNorm;
normalCount[face.I1]++;
normalCount[face.I2]++;
normalCount[face.I3]++;
}
// copy points and set normals
for (unsigned int i = 0; i < points.size(); i++) {
Base::Vector3d& point = points[i];
Base::Vector3d& normal = normals[i];
int count = normalCount[i];
normal /= count;
ret.verts.push_back(Vertex(point.x, point.y, point.z, normal.x, normal.y, normal.z));
}
nVerts = ret.verts.size();
}
ret.needsUpdate = true;
return ret;
}
void DlgCAMSimulator::setStockShape(const Part::TopoShape& shape, float resolution)
{
mStock = getMeshData(shape, resolution);
}
void DlgCAMSimulator::setBaseShape(const Part::TopoShape& tshape, float resolution)
{
mBase = getMeshData(tshape, resolution);
}
void DlgCAMSimulator::mouseMoveEvent(QMouseEvent* ev)
{
int modifiers = (ev->modifiers() & Qt::ShiftModifier) != 0 ? MS_KBD_SHIFT : 0;
modifiers |= (ev->modifiers() & Qt::ControlModifier) != 0 ? MS_KBD_CONTROL : 0;
modifiers |= (ev->modifiers() & Qt::AltModifier) != 0 ? MS_KBD_ALT : 0;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QPoint pnt = ev->pos();
#else
QPoint pnt = ev->position().toPoint();
#endif
const qreal ratio = devicePixelRatioF();
mMillSimulator->MouseMove(pnt.x() * ratio, pnt.y() * ratio, modifiers);
update();
}
void DlgCAMSimulator::mousePressEvent(QMouseEvent* ev)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QPoint pnt = ev->pos();
#else
QPoint pnt = ev->position().toPoint();
#endif
const qreal ratio = devicePixelRatioF();
mMillSimulator->MousePress(ev->button(), true, pnt.x() * ratio, pnt.y() * ratio);
update();
}
void DlgCAMSimulator::mouseReleaseEvent(QMouseEvent* ev)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QPoint pnt = ev->pos();
#else
QPoint pnt = ev->position().toPoint();
#endif
const qreal ratio = devicePixelRatioF();
mMillSimulator->MousePress(ev->button(), false, pnt.x() * ratio, pnt.y() * ratio);
update();
}
void DlgCAMSimulator::wheelEvent(QWheelEvent* ev)
{
mMillSimulator->MouseScroll((float)ev->angleDelta().y() / MouseScrollDelta);
update();
}
void DlgCAMSimulator::updateResources()
{
// clear simulator
if (mNeedsClear) {
mMillSimulator->Clear();
mLastGCode = 0;
mNeedsClear = false;
}
// update gcode
for (int i = mLastGCode; i < (int)mGCode.size(); i++) {
const std::string& cmd = mGCode[i];
mMillSimulator->AddGcodeLine(cmd.c_str());
}
mLastGCode = mGCode.size();
// update tools
for (const auto& tool : mTools) {
if (!mMillSimulator->ToolExists(tool.id)) {
mMillSimulator->AddTool(tool.profile, tool.id, tool.diameter);
}
}
// initialize simulator
if (mNeedsInitialize) {
mMillSimulator->InitSimulation(mQuality);
mNeedsInitialize = false;
}
// update stock and base
if (mStock.needsUpdate) {
mMillSimulator->SetArbitraryStock(mStock.verts, mStock.indices);
mStock.needsUpdate = false;
}
if (mBase.needsUpdate) {
mMillSimulator->SetBaseObject(mBase.verts, mBase.indices);
mBase.needsUpdate = false;
}
// update state
if (mState) {
mMillSimulator->SetState(*mState);
mState = nullptr;
}
}
void DlgCAMSimulator::updateWindowScale()
{
const qreal ratio = devicePixelRatioF();
mMillSimulator->UpdateWindowScale(width() * ratio, height() * ratio);
}
void DlgCAMSimulator::initializeGL()
{
initializeOpenGLFunctions();
}
void DlgCAMSimulator::paintGL()
{
updateResources();
// We need to call updateWindowScale on every render since the devicePixelRatio we get in
// resizeGL might be wrong on the first resize.
updateWindowScale();
mMillSimulator->ProcessSim((unsigned int)(QDateTime::currentMSecsSinceEpoch()));
}
void DlgCAMSimulator::resizeGL(int w, int h)
{
(void)w, (void)h;
}
} // namespace CAMSimulator