initial commit

This commit is contained in:
2026-04-07 20:55:30 +08:00
commit 81d1fb7856
84 changed files with 11929 additions and 0 deletions

View File

@@ -0,0 +1,148 @@
#pragma once
#include "domain/Project.h"
#include <QImage>
#include <QJsonObject>
#include <QRect>
#include <QString>
#include <QStringList>
#include <QVector>
namespace core {
class ProjectWorkspace {
public:
static constexpr const char* kProjectIndexFileName = "project.json";
static constexpr const char* kAssetsDirName = "assets";
// 写入 project.json 的 version 字段;仍可读 version 1内嵌实体 + 可选 .anim
static constexpr int kProjectIndexFormatVersion = 2;
ProjectWorkspace() = default;
// 新建项目:
// - 传入的 parentDir 是“父目录”(你在文件对话框中选择的目录)
// - 会在 parentDir 下创建一个新的项目目录(默认使用项目名做文件夹名;若重名会自动加后缀)
// - 项目结构为v2
// <projectDir>/project.json (索引:背景/深度路径 + 实体 id 与 .hfe 路径)
// <projectDir>/assets/background.png
// <projectDir>/assets/entities/*.png / *.hfe
bool createNew(const QString& parentDir, const QString& name, const QString& backgroundImageSourcePath);
bool createNew(const QString& parentDir, const QString& name, const QString& backgroundImageSourcePath,
const QRect& cropRectInSourceImage);
bool openExisting(const QString& projectDir);
void close();
bool isOpen() const { return !m_projectDir.isEmpty(); }
const QString& projectDir() const { return m_projectDir; }
QString indexFilePath() const;
QString assetsDirPath() const;
bool hasBackground() const { return !m_project.backgroundImagePath().isEmpty(); }
QString backgroundAbsolutePath() const;
bool backgroundVisible() const { return m_project.backgroundVisible(); }
bool setBackgroundVisible(bool on);
bool hasDepth() const;
QString depthAbsolutePath() const;
// 写入 project.json 的 name 字段(可 undo
bool setProjectTitle(const QString& title);
Project& project() { return m_project; }
const Project& project() const { return m_project; }
// 历史操作(最多 30 步),类似 Blender维护 undo/redo 栈
bool canUndo() const;
bool canRedo() const;
bool undo();
bool redo();
QStringList historyLabelsNewestFirst() const;
// 追加一次“导入并设置背景图”操作:把图片拷贝进 assets/,并作为背景写入项目(会进入历史)。
bool importBackgroundImage(const QString& backgroundImageSourcePath);
bool importBackgroundImage(const QString& backgroundImageSourcePath, const QRect& cropRectInSourceImage);
// 计算并写入假深度图assets/depth.png同时更新 project.jsondepthComputed/depthMapPath
bool computeFakeDepthForProject();
// 从后端计算深度并落盘assets/depth.png同时更新 project.jsondepthComputed/depthMapPath
// - serverBaseUrl 为空时:优先读环境变量 MODEL_SERVER_URL否则默认 http://127.0.0.1:8000
// - outError 可选:返回失败原因
bool computeDepthForProjectFromServer(const QString& serverBaseUrl, QString* outError = nullptr, int timeoutMs = 30000);
// 直接保存深度图PNG bytes到 assets/depth.png并更新 project.json。
bool saveDepthMapPngBytes(const QByteArray& pngBytes, QString* outError = nullptr);
const QVector<Project::Entity>& entities() const { return m_project.entities(); }
bool addEntity(const Project::Entity& entity, const QImage& image);
bool setEntityVisible(const QString& id, bool on);
bool setEntityDisplayName(const QString& id, const QString& displayName);
bool setEntityUserScale(const QString& id, double userScale);
// 将多边形质心平移到 targetCentroidWorld整体平移sTotal 须与画布一致
bool moveEntityCentroidTo(const QString& id, int frame, const QPointF& targetCentroidWorld, double sTotal,
bool autoKeyLocation);
// 在保持外形不变的前提下移动枢轴点sTotal 须与画布一致(距离缩放×整体缩放)
bool reanchorEntityPivot(const QString& id, int frame, const QPointF& newPivotWorld, double sTotal);
bool reorderEntitiesById(const QStringList& idsInOrder);
// currentFrame自动关键帧时写入位置曲线autoKeyLocation 为 false 时忽略。
bool moveEntityBy(const QString& id, const QPointF& delta, int currentFrame, bool autoKeyLocation);
bool setEntityLocationKey(const QString& id, int frame, const QPointF& originWorld);
bool setEntityDepthScaleKey(const QString& id, int frame, double value01);
bool setEntityUserScaleKey(const QString& id, int frame, double userScale);
bool setEntityImageFrame(const QString& id, int frame, const QImage& image, QString* outRelPath = nullptr);
bool removeEntityLocationKey(const QString& id, int frame);
bool removeEntityDepthScaleKey(const QString& id, int frame);
bool removeEntityUserScaleKey(const QString& id, int frame);
bool removeEntityImageFrame(const QString& id, int frame);
private:
bool writeIndexJson();
bool readIndexJson(const QString& indexPath);
bool syncEntityPayloadsToDisk();
bool hydrateEntityPayloadsFromDisk();
void loadV1LegacyAnimationSidecars();
static QJsonObject projectToJson(const Project& project);
static bool projectFromJson(const QJsonObject& root, Project& outProject, int* outFileVersion);
static QString asRelativeUnderProject(const QString& relativePath);
static QString fileSuffixWithDot(const QString& path);
static QString asOptionalRelativeUnderProject(const QString& relativePath);
static QJsonObject entityToJson(const Project::Entity& e);
static bool entityFromJsonV1(const QJsonObject& o, Project::Entity& out);
static bool entityStubFromJsonV2(const QJsonObject& o, Project::Entity& out);
struct Operation {
enum class Type { ImportBackground, SetEntities, SetProjectTitle };
Type type {Type::ImportBackground};
QString label;
QString beforeBackgroundPath;
QString afterBackgroundPath;
QVector<Project::Entity> beforeEntities;
QVector<Project::Entity> afterEntities;
QString beforeProjectTitle;
QString afterProjectTitle;
};
static constexpr int kMaxHistorySteps = 30;
void pushOperation(const Operation& op);
bool applyBackgroundPath(const QString& relativePath, bool recordHistory, const QString& label);
bool applyEntities(const QVector<Project::Entity>& entities, bool recordHistory, const QString& label);
QString copyIntoAssetsAsBackground(const QString& sourceFilePath, const QRect& cropRectInSourceImage);
bool writeDepthMap(const QImage& depth8);
bool writeDepthMapBytes(const QByteArray& pngBytes);
QString ensureEntitiesDir() const;
bool writeEntityImage(const QString& entityId, const QImage& image, QString& outRelPath);
bool writeEntityFrameImage(const QString& entityId, int frame, const QImage& image, QString& outRelPath);
private:
QString m_projectDir;
Project m_project;
QVector<Operation> m_undoStack;
QVector<Operation> m_redoStack;
};
} // namespace core