diff --git a/CMakeLists.txt b/CMakeLists.txt index bd058dc..15a4fbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,9 @@ add_executable(OpenCAD src/ViewportWidget.cpp src/ViewCube.cpp src/SketchGrid.cpp + src/Document.cpp + src/Feature.cpp + src/SketchFeature.cpp resources.qrc ) diff --git a/src/Document.cpp b/src/Document.cpp new file mode 100644 index 0000000..cfac37b --- /dev/null +++ b/src/Document.cpp @@ -0,0 +1,90 @@ +#include "Document.h" +#include "Feature.h" +#include "SketchFeature.h" + +#include +#include +#include +#include + +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; +} diff --git a/src/Document.h b/src/Document.h new file mode 100644 index 0000000..2c2a023 --- /dev/null +++ b/src/Document.h @@ -0,0 +1,26 @@ +#ifndef DOCUMENT_H +#define DOCUMENT_H + +#include +#include + +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 m_features; +}; + +#endif // DOCUMENT_H diff --git a/src/Feature.cpp b/src/Feature.cpp new file mode 100644 index 0000000..b8acf03 --- /dev/null +++ b/src/Feature.cpp @@ -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; +} diff --git a/src/Feature.h b/src/Feature.h new file mode 100644 index 0000000..53ea8a8 --- /dev/null +++ b/src/Feature.h @@ -0,0 +1,25 @@ +#ifndef FEATURE_H +#define FEATURE_H + +#include +#include + +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 diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index 92b853a..4756636 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -1,6 +1,12 @@ #include "MainWindow.h" #include "ViewportWidget.h" +#include "Document.h" +#include "SketchFeature.h" +#include +#include +#include +#include #include #include #include @@ -16,6 +22,20 @@ MainWindow::MainWindow(QWidget *parent) setWindowTitle("OpenCAD"); 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"); mainToolBar->setMovable(false); mainToolBar->setFloatable(false); @@ -58,6 +78,9 @@ MainWindow::MainWindow(QWidget *parent) m_viewport = new ViewportWidget; setCentralWidget(m_viewport); + + m_document = new Document(this); + setCurrentFile(QString()); } void MainWindow::createSketch() @@ -69,12 +92,75 @@ void MainWindow::createSketch() QString item = QInputDialog::getItem(this, "Select Sketch Plane", "Plane:", items, 0, false, &ok); if (ok && !item.isEmpty()) { + auto feature = new SketchFeature("Sketch"); if (item == "XY-Plane") { m_viewport->startSketch(ViewportWidget::SketchPlane::XY); + feature->setPlane(SketchFeature::SketchPlane::XY); } else if (item == "XZ-Plane") { m_viewport->startSketch(ViewportWidget::SketchPlane::XZ); + feature->setPlane(SketchFeature::SketchPlane::XZ); } else if (item == "YZ-Plane") { 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"))); +} diff --git a/src/MainWindow.h b/src/MainWindow.h index ae29f45..897f8fa 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -4,6 +4,7 @@ #include class ViewportWidget; +class Document; class MainWindow : public QMainWindow { @@ -13,10 +14,18 @@ public: explicit MainWindow(QWidget *parent = nullptr); private slots: + void newFile(); + void open(); + bool save(); + bool saveAs(); void createSketch(); private: + void setCurrentFile(const QString &fileName); + ViewportWidget *m_viewport; + Document *m_document; + QString m_currentFile; }; #endif // MAINWINDOW_H diff --git a/src/SketchFeature.cpp b/src/SketchFeature.cpp new file mode 100644 index 0000000..edc9129 --- /dev/null +++ b/src/SketchFeature.cpp @@ -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; +} diff --git a/src/SketchFeature.h b/src/SketchFeature.h new file mode 100644 index 0000000..8009028 --- /dev/null +++ b/src/SketchFeature.h @@ -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