feat: Implement feature graph and JSON document save/load
Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
@@ -16,6 +16,9 @@ add_executable(OpenCAD
|
|||||||
src/ViewportWidget.cpp
|
src/ViewportWidget.cpp
|
||||||
src/ViewCube.cpp
|
src/ViewCube.cpp
|
||||||
src/SketchGrid.cpp
|
src/SketchGrid.cpp
|
||||||
|
src/Document.cpp
|
||||||
|
src/Feature.cpp
|
||||||
|
src/SketchFeature.cpp
|
||||||
resources.qrc
|
resources.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
90
src/Document.cpp
Normal file
90
src/Document.cpp
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
#include "Document.h"
|
||||||
|
#include "Feature.h"
|
||||||
|
#include "SketchFeature.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
Document::Document(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
Document::~Document()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Document::addFeature(Feature* feature)
|
||||||
|
{
|
||||||
|
m_features.append(feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Document::clear()
|
||||||
|
{
|
||||||
|
qDeleteAll(m_features);
|
||||||
|
m_features.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Document::save(const QString& path) const
|
||||||
|
{
|
||||||
|
QJsonArray featuresArray;
|
||||||
|
for (const Feature* feature : m_features) {
|
||||||
|
QJsonObject featureObject;
|
||||||
|
feature->write(featureObject);
|
||||||
|
featuresArray.append(featureObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject rootObject;
|
||||||
|
rootObject["features"] = featuresArray;
|
||||||
|
|
||||||
|
QJsonDocument doc(rootObject);
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::WriteOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
file.write(doc.toJson());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Document::load(const QString& path)
|
||||||
|
{
|
||||||
|
QFile file(path);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray data = file.readAll();
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||||
|
|
||||||
|
if (doc.isNull() || !doc.isObject()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
clear();
|
||||||
|
|
||||||
|
QJsonObject rootObject = doc.object();
|
||||||
|
if (rootObject.contains("features") && rootObject["features"].isArray()) {
|
||||||
|
QJsonArray featuresArray = rootObject["features"].toArray();
|
||||||
|
for (const QJsonValue& value : featuresArray) {
|
||||||
|
QJsonObject obj = value.toObject();
|
||||||
|
if (obj.contains("type") && obj["type"].isString()) {
|
||||||
|
QString type = obj["type"].toString();
|
||||||
|
Feature* feature = nullptr;
|
||||||
|
|
||||||
|
if (type == "Sketch") {
|
||||||
|
feature = new SketchFeature("");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (feature) {
|
||||||
|
feature->read(obj);
|
||||||
|
addFeature(feature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
26
src/Document.h
Normal file
26
src/Document.h
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#ifndef DOCUMENT_H
|
||||||
|
#define DOCUMENT_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
class Feature;
|
||||||
|
|
||||||
|
class Document : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit Document(QObject *parent = nullptr);
|
||||||
|
~Document();
|
||||||
|
|
||||||
|
void addFeature(Feature* feature);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
bool save(const QString& path) const;
|
||||||
|
bool load(const QString& path);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<Feature*> m_features;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DOCUMENT_H
|
||||||
28
src/Feature.cpp
Normal file
28
src/Feature.cpp
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
#include "Feature.h"
|
||||||
|
|
||||||
|
Feature::Feature(const QString& name) : m_name(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Feature::name() const
|
||||||
|
{
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Feature::setName(const QString& name)
|
||||||
|
{
|
||||||
|
m_name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Feature::read(const QJsonObject& json)
|
||||||
|
{
|
||||||
|
if (json.contains("name") && json["name"].isString()) {
|
||||||
|
m_name = json["name"].toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Feature::write(QJsonObject& json) const
|
||||||
|
{
|
||||||
|
json["type"] = type();
|
||||||
|
json["name"] = m_name;
|
||||||
|
}
|
||||||
25
src/Feature.h
Normal file
25
src/Feature.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
#ifndef FEATURE_H
|
||||||
|
#define FEATURE_H
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
class Feature
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Feature(const QString& name);
|
||||||
|
virtual ~Feature() = default;
|
||||||
|
|
||||||
|
QString name() const;
|
||||||
|
void setName(const QString& name);
|
||||||
|
|
||||||
|
virtual QString type() const = 0;
|
||||||
|
|
||||||
|
virtual void read(const QJsonObject &json);
|
||||||
|
virtual void write(QJsonObject &json) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FEATURE_H
|
||||||
@@ -1,6 +1,12 @@
|
|||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "ViewportWidget.h"
|
#include "ViewportWidget.h"
|
||||||
|
#include "Document.h"
|
||||||
|
#include "SketchFeature.h"
|
||||||
|
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QMessageBox>
|
||||||
#include <QToolBar>
|
#include <QToolBar>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
@@ -16,6 +22,20 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
setWindowTitle("OpenCAD");
|
setWindowTitle("OpenCAD");
|
||||||
resize(1280, 720);
|
resize(1280, 720);
|
||||||
|
|
||||||
|
QMenu *fileMenu = menuBar()->addMenu("&File");
|
||||||
|
QAction *newAction = fileMenu->addAction("&New");
|
||||||
|
connect(newAction, &QAction::triggered, this, &MainWindow::newFile);
|
||||||
|
|
||||||
|
QAction *openAction = fileMenu->addAction("&Open...");
|
||||||
|
connect(openAction, &QAction::triggered, this, &MainWindow::open);
|
||||||
|
|
||||||
|
QAction *saveAction = fileMenu->addAction("&Save");
|
||||||
|
connect(saveAction, &QAction::triggered, this, &MainWindow::save);
|
||||||
|
|
||||||
|
QAction *saveAsAction = fileMenu->addAction("Save &As...");
|
||||||
|
connect(saveAsAction, &QAction::triggered, this, &MainWindow::saveAs);
|
||||||
|
|
||||||
|
|
||||||
QToolBar* mainToolBar = addToolBar("Main Toolbar");
|
QToolBar* mainToolBar = addToolBar("Main Toolbar");
|
||||||
mainToolBar->setMovable(false);
|
mainToolBar->setMovable(false);
|
||||||
mainToolBar->setFloatable(false);
|
mainToolBar->setFloatable(false);
|
||||||
@@ -58,6 +78,9 @@ MainWindow::MainWindow(QWidget *parent)
|
|||||||
|
|
||||||
m_viewport = new ViewportWidget;
|
m_viewport = new ViewportWidget;
|
||||||
setCentralWidget(m_viewport);
|
setCentralWidget(m_viewport);
|
||||||
|
|
||||||
|
m_document = new Document(this);
|
||||||
|
setCurrentFile(QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createSketch()
|
void MainWindow::createSketch()
|
||||||
@@ -69,12 +92,75 @@ void MainWindow::createSketch()
|
|||||||
QString item = QInputDialog::getItem(this, "Select Sketch Plane",
|
QString item = QInputDialog::getItem(this, "Select Sketch Plane",
|
||||||
"Plane:", items, 0, false, &ok);
|
"Plane:", items, 0, false, &ok);
|
||||||
if (ok && !item.isEmpty()) {
|
if (ok && !item.isEmpty()) {
|
||||||
|
auto feature = new SketchFeature("Sketch");
|
||||||
if (item == "XY-Plane") {
|
if (item == "XY-Plane") {
|
||||||
m_viewport->startSketch(ViewportWidget::SketchPlane::XY);
|
m_viewport->startSketch(ViewportWidget::SketchPlane::XY);
|
||||||
|
feature->setPlane(SketchFeature::SketchPlane::XY);
|
||||||
} else if (item == "XZ-Plane") {
|
} else if (item == "XZ-Plane") {
|
||||||
m_viewport->startSketch(ViewportWidget::SketchPlane::XZ);
|
m_viewport->startSketch(ViewportWidget::SketchPlane::XZ);
|
||||||
|
feature->setPlane(SketchFeature::SketchPlane::XZ);
|
||||||
} else if (item == "YZ-Plane") {
|
} else if (item == "YZ-Plane") {
|
||||||
m_viewport->startSketch(ViewportWidget::SketchPlane::YZ);
|
m_viewport->startSketch(ViewportWidget::SketchPlane::YZ);
|
||||||
|
feature->setPlane(SketchFeature::SketchPlane::YZ);
|
||||||
}
|
}
|
||||||
|
m_document->addFeature(feature);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::newFile()
|
||||||
|
{
|
||||||
|
m_document->clear();
|
||||||
|
setCurrentFile(QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::open()
|
||||||
|
{
|
||||||
|
const QString fileName = QFileDialog::getOpenFileName(this);
|
||||||
|
if (!fileName.isEmpty()) {
|
||||||
|
if (!m_document->load(fileName)) {
|
||||||
|
QMessageBox::warning(this, tr("OpenCAD"),
|
||||||
|
tr("Cannot read file %1").arg(QDir::toNativeSeparators(fileName)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setCurrentFile(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::save()
|
||||||
|
{
|
||||||
|
if (m_currentFile.isEmpty()) {
|
||||||
|
return saveAs();
|
||||||
|
} else {
|
||||||
|
if (!m_document->save(m_currentFile)) {
|
||||||
|
QMessageBox::warning(this, tr("OpenCAD"),
|
||||||
|
tr("Cannot write file %1").arg(QDir::toNativeSeparators(m_currentFile)));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MainWindow::saveAs()
|
||||||
|
{
|
||||||
|
QFileDialog dialog(this);
|
||||||
|
dialog.setWindowModality(Qt::WindowModal);
|
||||||
|
dialog.setAcceptMode(QFileDialog::AcceptSave);
|
||||||
|
if (dialog.exec() != QDialog::Accepted)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const QString fileName = dialog.selectedFiles().first();
|
||||||
|
setCurrentFile(fileName);
|
||||||
|
return save();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void MainWindow::setCurrentFile(const QString &fileName)
|
||||||
|
{
|
||||||
|
m_currentFile = fileName;
|
||||||
|
setWindowFilePath(m_currentFile);
|
||||||
|
|
||||||
|
QString shownName = m_currentFile;
|
||||||
|
if (m_currentFile.isEmpty())
|
||||||
|
shownName = "untitled.json";
|
||||||
|
setWindowTitle(tr("%1[*] - %2").arg(QFileInfo(shownName).fileName(), tr("OpenCAD")));
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
||||||
class ViewportWidget;
|
class ViewportWidget;
|
||||||
|
class Document;
|
||||||
|
|
||||||
class MainWindow : public QMainWindow
|
class MainWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
@@ -13,10 +14,18 @@ public:
|
|||||||
explicit MainWindow(QWidget *parent = nullptr);
|
explicit MainWindow(QWidget *parent = nullptr);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
|
void newFile();
|
||||||
|
void open();
|
||||||
|
bool save();
|
||||||
|
bool saveAs();
|
||||||
void createSketch();
|
void createSketch();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void setCurrentFile(const QString &fileName);
|
||||||
|
|
||||||
ViewportWidget *m_viewport;
|
ViewportWidget *m_viewport;
|
||||||
|
Document *m_document;
|
||||||
|
QString m_currentFile;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
|||||||
44
src/SketchFeature.cpp
Normal file
44
src/SketchFeature.cpp
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#include "SketchFeature.h"
|
||||||
|
|
||||||
|
SketchFeature::SketchFeature(const QString& name)
|
||||||
|
: Feature(name)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString SketchFeature::type() const
|
||||||
|
{
|
||||||
|
return "Sketch";
|
||||||
|
}
|
||||||
|
|
||||||
|
void SketchFeature::setPlane(SketchPlane plane)
|
||||||
|
{
|
||||||
|
m_plane = plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
SketchFeature::SketchPlane SketchFeature::plane() const
|
||||||
|
{
|
||||||
|
return m_plane;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SketchFeature::read(const QJsonObject& json)
|
||||||
|
{
|
||||||
|
Feature::read(json);
|
||||||
|
if (json.contains("plane") && json["plane"].isString()) {
|
||||||
|
QString planeStr = json["plane"].toString();
|
||||||
|
if (planeStr == "XY") m_plane = SketchPlane::XY;
|
||||||
|
else if (planeStr == "XZ") m_plane = SketchPlane::XZ;
|
||||||
|
else if (planeStr == "YZ") m_plane = SketchPlane::YZ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SketchFeature::write(QJsonObject& json) const
|
||||||
|
{
|
||||||
|
Feature::write(json);
|
||||||
|
QString planeStr;
|
||||||
|
switch (m_plane) {
|
||||||
|
case SketchPlane::XY: planeStr = "XY"; break;
|
||||||
|
case SketchPlane::XZ: planeStr = "XZ"; break;
|
||||||
|
case SketchPlane::YZ: planeStr = "YZ"; break;
|
||||||
|
}
|
||||||
|
json["plane"] = planeStr;
|
||||||
|
}
|
||||||
29
src/SketchFeature.h
Normal file
29
src/SketchFeature.h
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
#ifndef SKETCHFEATURE_H
|
||||||
|
#define SKETCHFEATURE_H
|
||||||
|
|
||||||
|
#include "Feature.h"
|
||||||
|
|
||||||
|
class SketchFeature : public Feature
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
enum class SketchPlane {
|
||||||
|
XY,
|
||||||
|
XZ,
|
||||||
|
YZ
|
||||||
|
};
|
||||||
|
|
||||||
|
SketchFeature(const QString& name);
|
||||||
|
|
||||||
|
QString type() const override;
|
||||||
|
|
||||||
|
void setPlane(SketchPlane plane);
|
||||||
|
SketchPlane plane() const;
|
||||||
|
|
||||||
|
void read(const QJsonObject &json) override;
|
||||||
|
void write(QJsonObject &json) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SketchPlane m_plane;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SKETCHFEATURE_H
|
||||||
Reference in New Issue
Block a user