feat(v1.0.1): tangent_pull + error codes + version + license management
CI / Build & Test (push) Failing after 39s
CI / Release Build (push) Failing after 33s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

Remaining P3 item:
- tangent_pull: maintain G1 tangency when pulling faces

System infrastructure:
- Unified ErrorCode (0-7xxx): ErrorInfo with Chinese messages
- Version system: VDE_VERSION_MAJOR/MINOR/PATCH macros,
  vde_version() → VersionInfo, vde_version_string()
- License management: LicenseTier (Community/Professional/Enterprise),
  LicenseKey (VDE-XXXX-XXXX, Base32 + HMAC-SHA256 signature),
  LicenseManager singleton with trial support
- Convenience header: vde/vde_version.h

Tests: 43 total (26 license + 17 version)
This commit is contained in:
茂之钳
2026-07-27 21:54:46 +08:00
parent 4b90438315
commit 5bfcbcb8e9
14 changed files with 2355 additions and 24 deletions
+8
View File
@@ -4,6 +4,14 @@ set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# ── 生成构建时间戳 ──────────────────────────────────
string(TIMESTAMP VDE_BUILD_TIMESTAMP "%Y-%m-%dT%H:%M:%SZ" UTC)
configure_file(
${CMAKE_SOURCE_DIR}/cmake/vde_build_config.h.in
${CMAKE_BINARY_DIR}/generated/vde_build_config.h
@ONLY
)
option(BUILD_TESTS "Build tests" ON)
option(BUILD_BENCHMARKS "Build benchmarks" OFF)
option(BUILD_EXAMPLES "Build examples" ON)
+12
View File
@@ -0,0 +1,12 @@
#pragma once
/**
* @file vde_build_config.h
* @brief CMake configure_file 生成的构建配置
* @warning 此文件由 CMake 自动生成,请勿手动编辑。
*/
/// 构建时间戳(ISO 8601 UTC 格式)
#define VDE_BUILD_TIMESTAMP "@VDE_BUILD_TIMESTAMP@"
/// 构建类型
#define VDE_BUILD_TYPE "@VDE_BUILD_TYPE@"
+42
View File
@@ -343,4 +343,46 @@ struct DirectModelingResult {
int face_id,
int target_face_id);
// ═══════════════════════════════════════════════════════════
// Direct Modeling Enhancements — v5.6
// ═══════════════════════════════════════════════════════════
/**
* @brief 相切联动拉伸:拉伸面并保持相邻面相切
*
* 将指定面沿其法向拉伸,同时检测并调整相邻面,使其
* 在共享边处保持与拉伸面的相切关系(G1 连续)。
*
* 非相切邻面保持原有行为(仅共享边处适配新位置)。
*
* 算法:
* 1. 检测相邻面(与目标面共享边的面)
* 2. 对每个相邻面检测 G1 连续性
* - 平面面:通过法向量平行性判定(dot > 0.9999
* - 曲面:通过 surface_continuity 检测
* 3. 执行 push_pull 拉伸目标面
* 4. 对 G1 相邻面,将其非共享顶点沿目标面法向偏移相同距离
* 以保持相切关系
*
* @param body 输入 B-Rep 实体
* @param face_id 被拉伸的面索引
* @param distance 拉伸距离(正=挤出加材料,负=切除减材料)
* @return DirectModelingResult
* - result.body: 拉伸后保持相切的新模型
* - result.affected_faces: 受影响的面数(含调整的相邻面)
* - result.new_faces: 新创建的侧面数
*
* @pre face_id 在 [0, body.num_faces()) 范围内
* @pre 实体应为封闭水密体
* @pre distance 的绝对值不超过实体在法线方向的最小厚度
*
* @code{.cpp}
* auto box = make_box(10, 5, 3);
* // 拉伸顶面 2.0,相切邻面自动联动
* auto result = tangent_pull(box, 0, 2.0);
* @endcode
*/
[[nodiscard]] DirectModelingResult tangent_pull(const BrepModel& body,
int face_id, double distance);
} // namespace vde::brep
+278
View File
@@ -0,0 +1,278 @@
#pragma once
/**
* @file license_manager.h
* @brief ViewDesignEngine 许可证管理系统
*
* 提供三层许可证体系:
* - Community(社区版):基础 B-Rep + STEP/IGES 导入导出
* - Professional(专业版):+ 5轴CAM + 高级曲面 + GD&T
* - Enterprise(企业版):+ 技术支持 + 自定义格式
*
* LicenseKey 格式:VDE-XXXX-XXXX-XXXXBase32编码)
* 包含:版本号 + tier + 过期时间 + 签名(HMAC-SHA256
*
* @ingroup core
*/
#include <cstdint>
#include <string>
#include <vector>
#include <set>
#include <ctime>
#include <mutex>
namespace vde::core {
// ═══════════════════════════════════════════════════════════════
// LicenseTier 枚举
// ═══════════════════════════════════════════════════════════════
/// 许可证层级
enum class LicenseTier : int {
Community = 0, ///< 社区版
Professional = 1, ///< 专业版
Enterprise = 2, ///< 企业版
};
/// 将 LicenseTier 转为字符串
inline const char* license_tier_name(LicenseTier tier) {
switch (tier) {
case LicenseTier::Community: return "Community";
case LicenseTier::Professional: return "Professional";
case LicenseTier::Enterprise: return "Enterprise";
}
return "Unknown";
}
/// 从字符串解析 LicenseTier,失败返回 -1
int parse_license_tier(const std::string& name);
// ═══════════════════════════════════════════════════════════════
// Feature 枚举(按 tier 区分)
// ═══════════════════════════════════════════════════════════════
/// 功能特性标识
enum class Feature : int {
// Community 级别
BASIC_BREP = 0, ///< 基础B-Rep建模
STEP_IMPORT_EXPORT = 1, ///< STEP导入导出
IGES_IMPORT_EXPORT = 2, ///< IGES导入导出
BASE_IO_FORMATS = 3, ///< 基础格式IOOBJ/STL/PLY
MESH_OPERATIONS = 4, ///< 基本网格操作
SKETCH_SOLVER = 5, ///< 草图约束求解
// Professional 级别
CAM_5AXIS = 10, ///< 5轴CAM加工
ADVANCED_SURFACING = 11, ///< 高级曲面建模(A级曲面等)
GD_T = 12, ///< GD&T 几何尺寸和公差
PMI_MBD = 13, ///< PMI / 基于模型的定义
ASSEMBLY_DESIGN = 14, ///< 装配设计
SHEET_METAL = 15, ///< 钣金设计
ADVANCED_HEALING = 16, ///< 高级修复
FEA_MESH = 17, ///< FEA网格生成
IGA_ANALYSIS = 18, ///< IGA等几何分析
COLLISION_DETECTION = 19, ///< 碰撞检测
DIRECT_MODELING = 20, ///< 直接建模
SURFACE_FAIRING = 21, ///< 曲面光顺
// Enterprise 级别
TECHNICAL_SUPPORT = 30, ///< 技术支持
CUSTOM_FORMATS = 31, ///< 自定义格式支持
SCRIPTING_API = 32, ///< 脚本API
GPU_ACCELERATION = 33, ///< GPU加速
CLOUD_COLLABORATION = 34, ///< 云端协作
SDF_MODELING = 35, ///< SDF隐式建模
LICENSE_MANAGER_SDK = 36, ///< License SDK分发
};
/// Feature 转字符串
const char* feature_name(Feature f);
/// 字符串转 Feature,失败返回 -1
int parse_feature(const std::string& name);
/// 获取指定 tier 包含的所有 feature
std::set<Feature> features_for_tier(LicenseTier tier);
/// 检查某个 feature 是否属于指定 tier
bool feature_available_in_tier(Feature f, LicenseTier tier);
// ═══════════════════════════════════════════════════════════════
// LicenseInfo 结构体
// ═══════════════════════════════════════════════════════════════
/// 许可证信息
struct LicenseInfo {
LicenseTier tier; ///< 层级
std::string licensee; ///< 被许可方(公司/个人名称)
std::time_t expiry; ///< 过期时间(Unix时间戳,0=永久)
std::set<Feature> features; ///< 启用的功能列表
std::string key_string; ///< 原始License Key字符串
int version; ///< License版本号
/// 默认构造(Community试用版)
LicenseInfo()
: tier(LicenseTier::Community)
, licensee("Trial User")
, expiry(0)
, version(1) {}
/// 是否已过期
[[nodiscard]] bool is_expired() const;
/// 距离过期还剩多少天(返回 -1 表示永久有效)
[[nodiscard]] int days_until_expiry() const;
};
// ═══════════════════════════════════════════════════════════════
// LicenseKey 类
// ═══════════════════════════════════════════════════════════════
/**
* @brief LicenseKey 生成/验证/解析类
*
* License Key 格式:VDE-XXXX-XXXX-XXXX
* 其中 XXXX 段为 Base32 编码的二进制数据。
*
* 二进制数据布局(17字节):
* - byte 0: version (1)
* - byte 1: tier (0=Community, 1=Professional, 2=Enterprise)
* - byte 2-9: expiry (uint64_t, Unix timestamp, big-endian)
* - byte 10-16: reserved (0)
*
* 签名:HMAC-SHA256 的前 4 字节附加到 Base32 编码后的最后一段。
*
* 完整的 Key 编码流程:
* payload(17B) → HMAC-SHA256 → 取前4B → payload+signature(21B) → Base32 → VDE-XXXX-XXXX-XXXX
*/
class LicenseKey {
public:
/// 默认构造(无效Key
LicenseKey() = default;
/**
* @brief 从字符串解析 LicenseKey
* @param key_str 如 "VDE-ABCD-EFGH-IJKL"
* @return 如果格式有效返回 true
*/
bool parse(const std::string& key_str);
/**
* @brief 生成 LicenseKey
* @param tier 许可证层级
* @param expiry 过期时间(Unix时间戳,0=永久)
* @param licensee 被许可方名称
* @return 格式化的License Key字符串
*/
static std::string generate(LicenseTier tier, std::time_t expiry,
const std::string& licensee);
/**
* @brief 验证 LicenseKey 是否有效
* @param key_str License Key 字符串
* @return 有效则返回 true
*/
static bool validate(const std::string& key_str);
/// 获取解析后的 LicenseInfo
[[nodiscard]] const LicenseInfo& info() const { return info_; }
/// 原始 Key 字符串
[[nodiscard]] const std::string& key_string() const { return key_string_; }
/// 签名验证是否通过
[[nodiscard]] bool signature_valid() const { return sig_valid_; }
private:
/// Base32 编解码(RFC 4648,无填充)
static std::string base32_encode(const std::vector<uint8_t>& data);
static std::vector<uint8_t> base32_decode(const std::string& encoded);
/// HMAC-SHA256 简化实现(使用占位密钥)
static std::vector<uint8_t> compute_hmac(const std::vector<uint8_t>& data);
LicenseInfo info_;
std::string key_string_;
bool sig_valid_ = false;
};
// ═══════════════════════════════════════════════════════════════
// LicenseManager 单例
// ═══════════════════════════════════════════════════════════════
/**
* @brief LicenseManager 全局许可证管理器(单例)
*
* 管理当前运行时使用的许可证,提供功能查询接口。
*
* 用法:
* @code
* auto& mgr = LicenseManager::instance();
* mgr.set_license("VDE-ABCD-EFGH-IJKL");
* if (mgr.has_feature("CAM_5AXIS")) { ... }
* if (mgr.is_expired()) { ... }
* @endcode
*/
class LicenseManager {
public:
/// 获取单例实例
static LicenseManager& instance();
// ── 禁止拷贝和移动 ──────────────────────────────
LicenseManager(const LicenseManager&) = delete;
LicenseManager& operator=(const LicenseManager&) = delete;
/**
* @brief 设置 License Key
* @param key License Key 字符串
* @return true 设置成功(Key有效且未过期)
*/
bool set_license(const std::string& key);
/**
* @brief 进入试用模式(30天Community试用)
* @param days 试用天数(默认30
*/
void start_trial(int days = 30);
/// 当前许可证层级
[[nodiscard]] LicenseTier current_tier() const;
/**
* @brief 检查是否拥有某个功能
* @param feature_name 功能名称字符串(如 "CAM_5AXIS"
* @return true 表示该功能可用
*/
[[nodiscard]] bool has_feature(const std::string& feature_name) const;
/**
* @brief 检查 Feature 枚举是否可用
* @param f Feature 枚举值
* @return true 表示该功能可用
*/
[[nodiscard]] bool has_feature(Feature f) const;
/// 许可证是否已过期
[[nodiscard]] bool is_expired() const;
/// 试用剩余天数(-1 表示已过期或无试用)
[[nodiscard]] int trial_days_left() const;
/// 获取当前许可证信息
[[nodiscard]] const LicenseInfo& current_license() const;
/// 是否处于试用模式
[[nodiscard]] bool is_trial() const;
private:
LicenseManager();
~LicenseManager() = default;
LicenseInfo current_;
bool trial_mode_ = false;
std::time_t trial_start_ = 0;
int trial_days_ = 30;
mutable std::mutex mutex_;
};
} // namespace vde::core
+176
View File
@@ -0,0 +1,176 @@
#pragma once
/**
* @file version.h
* @brief ViewDesignEngine 版本信息
*
* 提供编译时版本宏和运行时版本查询函数。
* 编译时间戳通过 CMake 的 configure_file 在构建时注入。
*
* @ingroup core
*/
#include <cstdint>
#include <string>
// 包含 CMake 生成的构建配置
#if __has_include("vde_build_config.h")
#include "vde_build_config.h"
#endif
// ═══════════════════════════════════════════════════════════════
// 编译时版本宏
// ═══════════════════════════════════════════════════════════════
/// 主版本号(不兼容的 API 变更)
#define VDE_VERSION_MAJOR 1
/// 次版本号(向后兼容的功能新增)
#define VDE_VERSION_MINOR 0
/// 修订版本号(向后兼容的问题修复)
#define VDE_VERSION_PATCH 1
/// 版本字符串
#define VDE_VERSION_STRING "1.0.1"
// ── 版本号整型(用于比较) ────────────────────────────
/// 版本号编码为大整数: VDE_VERSION(major, minor, patch) = major*10000 + minor*100 + patch
#define VDE_VERSION_CODE(major, minor, patch) \
((major) * 10000 + (minor) * 100 + (patch))
/// 当前版本号整型编码
#define VDE_VERSION_NUM VDE_VERSION_CODE(1, 0, 1)
// ═══════════════════════════════════════════════════════════════
// 构建信息(由 CMake configure_file 注入)
// ═══════════════════════════════════════════════════════════════
#ifndef VDE_BUILD_TYPE
/// 构建类型:Debug / Release / RelWithDebInfo / MinSizeRel
#ifdef NDEBUG
#define VDE_BUILD_TYPE "Release"
#else
#define VDE_BUILD_TYPE "Debug"
#endif
#endif
#ifndef VDE_BUILD_TIMESTAMP
/// 构建时间戳(ISO 8601 格式,如 "2026-07-27T12:00:00Z"
/// 由 CMake 的 configure_file 注入
#define VDE_BUILD_TIMESTAMP "unknown"
#endif
#ifndef VDE_COMPILER_INFO
/// 编译器信息字符串
#if defined(__clang__)
#define VDE_COMPILER_INFO "Clang " __clang_version__
#elif defined(__GNUC__) || defined(__GNUG__)
#define VDE_COMPILER_INFO "GCC " __VERSION__
#elif defined(_MSC_VER)
#define VDE_COMPILER_INFO "MSVC " _MSC_VER_STR
#else
#define VDE_COMPILER_INFO "Unknown Compiler"
#endif
#endif
// ═══════════════════════════════════════════════════════════════
// C++ 标准信息
// ═══════════════════════════════════════════════════════════════
/// C++ 标准字符串(如 "C++20"
#if __cplusplus >= 202002L
#define VDE_CPP_STANDARD_STRING "C++20"
#elif __cplusplus >= 201703L
#define VDE_CPP_STANDARD_STRING "C++17"
#elif __cplusplus >= 201402L
#define VDE_CPP_STANDARD_STRING "C++14"
#elif __cplusplus >= 201103L
#define VDE_CPP_STANDARD_STRING "C++11"
#else
#define VDE_CPP_STANDARD_STRING "C++03"
#endif
namespace vde::core {
// ═══════════════════════════════════════════════════════════════
// VersionInfo 结构体
// ═══════════════════════════════════════════════════════════════
/**
* @brief 版本信息结构体
*
* 包含库版本号、构建类型、时间戳和编译器信息。
*/
struct VersionInfo {
int major; ///< 主版本号
int minor; ///< 次版本号
int patch; ///< 修订版本号
std::string version_string; ///< 版本字符串 "1.0.1"
std::string build_type; ///< 构建类型 "Debug"/"Release"
std::string build_timestamp;///< 构建时间戳
std::string compiler_info; ///< 编译器信息
std::string cpp_standard; ///< C++ 标准字符串 "C++20"
/// 默认构造(使用编译时宏填充)
VersionInfo()
: major(VDE_VERSION_MAJOR)
, minor(VDE_VERSION_MINOR)
, patch(VDE_VERSION_PATCH)
, version_string(VDE_VERSION_STRING)
, build_type(VDE_BUILD_TYPE)
, build_timestamp(VDE_BUILD_TIMESTAMP)
, compiler_info(VDE_COMPILER_INFO)
, cpp_standard(VDE_CPP_STANDARD_STRING) {}
};
// ═══════════════════════════════════════════════════════════════
// API 函数
// ═══════════════════════════════════════════════════════════════
/**
* @brief 获取版本信息结构体
* @return VersionInfo 包含完整的版本和构建信息
*/
inline VersionInfo vde_version() {
return VersionInfo{};
}
/**
* @brief 获取人类可读的版本字符串
*
* 格式:"ViewDesignEngine v1.0.1 (C++20, GCC 11.4)"
*
* @return 版本信息字符串
*/
inline std::string vde_version_string() {
std::string result = "ViewDesignEngine v";
result += VDE_VERSION_STRING;
result += " (";
result += VDE_CPP_STANDARD_STRING;
result += ", ";
result += VDE_COMPILER_INFO;
result += ")";
return result;
}
/**
* @brief 比较两个版本号
*
* 返回负值表示 lhs < rhs,零表示相等,正值表示 lhs > rhs。
*
* @param lhs_major 左主版本
* @param lhs_minor 左次版本
* @param lhs_patch 左修订版本
* @param rhs_major 右主版本
* @param rhs_minor 右次版本
* @param rhs_patch 右修订版本
* @return 比较结果
*/
inline int vde_version_compare(int lhs_major, int lhs_minor, int lhs_patch,
int rhs_major, int rhs_minor, int rhs_patch) {
int lhs = VDE_VERSION_CODE(lhs_major, lhs_minor, lhs_patch);
int rhs = VDE_VERSION_CODE(rhs_major, rhs_minor, rhs_patch);
return lhs - rhs;
}
} // namespace vde::core
+280 -24
View File
@@ -1,33 +1,289 @@
#pragma once
#include <cstdint>
namespace vde::foundation {
/**
* @brief 全局错误码枚举
* @file error_codes.h
* @brief ViewDesignEngine 统一错误码体系
*
* VDE 引擎中所有函数可能返回的错误状态码。
* 成功用 kSuccess(0) 表示,非零值表示各类错误。
* 所有模块的错误码集中定义在此,采用分段编码:
* 0 — SUCCESS
* 1xxx — foundation(格式IO错误)
* 2xxx — core(几何计算错误)
* 3xxx — curves(曲线曲面错误)
* 4xxx — mesh(网格错误)
* 5xxx — brepB-Rep错误)
* 6xxx — sdfSDF错误)
* 7xxx — capiC API错误)
*
* @ingroup foundation
*/
#include <cstdint>
#include <string>
#include <string_view>
namespace vde::foundation {
// ═══════════════════════════════════════════════════════════════
// ErrorCode 枚举 — 所有模块统一
// ═══════════════════════════════════════════════════════════════
enum class ErrorCode : int32_t {
/// 操作成功完成,无错误
kSuccess = 0,
/// 传入参数无效(空指针、越界、非法值等)
kInvalidArgument,
/// 输入几何数据不合法(自交、非流形、退化等)
kInvalidGeometry,
/// 退化情况(共线、共面等导致无法确定唯一解)
kDegenerateCase,
/// 功能尚未实现
kNotImplemented,
/// 数值计算误差超出容差范围
kNumericalError,
/// 内存分配失败
kOutOfMemory,
/// 内部逻辑错误(不应出现的情况)
kInternalError,
// ── 成功 ───────────────────────────────────────────
SUCCESS = 0,
// ── 1xxxfoundation 格式IO错误 ──────────────────────
// 通用文件IO
ERR_FILE_NOT_FOUND = 1001, // 文件未找到
ERR_FILE_OPEN_FAILED = 1002, // 文件打开失败
ERR_FILE_WRITE_FAILED = 1003, // 文件写入失败
ERR_FILE_READ_FAILED = 1004, // 文件读取失败
ERR_FILE_PERMISSION_DENIED = 1005, // 文件权限不足
ERR_FILE_TOO_LARGE = 1006, // 文件过大
ERR_FILE_EMPTY = 1007, // 文件为空
ERR_FILE_ALREADY_EXISTS = 1008, // 文件已存在
// 格式解析
ERR_FORMAT_INVALID = 1101, // 格式无效
ERR_FORMAT_UNSUPPORTED = 1102, // 格式不支持
ERR_FORMAT_VERSION_MISMATCH = 1103, // 格式版本不匹配
ERR_FORMAT_CORRUPTED = 1104, // 格式数据损坏
ERR_FORMAT_TRUNCATED = 1105, // 格式数据截断
ERR_FORMAT_MAGIC_MISMATCH = 1106, // 格式魔术字不匹配
// VDE 原生格式
ERR_VDE_HEADER_INVALID = 1201, // VDE文件头无效
ERR_VDE_SECTION_MISSING = 1202, // VDE段缺失
ERR_VDE_SECTION_CORRUPTED = 1203, // VDE段损坏
ERR_VDE_TOPOLOGY_INVALID = 1204, // VDE拓扑数据无效
ERR_VDE_GEOMETRY_INVALID = 1205, // VDE几何数据无效
ERR_VDE_SERIALIZE_FAILED = 1206, // VDE序列化失败
ERR_VDE_DESERIALIZE_FAILED = 1207, // VDE反序列化失败
// 工业格式 IO
ERR_STEP_PARSE = 1301, // STEP解析错误
ERR_STEP_SCHEMA_MISMATCH = 1302, // STEP模式不匹配
ERR_STEP_ENTITY_UNKNOWN = 1303, // STEP未知实体
ERR_STEP_TOPOLOGY = 1304, // STEP拓扑构建失败
ERR_STEP_GEOMETRY = 1305, // STEP几何构建失败
ERR_IGES_PARSE = 1311, // IGES解析错误
ERR_IGES_ENTITY_UNSUPPORTED = 1312, // IGES不支持的实体
ERR_OBJ_PARSE = 1321, // OBJ解析错误
ERR_STL_PARSE = 1331, // STL解析错误
ERR_STL_BINARY_INVALID = 1332, // STL二进制格式无效
ERR_PLY_PARSE = 1341, // PLY解析错误
ERR_GLTF_PARSE = 1351, // glTF解析错误
ERR_3MF_PARSE = 1361, // 3MF解析错误
ERR_DXF_PARSE = 1371, // DXF解析错误
// ── 2xxx:core 几何计算错误 ─────────────────────────
ERR_DEGENERATE_GEOMETRY = 2001, // 退化几何
ERR_NUMERIC_INSTABILITY = 2002, // 数值不稳定
ERR_DIVISION_BY_ZERO = 2003, // 除零错误
ERR_SINGULAR_MATRIX = 2004, // 奇异矩阵
ERR_CONVERGENCE_FAILURE = 2005, // 收敛失败
ERR_OUT_OF_MEMORY = 2006, // 内存不足
ERR_OVERFLOW = 2007, // 数值溢出
ERR_PRECISION_LOSS = 2008, // 精度丢失
ERR_BUFFER_TOO_SMALL = 2009, // 缓冲区太小
ERR_INDEX_OUT_OF_BOUNDS = 2010, // 索引越界
// 几何运算
ERR_INTERSECTION_FAILED = 2101, // 求交失败
ERR_PROJECTION_FAILED = 2102, // 投影失败
ERR_DISTANCE_COMPUTE_FAILED = 2103, // 距离计算失败
ERR_CONVEX_HULL_FAILED = 2104, // 凸包计算失败
ERR_VORONOI_FAILED = 2105, // Voronoi计算失败
ERR_TRIANGULATION_FAILED = 2106, // 三角剖分失败
ERR_SIMPLIFY_FAILED = 2107, // 简化失败
// CAM
ERR_CAM_TOOLPATH_GENERATION = 2201, // 刀路生成失败
ERR_CAM_COLLISION_DETECTED = 2202, // 刀具碰撞检测到
ERR_CAM_INVALID_STRATEGY = 2203, // 无效的加工策略
ERR_CAM_5AXIS_LIMIT = 2204, // 五轴极限超限
ERR_CAM_OPTIMIZATION_FAILED = 2205, // CAM优化失败
// 性能/并发
ERR_THREAD_CREATE_FAILED = 2301, // 线程创建失败
ERR_LOCK_TIMEOUT = 2302, // 锁超时
ERR_TASK_CANCELLED = 2303, // 任务已取消
ERR_TRANSACTION_CONFLICT = 2304, // 事务冲突
ERR_TRANSACTION_ROLLBACK = 2305, // 事务回滚
// ── 3xxx:curves 曲线曲面错误 ───────────────────────
ERR_CURVE_DEGREE_INVALID = 3001, // 曲线阶数无效
ERR_CURVE_KNOT_INVALID = 3002, // 节点向量无效
ERR_CURVE_CP_COUNT_MISMATCH = 3003, // 控制点数量不匹配
ERR_CURVE_EVALUATION_FAILED = 3004, // 曲线求值失败
ERR_CURVE_DERIVATIVE_FAILED = 3005, // 曲线求导失败
ERR_CURVE_PARAM_OUT_OF_RANGE= 3006, // 参数超出范围
ERR_CURVE_SELF_INTERSECT = 3007, // 曲线自交
ERR_CURVE_DISCONTINUITY = 3008, // 曲线不连续
// 曲面
ERR_SURFACE_INVALID = 3101, // 曲面无效
ERR_SURFACE_PARAM_RANGE = 3102, // 曲面参数范围无效
ERR_SURFACE_DEGENERATE = 3103, // 退化曲面
ERR_SURFACE_SINGULARITY = 3104, // 曲面奇点
ERR_SURFACE_INTERSECTION = 3105, // 曲面求交失败
ERR_SURFACE_CONTINUITY = 3106, // 曲面连续性不满足
ERR_SURFACE_TRIMMING = 3107, // 曲面裁剪失败
ERR_SURFACE_EXTENSION = 3108, // 曲面延伸失败
ERR_SURFACE_OFFSET = 3109, // 曲面偏置失败
ERR_SURFACE_FAIRING = 3110, // 曲面光顺失败
// IGA
ERR_IGA_MESH_INVALID = 3201, // IGA网格无效
ERR_IGA_REFINEMENT_FAILED = 3202, // IGA细化失败
// ── 4xxx:mesh 网格错误 ─────────────────────────────
ERR_MESH_EMPTY = 4001, // 网格为空
ERR_MESH_NON_MANIFOLD = 4002, // 非流形网格
ERR_MESH_DEGENERATE_FACE = 4003, // 退化面
ERR_MESH_SELF_INTERSECTION = 4004, // 网格自交
ERR_MESH_INVERTED_NORMAL = 4005, // 法线翻转
ERR_MESH_HOLE_DETECTED = 4006, // 检测到孔洞
ERR_MESH_DISCONNECTED = 4007, // 网格不连通
// 网格操作
ERR_MESH_BOOLEAN_FAILED = 4101, // 网格布尔失败
ERR_MESH_SIMPLIFY_FAILED = 4102, // 网格简化失败
ERR_MESH_SMOOTH_FAILED = 4103, // 网格平滑失败
ERR_MESH_REPAIR_FAILED = 4104, // 网格修复失败
ERR_MESH_PARAMETERIZE_FAILED= 4105, // 网格参数化失败
ERR_MESH_REMESH_FAILED = 4106, // 网格重划分失败
ERR_MESH_SPLIT_FAILED = 4107, // 网格分割失败
// 高级网格
ERR_FEA_MESH_QUALITY = 4201, // FEA网格质量不足
ERR_FEA_MESH_GENERATION = 4202, // FEA网格生成失败
ERR_MARCHING_CUBES_FAILED = 4203, // Marching Cubes失败
ERR_POINT_CLOUD_EMPTY = 4204, // 点云为空
ERR_POINT_CLOUD_NOISE = 4205, // 点云噪声过大
// ── 5xxxbrep B-Rep错误 ───────────────────────────
ERR_BREP_TOPOLOGY_INVALID = 5001, // B-Rep拓扑无效
ERR_BREP_GEOMETRY_MISMATCH = 5002, // B-Rep几何不匹配
ERR_BREP_SHELL_NOT_CLOSED = 5003, // 壳不封闭
ERR_BREP_ORIENTATION = 5004, // 方向不一致
ERR_BREP_SEWING_FAILED = 5005, // 缝合失败
ERR_BREP_HEALING_FAILED = 5006, // 修复失败
ERR_BREP_VALIDATION_FAILED = 5007, // 验证失败
// 布尔操作
ERR_BOOLEAN_OPERATION_FAILED= 5101, // 布尔操作失败
ERR_BOOLEAN_INTERSECT_FAILED= 5102, // 布尔求交失败
ERR_BOOLEAN_SUBTRACT_FAILED = 5103, // 布尔求差失败
ERR_BOOLEAN_UNION_FAILED = 5104, // 布尔求并失败
ERR_BOOLEAN_DEGENERATE = 5105, // 布尔操作产生退化
ERR_BOOLEAN_FALLBACK_USED = 5106, // 布尔操作回退到备用方法
// 特征建模
ERR_FEATURE_CREATION_FAILED = 5201, // 特征创建失败
ERR_FEATURE_TREE_INVALID = 5202, // 特征树无效
ERR_FEATURE_UPDATE_FAILED = 5203, // 特征更新失败
ERR_FEATURE_RECOGNITION = 5204, // 特征识别失败
// 装配
ERR_ASSEMBLY_CONSTRAINT = 5301, // 装配约束冲突
ERR_ASSEMBLY_INTERFERENCE = 5302, // 装配干涉
ERR_ASSEMBLY_CYCLE = 5303, // 装配循环引用
ERR_ASSEMBLY_MATE_FAILED = 5304, // 配合失败
ERR_ASSEMBLY_LOD_INVALID = 5305, // LOD级别无效
// 钣金
ERR_SHEET_METAL_THICKNESS = 5401, // 钣金厚度无效
ERR_SHEET_METAL_BEND_RADIUS = 5402, // 弯曲半径无效
ERR_SHEET_METAL_UNFOLD = 5403, // 展开失败
// PMI/GD&T
ERR_PMI_INVALID = 5501, // PMI无效
ERR_GDT_TOLERANCE = 5502, // GD&T公差错误
ERR_AUTO_DIM_FAILED = 5503, // 自动标注失败
ERR_DRAWING_STANDARD = 5504, // 制图标准错误
// 柔性体
ERR_FFD_INVALID = 5601, // FFD变形无效
// ── 6xxx:sdf SDF错误 ──────────────────────────────
ERR_SDF_EVALUATE_FAILED = 6001, // SDF求值失败
ERR_SDF_TREE_INVALID = 6002, // SDF树无效
ERR_SDF_TO_MESH_FAILED = 6003, // SDF转网格失败
ERR_SDF_OPTIMIZE_FAILED = 6004, // SDF优化失败
ERR_SDF_TORCH_ERROR = 6005, // SDF Torch推理错误
ERR_SDF_PRIMITIVE_INVALID = 6006, // SDF基元无效
// ── 7xxxcapi C API错误 ───────────────────────────
ERR_CAPI_NULL_HANDLE = 7001, // 空句柄
ERR_CAPI_INVALID_ARGUMENT = 7002, // 无效参数
ERR_CAPI_TYPE_MISMATCH = 7003, // 类型不匹配
ERR_CAPI_NOT_INITIALIZED = 7004, // 引擎未初始化
ERR_CAPI_ALREADY_INITIALIZED= 7005, // 引擎已初始化
ERR_CAPI_BUFFER_TOO_SMALL = 7006, // 输出缓冲区太小
ERR_CAPI_FEATURE_NOT_AVAIL = 7007, // 功能不可用
ERR_CAPI_UNKNOWN_ERROR = 7999, // 未知C API错误
};
} // namespace vde::foundation
// ═══════════════════════════════════════════════════════════════
// ErrorInfo 结构体
// ═══════════════════════════════════════════════════════════════
/**
* @brief 错误信息结构体
*
* 包含错误码、中文描述、所属模块和发生的源位置。
*/
struct ErrorInfo {
ErrorCode code; ///< 错误码
std::string message; ///< 中文错误描述
std::string module; ///< 所属模块名
std::string file; ///< 源文件名
int line; ///< 源文件行号
/// 默认构造(SUCCESS
ErrorInfo()
: code(ErrorCode::SUCCESS), message("成功"), module(""), file(""), line(0) {}
/// 完整构造
ErrorInfo(ErrorCode c, std::string msg, std::string mod,
std::string f = "", int l = 0)
: code(c), message(std::move(msg)), module(std::move(mod)),
file(std::move(f)), line(l) {}
};
// ═══════════════════════════════════════════════════════════════
// API 函数
// ═══════════════════════════════════════════════════════════════
/**
* @brief 获取错误码对应的中文描述
* @param code 错误码
* @return 中文错误描述字符串
*/
std::string error_message(ErrorCode code);
/**
* @brief 判断错误码是否表示错误(非成功)
* @param code 错误码
* @return true 表示有错误
*/
constexpr bool is_error(ErrorCode code) noexcept {
return code != ErrorCode::SUCCESS;
}
/**
* @brief 判断错误码是否表示成功
* @param code 错误码
* @return true 表示成功
*/
constexpr bool is_success(ErrorCode code) noexcept {
return code == ErrorCode::SUCCESS;
}
/**
* @brief 将错误码转换为所属模块名
* @param code 错误码
* @return 模块名称字符串
*/
std::string error_module(ErrorCode code);
} // namespace vde::foundation
+32
View File
@@ -0,0 +1,32 @@
#pragma once
/**
* @file vde_version.h
* @brief ViewDesignEngine 版本和许可证顶层便捷头文件
*
* 上层应用/用户只需 #include <vde/vde_version.h> 即可获得:
* - ViewDesignEngine 版本信息(版本号、构建信息)
* - 许可证管理(LicenseManager 单例)
* - 功能查询接口(has_feature
*
* 使用示例:
* @code
* #include <vde/vde_version.h>
*
* // 打印版本
* std::cout << vde::core::vde_version_string() << std::endl;
*
* // 设置许可证
* auto& lm = vde::core::LicenseManager::instance();
* lm.set_license("VDE-XXXX-XXXX-XXXX");
*
* // 检查功能
* if (lm.has_feature("CAM_5AXIS")) {
* // 使用5轴CAM功能
* }
* @endcode
*
* @ingroup core
*/
#include "vde/core/version.h"
#include "vde/core/license_manager.h"
+3
View File
@@ -12,6 +12,7 @@ add_library(vde_foundation STATIC
foundation/io_3mf.cpp
foundation/industrial_formats.cpp
foundation/vde_format.cpp
foundation/error_codes.cpp
)
target_include_directories(vde_foundation
PUBLIC ${CMAKE_SOURCE_DIR}/include
@@ -38,9 +39,11 @@ add_library(vde_core STATIC
core/concurrent_data.cpp
core/transaction.cpp
core/topology_compression.cpp
core/license_manager.cpp
)
target_include_directories(vde_core
PUBLIC ${CMAKE_SOURCE_DIR}/include
PUBLIC ${CMAKE_BINARY_DIR}/generated
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
)
target_link_libraries(vde_core
+158
View File
@@ -1,6 +1,7 @@
#include "vde/brep/direct_modeling.h"
#include "vde/brep/brep_validate.h"
#include "vde/brep/trimmed_surface.h"
#include "vde/curves/surface_continuity.h"
#include <cmath>
#include <set>
#include <map>
@@ -1147,4 +1148,161 @@ DirectModelingResult pull_up_to_face(const BrepModel& body,
return result;
}
// ═══════════════════════════════════════════════════════════
// tangent_pull — 相切联动拉伸
// ═══════════════════════════════════════════════════════════
/// 检测两面是否 G1 相切连续
/// 优先通过法向量平行性快速判定(适用于平面),
/// 非平面或判定不明确时回退到 surface_continuity
static bool is_g1_tangent(const BrepModel& body, int fa, int fb) {
Vector3D na = compute_face_normal(body, fa);
Vector3D nb = compute_face_normal(body, fb);
// 法向量平行(同向)→ G1 tangent
double dot_abs = std::abs(na.dot(nb));
if (dot_abs > 0.9999) return true;
// 法向量平行(反向,reversed face)→ G1 tangent
// 已由 dot_abs 覆盖
// 通过曲面连续性检测(曲线/曲面需要)
const auto& face_a = body.face(fa);
const auto& face_b = body.face(fb);
if (face_a.surface_id >= 0 && face_b.surface_id >= 0) {
const auto& surf_a = body.surface(face_a.surface_id);
const auto& surf_b = body.surface(face_b.surface_id);
auto cont = curves::surface_continuity(surf_a, surf_b, 20, 1e-6);
if (cont >= curves::ContinuityLevel::G1) return true;
}
return false;
}
/// 收集非共享顶点:face_id 的所有顶点中,排除与 ref_face 共享的顶点
static std::set<int> collect_non_shared_vertices(const BrepModel& body,
int face_id, int ref_face_id) {
auto face_verts = collect_face_vertices(body, face_id);
auto ref_verts = collect_face_vertices(body, ref_face_id);
std::set<int> result;
for (int vi : face_verts) {
if (ref_verts.find(vi) == ref_verts.end()) {
result.insert(vi);
}
}
return result;
}
DirectModelingResult tangent_pull(const BrepModel& body,
int face_id, double distance) {
DirectModelingResult result;
result.operation = "tangent_pull";
// ── 1. 参数验证 ──
if (face_id < 0 || face_id >= static_cast<int>(body.num_faces())) {
result.errors.push_back("tangent_pull: face_id " + std::to_string(face_id) +
" out of range [0, " + std::to_string(body.num_faces()) + ")");
return result;
}
if (std::abs(distance) < 1e-15) {
result.success = true;
result.body = body;
result.warnings.push_back("tangent_pull: distance is zero, no change made");
return result;
}
// ── 2. 检测相邻面 ──
std::vector<int> adjacent_faces;
for (size_t fi = 0; fi < body.num_faces(); ++fi) {
int cur_fi = static_cast<int>(fi);
if (cur_fi != face_id && faces_share_edge(body, face_id, cur_fi)) {
adjacent_faces.push_back(cur_fi);
}
}
if (adjacent_faces.empty()) {
result.errors.push_back("tangent_pull: face " + std::to_string(face_id) +
" has no adjacent faces (isolated)");
return result;
}
// ── 3. 检测每个相邻面是否 G1 连续 ──
std::set<int> g1_adjacent;
for (int af : adjacent_faces) {
if (is_g1_tangent(body, face_id, af)) {
g1_adjacent.insert(af);
}
}
// ── 4. 计算拉伸面法向 ──
Vector3D normal = compute_face_normal(body, face_id);
// ── 5. 构建顶点偏移 ──
// 拉伸面顶点 → 偏移 distance
// G1 相邻面非共享顶点 → 也偏移 distance(保持相切)
// 拉伸面顶点偏移
auto face_verts = collect_face_vertices(body, face_id);
std::map<int, Point3D> offsets;
for (int vi : face_verts) {
offsets[vi] = body.vertex(vi).point + normal * distance;
}
// G1 相邻面非共享顶点偏移
for (int af : g1_adjacent) {
auto non_shared = collect_non_shared_vertices(body, af, face_id);
for (int vi : non_shared) {
// 避免重复添加(一个顶点可能属于多个 G1 相邻面)
if (offsets.find(vi) == offsets.end()) {
offsets[vi] = body.vertex(vi).point + normal * distance;
}
}
}
int g1_count = static_cast<int>(g1_adjacent.size());
int total_adjacent = static_cast<int>(adjacent_faces.size());
// ── 6. 重建模型(创建侧面 = push_pull 行为) ──
bool create_sides = (std::abs(distance) > 1e-15);
result.body = rebuild_with_offset(body, face_id, offsets, create_sides);
// ── 7. 统计侧面数 ──
if (create_sides) {
const auto& src_face = body.face(face_id);
for (int li : src_face.loops) {
const auto& loop = body.loop_by_id(li);
if (loop.is_outer) {
result.new_faces = static_cast<int>(loop.edges.size());
break;
}
}
}
// ── 8. 验证 ──
auto val_result = validate(result.body);
result.success = val_result.valid;
result.affected_faces = 1 + g1_count;
if (!val_result.valid) {
for (const auto& e : val_result.errors)
result.warnings.push_back("Validation: " + e);
}
// 诊断信息
if (g1_count > 0) {
result.warnings.push_back("tangent_pull: " + std::to_string(g1_count) +
" / " + std::to_string(total_adjacent) +
" adjacent faces adjusted for G1 tangency");
} else {
result.warnings.push_back("tangent_pull: no G1-adjacent faces found " +
std::string("(all ") + std::to_string(total_adjacent) +
" adjacent faces are non-tangent)");
}
return result;
}
} // namespace vde::brep
+617
View File
@@ -0,0 +1,617 @@
/**
* @file license_manager.cpp
* @brief 许可证管理实现
*
* License Key 格式:VDE-XXXX-XXXX-XXXX
*
* 二进制布局(17字节 payload):
* - byte 0: version (1)
* - byte 1: tier (0=Community, 1=Professional, 2=Enterprise)
* - byte 2-9: expiry (uint64_t, Unix timestamp, big-endian)
* - byte 10-16: reserved (0)
*
* 签名:HMAC-SHA256(使用固定种子密钥)前 4 字节附加到 payload。
* 完整编码:payload(17B) + signature(4B) → Base32 → VDE-XXXX-XXXX-XXXX
*/
#include "vde/core/license_manager.h"
#include <algorithm>
#include <cstring>
#include <ctime>
#include <sstream>
#include <stdexcept>
namespace vde::core {
// ═══════════════════════════════════════════════════════════════
// 内部常量
// ═══════════════════════════════════════════════════════════════
static constexpr int PAYLOAD_SIZE = 17; // 载荷字节数
static constexpr int SIGNATURE_SIZE = 4; // 签名截断字节数
static constexpr int ENCODED_SIZE = 21; // payload + signature
static constexpr char KEY_PREFIX[] = "VDE-";
static constexpr size_t KEY_PREFIX_LEN = 4;
// Base32 字母表(RFC 4648,大写)
static const char BASE32_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
// HMAC 种子密钥(固定,用于简单验证;生产环境应从安全存储加载)
static const uint8_t HMAC_KEY[] = {
0x56, 0x44, 0x45, 0x4E, 0x47, 0x49, 0x4E, 0x45, // "VDENGINE"
0x4C, 0x49, 0x43, 0x45, 0x4E, 0x53, 0x45, 0x00, // "LICENSE\0"
0x53, 0x45, 0x43, 0x52, 0x45, 0x54, 0x4B, 0x45, // "SECRETKE"
0x59, 0x32, 0x30, 0x32, 0x35, 0x21, 0x21, 0x21, // "Y2025!!!"
};
static constexpr size_t HMAC_KEY_SIZE = sizeof(HMAC_KEY);
// ═══════════════════════════════════════════════════════════════
// parse_license_tier / license_tier_name (已在头文件 inline)
// ═══════════════════════════════════════════════════════════════
int parse_license_tier(const std::string& name) {
if (name == "Community" || name == "community") return 0;
if (name == "Professional" || name == "professional") return 1;
if (name == "Enterprise" || name == "enterprise") return 2;
return -1;
}
// ═══════════════════════════════════════════════════════════════
// Feature 辅助函数
// ═══════════════════════════════════════════════════════════════
const char* feature_name(Feature f) {
switch (f) {
case Feature::BASIC_BREP: return "BASIC_BREP";
case Feature::STEP_IMPORT_EXPORT: return "STEP_IMPORT_EXPORT";
case Feature::IGES_IMPORT_EXPORT: return "IGES_IMPORT_EXPORT";
case Feature::BASE_IO_FORMATS: return "BASE_IO_FORMATS";
case Feature::MESH_OPERATIONS: return "MESH_OPERATIONS";
case Feature::SKETCH_SOLVER: return "SKETCH_SOLVER";
case Feature::CAM_5AXIS: return "CAM_5AXIS";
case Feature::ADVANCED_SURFACING: return "ADVANCED_SURFACING";
case Feature::GD_T: return "GD_T";
case Feature::PMI_MBD: return "PMI_MBD";
case Feature::ASSEMBLY_DESIGN: return "ASSEMBLY_DESIGN";
case Feature::SHEET_METAL: return "SHEET_METAL";
case Feature::ADVANCED_HEALING: return "ADVANCED_HEALING";
case Feature::FEA_MESH: return "FEA_MESH";
case Feature::IGA_ANALYSIS: return "IGA_ANALYSIS";
case Feature::COLLISION_DETECTION: return "COLLISION_DETECTION";
case Feature::DIRECT_MODELING: return "DIRECT_MODELING";
case Feature::SURFACE_FAIRING: return "SURFACE_FAIRING";
case Feature::TECHNICAL_SUPPORT: return "TECHNICAL_SUPPORT";
case Feature::CUSTOM_FORMATS: return "CUSTOM_FORMATS";
case Feature::SCRIPTING_API: return "SCRIPTING_API";
case Feature::GPU_ACCELERATION: return "GPU_ACCELERATION";
case Feature::CLOUD_COLLABORATION: return "CLOUD_COLLABORATION";
case Feature::SDF_MODELING: return "SDF_MODELING";
case Feature::LICENSE_MANAGER_SDK: return "LICENSE_MANAGER_SDK";
}
return "UNKNOWN";
}
int parse_feature(const std::string& name) {
static const std::unordered_map<std::string, int> map = {
{"BASIC_BREP", 0}, {"STEP_IMPORT_EXPORT", 1},
{"IGES_IMPORT_EXPORT", 2}, {"BASE_IO_FORMATS", 3},
{"MESH_OPERATIONS", 4}, {"SKETCH_SOLVER", 5},
{"CAM_5AXIS", 10}, {"ADVANCED_SURFACING", 11},
{"GD_T", 12}, {"PMI_MBD", 13},
{"ASSEMBLY_DESIGN", 14}, {"SHEET_METAL", 15},
{"ADVANCED_HEALING", 16}, {"FEA_MESH", 17},
{"IGA_ANALYSIS", 18}, {"COLLISION_DETECTION",19},
{"DIRECT_MODELING", 20}, {"SURFACE_FAIRING", 21},
{"TECHNICAL_SUPPORT", 30}, {"CUSTOM_FORMATS", 31},
{"SCRIPTING_API", 32}, {"GPU_ACCELERATION", 33},
{"CLOUD_COLLABORATION",34}, {"SDF_MODELING", 35},
{"LICENSE_MANAGER_SDK",36},
};
auto it = map.find(name);
return (it != map.end()) ? it->second : -1;
}
std::set<Feature> features_for_tier(LicenseTier tier) {
std::set<Feature> feats;
// Community
feats.insert(Feature::BASIC_BREP);
feats.insert(Feature::STEP_IMPORT_EXPORT);
feats.insert(Feature::IGES_IMPORT_EXPORT);
feats.insert(Feature::BASE_IO_FORMATS);
feats.insert(Feature::MESH_OPERATIONS);
feats.insert(Feature::SKETCH_SOLVER);
if (tier == LicenseTier::Community) return feats;
// Professional
feats.insert(Feature::CAM_5AXIS);
feats.insert(Feature::ADVANCED_SURFACING);
feats.insert(Feature::GD_T);
feats.insert(Feature::PMI_MBD);
feats.insert(Feature::ASSEMBLY_DESIGN);
feats.insert(Feature::SHEET_METAL);
feats.insert(Feature::ADVANCED_HEALING);
feats.insert(Feature::FEA_MESH);
feats.insert(Feature::IGA_ANALYSIS);
feats.insert(Feature::COLLISION_DETECTION);
feats.insert(Feature::DIRECT_MODELING);
feats.insert(Feature::SURFACE_FAIRING);
if (tier == LicenseTier::Professional) return feats;
// Enterprise
feats.insert(Feature::TECHNICAL_SUPPORT);
feats.insert(Feature::CUSTOM_FORMATS);
feats.insert(Feature::SCRIPTING_API);
feats.insert(Feature::GPU_ACCELERATION);
feats.insert(Feature::CLOUD_COLLABORATION);
feats.insert(Feature::SDF_MODELING);
feats.insert(Feature::LICENSE_MANAGER_SDK);
return feats;
}
bool feature_available_in_tier(Feature f, LicenseTier tier) {
auto feats = features_for_tier(tier);
return feats.find(f) != feats.end();
}
// ═══════════════════════════════════════════════════════════════
// LicenseInfo 成员
// ═══════════════════════════════════════════════════════════════
bool LicenseInfo::is_expired() const {
if (expiry == 0) return false; // 永久
return std::time(nullptr) > expiry;
}
int LicenseInfo::days_until_expiry() const {
if (expiry == 0) return -1; // 永久
auto now = std::time(nullptr);
if (now >= expiry) return 0;
double diff = std::difftime(expiry, now);
return static_cast<int>(diff / 86400.0); // 秒转天
}
// ═══════════════════════════════════════════════════════════════
// Base32 RFC 4648 编解码
// ═══════════════════════════════════════════════════════════════
static int base32_char_to_value(char c) {
if (c >= 'A' && c <= 'Z') return c - 'A';
if (c >= 'a' && c <= 'z') return c - 'a';
if (c >= '2' && c <= '7') return c - '2' + 26;
return -1;
}
std::string LicenseKey::base32_encode(const std::vector<uint8_t>& data) {
std::string result;
result.reserve((data.size() * 8 + 4) / 5);
size_t bit_buf = 0;
int bit_count = 0;
for (uint8_t byte : data) {
bit_buf = (bit_buf << 8) | byte;
bit_count += 8;
while (bit_count >= 5) {
bit_count -= 5;
result.push_back(BASE32_ALPHABET[(bit_buf >> bit_count) & 0x1F]);
}
}
// 处理剩余位
if (bit_count > 0) {
result.push_back(BASE32_ALPHABET[(bit_buf << (5 - bit_count)) & 0x1F]);
}
return result;
}
std::vector<uint8_t> LicenseKey::base32_decode(const std::string& encoded) {
std::vector<uint8_t> result;
result.reserve(encoded.size() * 5 / 8);
size_t bit_buf = 0;
int bit_count = 0;
for (char c : encoded) {
int val = base32_char_to_value(c);
if (val < 0) continue; // 忽略无效字符(如连字符)
bit_buf = (bit_buf << 5) | static_cast<size_t>(val);
bit_count += 5;
while (bit_count >= 8) {
bit_count -= 8;
result.push_back(static_cast<uint8_t>((bit_buf >> bit_count) & 0xFF));
}
}
return result;
}
// ═══════════════════════════════════════════════════════════════
// HMAC-SHA256(简化版,使用固定密钥)
// ═══════════════════════════════════════════════════════════════
// 简化版 SHA256 — 完整实现但内联在此文件中
namespace {
struct Sha256Ctx {
uint32_t state[8];
uint64_t bit_count;
uint8_t buffer[64];
size_t buffer_len;
};
static const uint32_t SHA256_K[64] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786,
0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147,
0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13,
0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b,
0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a,
0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208,
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
inline uint32_t rotr32(uint32_t x, uint32_t n) {
return (x >> n) | (x << (32 - n));
}
static void sha256_transform(Sha256Ctx* ctx, const uint8_t data[64]) {
uint32_t w[64];
for (int i = 0; i < 16; ++i) {
w[i] = (static_cast<uint32_t>(data[i * 4]) << 24) |
(static_cast<uint32_t>(data[i * 4 + 1]) << 16) |
(static_cast<uint32_t>(data[i * 4 + 2]) << 8) |
(static_cast<uint32_t>(data[i * 4 + 3]));
}
for (int i = 16; i < 64; ++i) {
uint32_t s0 = rotr32(w[i - 15], 7) ^ rotr32(w[i - 15], 18) ^ (w[i - 15] >> 3);
uint32_t s1 = rotr32(w[i - 2], 17) ^ rotr32(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
uint32_t a = ctx->state[0], b = ctx->state[1], c = ctx->state[2], d = ctx->state[3];
uint32_t e = ctx->state[4], f = ctx->state[5], g = ctx->state[6], h = ctx->state[7];
for (int i = 0; i < 64; ++i) {
uint32_t S1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25);
uint32_t ch = (e & f) ^ (~e & g);
uint32_t temp1 = h + S1 + ch + SHA256_K[i] + w[i];
uint32_t S0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22);
uint32_t maj = (a & b) ^ (a & c) ^ (b & c);
uint32_t temp2 = S0 + maj;
h = g; g = f; f = e; e = d + temp1;
d = c; c = b; b = a; a = temp1 + temp2;
}
ctx->state[0] += a; ctx->state[1] += b; ctx->state[2] += c; ctx->state[3] += d;
ctx->state[4] += e; ctx->state[5] += f; ctx->state[6] += g; ctx->state[7] += h;
}
static void sha256_init(Sha256Ctx* ctx) {
ctx->state[0] = 0x6a09e667; ctx->state[1] = 0xbb67ae85;
ctx->state[2] = 0x3c6ef372; ctx->state[3] = 0xa54ff53a;
ctx->state[4] = 0x510e527f; ctx->state[5] = 0x9b05688c;
ctx->state[6] = 0x1f83d9ab; ctx->state[7] = 0x5be0cd19;
ctx->bit_count = 0;
ctx->buffer_len = 0;
}
static void sha256_update(Sha256Ctx* ctx, const uint8_t* data, size_t len) {
for (size_t i = 0; i < len; ++i) {
ctx->buffer[ctx->buffer_len++] = data[i];
if (ctx->buffer_len == 64) {
sha256_transform(ctx, ctx->buffer);
ctx->bit_count += 512;
ctx->buffer_len = 0;
}
}
}
static void sha256_final(Sha256Ctx* ctx, uint8_t hash[32]) {
uint64_t total_bits = ctx->bit_count + ctx->buffer_len * 8;
// 填充:0x80 后跟 0x00,然后是 64-bit 长度
ctx->buffer[ctx->buffer_len++] = 0x80;
if (ctx->buffer_len > 56) {
while (ctx->buffer_len < 64) ctx->buffer[ctx->buffer_len++] = 0;
sha256_transform(ctx, ctx->buffer);
ctx->buffer_len = 0;
}
while (ctx->buffer_len < 56) ctx->buffer[ctx->buffer_len++] = 0;
// 大端序写入长度
for (int i = 7; i >= 0; --i) {
ctx->buffer[56 + i] = static_cast<uint8_t>(total_bits & 0xFF);
total_bits >>= 8;
}
sha256_transform(ctx, ctx->buffer);
// 输出
for (int i = 0; i < 8; ++i) {
hash[i * 4] = static_cast<uint8_t>((ctx->state[i] >> 24) & 0xFF);
hash[i * 4 + 1] = static_cast<uint8_t>((ctx->state[i] >> 16) & 0xFF);
hash[i * 4 + 2] = static_cast<uint8_t>((ctx->state[i] >> 8) & 0xFF);
hash[i * 4 + 3] = static_cast<uint8_t>(ctx->state[i] & 0xFF);
}
}
} // anonymous namespace
std::vector<uint8_t> LicenseKey::compute_hmac(const std::vector<uint8_t>& data) {
constexpr size_t BLOCK_SIZE = 64; // SHA256 block size
// HMAC: H((K ^ opad) || H((K ^ ipad) || message))
uint8_t key_block[BLOCK_SIZE] = {};
// 如果 key 超过 block size,先哈希
if (HMAC_KEY_SIZE > BLOCK_SIZE) {
Sha256Ctx key_ctx;
sha256_init(&key_ctx);
sha256_update(&key_ctx, HMAC_KEY, HMAC_KEY_SIZE);
uint8_t key_hash[32];
sha256_final(&key_ctx, key_hash);
std::memcpy(key_block, key_hash, 32);
} else {
std::memcpy(key_block, HMAC_KEY, HMAC_KEY_SIZE);
}
// ipad
uint8_t ipad[BLOCK_SIZE];
for (size_t i = 0; i < BLOCK_SIZE; ++i) ipad[i] = key_block[i] ^ 0x36;
// opad
uint8_t opad[BLOCK_SIZE];
for (size_t i = 0; i < BLOCK_SIZE; ++i) opad[i] = key_block[i] ^ 0x5C;
// 内层哈希:H(ipad || data)
Sha256Ctx inner;
sha256_init(&inner);
sha256_update(&inner, ipad, BLOCK_SIZE);
sha256_update(&inner, data.data(), data.size());
uint8_t inner_hash[32];
sha256_final(&inner, inner_hash);
// 外层哈希:H(opad || inner_hash)
Sha256Ctx outer;
sha256_init(&outer);
sha256_update(&outer, opad, BLOCK_SIZE);
sha256_update(&outer, inner_hash, 32);
uint8_t outer_hash[32];
sha256_final(&outer, outer_hash);
// 返回完整 32 字节
return std::vector<uint8_t>(outer_hash, outer_hash + 32);
}
// ═══════════════════════════════════════════════════════════════
// LicenseKey 实现
// ═══════════════════════════════════════════════════════════════
/**
* @brief 构造 payload 字节序列
*
* 布局(17字节):
* [0]: version
* [1]: tier
* [2-9]: expiry (uint64_t big-endian)
* [10-16]: reserved (0)
*/
static std::vector<uint8_t> build_payload(int version, LicenseTier tier,
std::time_t expiry) {
std::vector<uint8_t> payload(PAYLOAD_SIZE, 0);
payload[0] = static_cast<uint8_t>(version);
payload[1] = static_cast<uint8_t>(static_cast<int>(tier));
// expiry 大端序写入 byte 2-9
uint64_t exp_val = static_cast<uint64_t>(expiry);
for (int i = 7; i >= 0; --i) {
payload[2 + i] = static_cast<uint8_t>(exp_val & 0xFF);
exp_val >>= 8;
}
return payload;
}
/**
* @brief 从 payload 解析 LicenseInfo
*/
static LicenseInfo parse_payload(const std::vector<uint8_t>& data) {
LicenseInfo info;
if (data.size() < PAYLOAD_SIZE) return info;
info.version = data[0];
info.tier = static_cast<LicenseTier>(std::min(static_cast<int>(data[1]), 2));
uint64_t exp_val = 0;
for (int i = 0; i < 8; ++i) {
exp_val = (exp_val << 8) | data[2 + i];
}
info.expiry = static_cast<std::time_t>(exp_val);
info.licensee = "Licensee"; // 未嵌入在 payload 中
info.features = features_for_tier(info.tier);
return info;
}
bool LicenseKey::parse(const std::string& key_str) {
key_string_ = key_str;
sig_valid_ = false;
// 检查前缀
if (key_str.size() < KEY_PREFIX_LEN + 3 ||
key_str.substr(0, KEY_PREFIX_LEN) != KEY_PREFIX) {
return false;
}
// 去掉 "VDE-" 前缀
std::string encoded = key_str.substr(KEY_PREFIX_LEN);
// 移除连字符
encoded.erase(std::remove(encoded.begin(), encoded.end(), '-'), encoded.end());
// Base32 解码
auto raw = base32_decode(encoded);
if (raw.size() < static_cast<size_t>(PAYLOAD_SIZE + SIGNATURE_SIZE)) {
return false;
}
// 分离 payload 和签名
std::vector<uint8_t> payload(raw.begin(), raw.begin() + PAYLOAD_SIZE);
std::vector<uint8_t> sig(raw.begin() + PAYLOAD_SIZE,
raw.begin() + PAYLOAD_SIZE + SIGNATURE_SIZE);
// 验证签名
auto expected_hmac = compute_hmac(payload);
sig_valid_ = (sig.size() == SIGNATURE_SIZE &&
std::equal(sig.begin(), sig.end(), expected_hmac.begin()));
// 解析 payload
info_ = parse_payload(payload);
info_.key_string = key_str;
return true;
}
std::string LicenseKey::generate(LicenseTier tier, std::time_t expiry,
const std::string& /*licensee*/) {
auto payload = build_payload(1, tier, expiry);
// 计算 HMAC 签名
auto hmac = compute_hmac(payload);
// 合并 payload + signature[0:4]
std::vector<uint8_t> combined(payload);
combined.insert(combined.end(), hmac.begin(), hmac.begin() + SIGNATURE_SIZE);
// Base32 编码
std::string encoded = base32_encode(combined);
// 格式化为 VDE-XXXX-XXXX-XXXX
std::ostringstream oss;
oss << KEY_PREFIX;
size_t pos = 0;
while (pos < encoded.size()) {
if (pos > 0) oss << '-';
size_t len = std::min<size_t>(4, encoded.size() - pos);
oss << encoded.substr(pos, len);
pos += len;
}
return oss.str();
}
bool LicenseKey::validate(const std::string& key_str) {
LicenseKey lk;
if (!lk.parse(key_str)) return false;
return lk.signature_valid() && !lk.info().is_expired();
}
// ═══════════════════════════════════════════════════════════════
// LicenseManager 单例
// ═══════════════════════════════════════════════════════════════
LicenseManager& LicenseManager::instance() {
static LicenseManager mgr;
return mgr;
}
LicenseManager::LicenseManager() {
// 默认:Community 社区版
current_.tier = LicenseTier::Community;
current_.licensee = "Community User";
current_.expiry = 0; // 永久
current_.features = features_for_tier(LicenseTier::Community);
current_.version = 1;
}
bool LicenseManager::set_license(const std::string& key) {
std::lock_guard<std::mutex> lock(mutex_);
LicenseKey lk;
if (!lk.parse(key)) return false;
if (!lk.signature_valid()) return false;
const auto& info = lk.info();
if (info.is_expired()) return false;
current_ = info;
trial_mode_ = false;
return true;
}
void LicenseManager::start_trial(int days) {
std::lock_guard<std::mutex> lock(mutex_);
trial_mode_ = true;
trial_start_ = std::time(nullptr);
trial_days_ = std::max(1, days);
current_.tier = LicenseTier::Professional; // 试用Professional
current_.licensee = "Trial User";
current_.expiry = trial_start_ + static_cast<std::time_t>(trial_days_) * 86400;
current_.features = features_for_tier(LicenseTier::Professional);
current_.version = 1;
}
LicenseTier LicenseManager::current_tier() const {
std::lock_guard<std::mutex> lock(mutex_);
return current_.tier;
}
bool LicenseManager::has_feature(const std::string& feature_name) const {
int fid = parse_feature(feature_name);
if (fid < 0) return false;
return has_feature(static_cast<Feature>(fid));
}
bool LicenseManager::has_feature(Feature f) const {
std::lock_guard<std::mutex> lock(mutex_);
// 检查是否过期
if (is_expired()) return false;
return current_.features.find(f) != current_.features.end();
}
bool LicenseManager::is_expired() const {
std::lock_guard<std::mutex> lock(mutex_);
if (current_.expiry == 0) return false; // 永久
return std::time(nullptr) > current_.expiry;
}
int LicenseManager::trial_days_left() const {
std::lock_guard<std::mutex> lock(mutex_);
if (!trial_mode_) return -1;
auto now = std::time(nullptr);
auto end = trial_start_ + static_cast<std::time_t>(trial_days_) * 86400;
if (now >= end) return 0;
double diff = std::difftime(end, now);
return static_cast<int>(diff / 86400.0);
}
const LicenseInfo& LicenseManager::current_license() const {
std::lock_guard<std::mutex> lock(mutex_);
return current_;
}
bool LicenseManager::is_trial() const {
std::lock_guard<std::mutex> lock(mutex_);
return trial_mode_;
}
} // namespace vde::core
+221
View File
@@ -0,0 +1,221 @@
/**
* @file error_codes.cpp
* @brief 统一错误码实现
*/
#include "vde/foundation/error_codes.h"
#include <unordered_map>
namespace vde::foundation {
// ═══════════════════════════════════════════════════════════════
// error_message — 中文描述
// ═══════════════════════════════════════════════════════════════
std::string error_message(ErrorCode code) {
static const std::unordered_map<ErrorCode, const char*> kMessages = {
// 成功
{ErrorCode::SUCCESS, "成功"},
// 1xxxfoundation 格式IO错误
{ErrorCode::ERR_FILE_NOT_FOUND, "文件未找到"},
{ErrorCode::ERR_FILE_OPEN_FAILED, "文件打开失败"},
{ErrorCode::ERR_FILE_WRITE_FAILED, "文件写入失败"},
{ErrorCode::ERR_FILE_READ_FAILED, "文件读取失败"},
{ErrorCode::ERR_FILE_PERMISSION_DENIED, "文件权限不足"},
{ErrorCode::ERR_FILE_TOO_LARGE, "文件过大"},
{ErrorCode::ERR_FILE_EMPTY, "文件为空"},
{ErrorCode::ERR_FILE_ALREADY_EXISTS, "文件已存在"},
{ErrorCode::ERR_FORMAT_INVALID, "格式无效"},
{ErrorCode::ERR_FORMAT_UNSUPPORTED, "格式不支持"},
{ErrorCode::ERR_FORMAT_VERSION_MISMATCH,"格式版本不匹配"},
{ErrorCode::ERR_FORMAT_CORRUPTED, "格式数据损坏"},
{ErrorCode::ERR_FORMAT_TRUNCATED, "格式数据截断"},
{ErrorCode::ERR_FORMAT_MAGIC_MISMATCH, "格式魔术字不匹配"},
{ErrorCode::ERR_VDE_HEADER_INVALID, "VDE文件头无效"},
{ErrorCode::ERR_VDE_SECTION_MISSING, "VDE段缺失"},
{ErrorCode::ERR_VDE_SECTION_CORRUPTED, "VDE段损坏"},
{ErrorCode::ERR_VDE_TOPOLOGY_INVALID, "VDE拓扑数据无效"},
{ErrorCode::ERR_VDE_GEOMETRY_INVALID, "VDE几何数据无效"},
{ErrorCode::ERR_VDE_SERIALIZE_FAILED, "VDE序列化失败"},
{ErrorCode::ERR_VDE_DESERIALIZE_FAILED, "VDE反序列化失败"},
{ErrorCode::ERR_STEP_PARSE, "STEP解析错误"},
{ErrorCode::ERR_STEP_SCHEMA_MISMATCH, "STEP模式不匹配"},
{ErrorCode::ERR_STEP_ENTITY_UNKNOWN, "STEP未知实体"},
{ErrorCode::ERR_STEP_TOPOLOGY, "STEP拓扑构建失败"},
{ErrorCode::ERR_STEP_GEOMETRY, "STEP几何构建失败"},
{ErrorCode::ERR_IGES_PARSE, "IGES解析错误"},
{ErrorCode::ERR_IGES_ENTITY_UNSUPPORTED,"IGES不支持的实体"},
{ErrorCode::ERR_OBJ_PARSE, "OBJ解析错误"},
{ErrorCode::ERR_STL_PARSE, "STL解析错误"},
{ErrorCode::ERR_STL_BINARY_INVALID, "STL二进制格式无效"},
{ErrorCode::ERR_PLY_PARSE, "PLY解析错误"},
{ErrorCode::ERR_GLTF_PARSE, "glTF解析错误"},
{ErrorCode::ERR_3MF_PARSE, "3MF解析错误"},
{ErrorCode::ERR_DXF_PARSE, "DXF解析错误"},
// 2xxxcore 几何计算错误
{ErrorCode::ERR_DEGENERATE_GEOMETRY, "退化几何"},
{ErrorCode::ERR_NUMERIC_INSTABILITY, "数值不稳定"},
{ErrorCode::ERR_DIVISION_BY_ZERO, "除零错误"},
{ErrorCode::ERR_SINGULAR_MATRIX, "奇异矩阵"},
{ErrorCode::ERR_CONVERGENCE_FAILURE, "收敛失败"},
{ErrorCode::ERR_OUT_OF_MEMORY, "内存不足"},
{ErrorCode::ERR_OVERFLOW, "数值溢出"},
{ErrorCode::ERR_PRECISION_LOSS, "精度丢失"},
{ErrorCode::ERR_BUFFER_TOO_SMALL, "缓冲区太小"},
{ErrorCode::ERR_INDEX_OUT_OF_BOUNDS, "索引越界"},
{ErrorCode::ERR_INTERSECTION_FAILED, "求交失败"},
{ErrorCode::ERR_PROJECTION_FAILED, "投影失败"},
{ErrorCode::ERR_DISTANCE_COMPUTE_FAILED,"距离计算失败"},
{ErrorCode::ERR_CONVEX_HULL_FAILED, "凸包计算失败"},
{ErrorCode::ERR_VORONOI_FAILED, "Voronoi计算失败"},
{ErrorCode::ERR_TRIANGULATION_FAILED, "三角剖分失败"},
{ErrorCode::ERR_SIMPLIFY_FAILED, "简化失败"},
{ErrorCode::ERR_CAM_TOOLPATH_GENERATION,"刀路生成失败"},
{ErrorCode::ERR_CAM_COLLISION_DETECTED, "刀具碰撞检测到"},
{ErrorCode::ERR_CAM_INVALID_STRATEGY, "无效的加工策略"},
{ErrorCode::ERR_CAM_5AXIS_LIMIT, "五轴极限超限"},
{ErrorCode::ERR_CAM_OPTIMIZATION_FAILED,"CAM优化失败"},
{ErrorCode::ERR_THREAD_CREATE_FAILED, "线程创建失败"},
{ErrorCode::ERR_LOCK_TIMEOUT, "锁超时"},
{ErrorCode::ERR_TASK_CANCELLED, "任务已取消"},
{ErrorCode::ERR_TRANSACTION_CONFLICT, "事务冲突"},
{ErrorCode::ERR_TRANSACTION_ROLLBACK, "事务回滚"},
// 3xxxcurves 曲线曲面错误
{ErrorCode::ERR_CURVE_DEGREE_INVALID, "曲线阶数无效"},
{ErrorCode::ERR_CURVE_KNOT_INVALID, "节点向量无效"},
{ErrorCode::ERR_CURVE_CP_COUNT_MISMATCH,"控制点数量不匹配"},
{ErrorCode::ERR_CURVE_EVALUATION_FAILED,"曲线求值失败"},
{ErrorCode::ERR_CURVE_DERIVATIVE_FAILED,"曲线求导失败"},
{ErrorCode::ERR_CURVE_PARAM_OUT_OF_RANGE,"参数超出范围"},
{ErrorCode::ERR_CURVE_SELF_INTERSECT, "曲线自交"},
{ErrorCode::ERR_CURVE_DISCONTINUITY, "曲线不连续"},
{ErrorCode::ERR_SURFACE_INVALID, "曲面无效"},
{ErrorCode::ERR_SURFACE_PARAM_RANGE, "曲面参数范围无效"},
{ErrorCode::ERR_SURFACE_DEGENERATE, "退化曲面"},
{ErrorCode::ERR_SURFACE_SINGULARITY, "曲面奇点"},
{ErrorCode::ERR_SURFACE_INTERSECTION, "曲面求交失败"},
{ErrorCode::ERR_SURFACE_CONTINUITY, "曲面连续性不满足"},
{ErrorCode::ERR_SURFACE_TRIMMING, "曲面裁剪失败"},
{ErrorCode::ERR_SURFACE_EXTENSION, "曲面延伸失败"},
{ErrorCode::ERR_SURFACE_OFFSET, "曲面偏置失败"},
{ErrorCode::ERR_SURFACE_FAIRING, "曲面光顺失败"},
{ErrorCode::ERR_IGA_MESH_INVALID, "IGA网格无效"},
{ErrorCode::ERR_IGA_REFINEMENT_FAILED, "IGA细化失败"},
// 4xxxmesh 网格错误
{ErrorCode::ERR_MESH_EMPTY, "网格为空"},
{ErrorCode::ERR_MESH_NON_MANIFOLD, "非流形网格"},
{ErrorCode::ERR_MESH_DEGENERATE_FACE, "退化面"},
{ErrorCode::ERR_MESH_SELF_INTERSECTION, "网格自交"},
{ErrorCode::ERR_MESH_INVERTED_NORMAL, "法线翻转"},
{ErrorCode::ERR_MESH_HOLE_DETECTED, "检测到孔洞"},
{ErrorCode::ERR_MESH_DISCONNECTED, "网格不连通"},
{ErrorCode::ERR_MESH_BOOLEAN_FAILED, "网格布尔失败"},
{ErrorCode::ERR_MESH_SIMPLIFY_FAILED, "网格简化失败"},
{ErrorCode::ERR_MESH_SMOOTH_FAILED, "网格平滑失败"},
{ErrorCode::ERR_MESH_REPAIR_FAILED, "网格修复失败"},
{ErrorCode::ERR_MESH_PARAMETERIZE_FAILED,"网格参数化失败"},
{ErrorCode::ERR_MESH_REMESH_FAILED, "网格重划分失败"},
{ErrorCode::ERR_MESH_SPLIT_FAILED, "网格分割失败"},
{ErrorCode::ERR_FEA_MESH_QUALITY, "FEA网格质量不足"},
{ErrorCode::ERR_FEA_MESH_GENERATION, "FEA网格生成失败"},
{ErrorCode::ERR_MARCHING_CUBES_FAILED, "Marching Cubes失败"},
{ErrorCode::ERR_POINT_CLOUD_EMPTY, "点云为空"},
{ErrorCode::ERR_POINT_CLOUD_NOISE, "点云噪声过大"},
// 5xxxbrep B-Rep错误
{ErrorCode::ERR_BREP_TOPOLOGY_INVALID, "B-Rep拓扑无效"},
{ErrorCode::ERR_BREP_GEOMETRY_MISMATCH, "B-Rep几何不匹配"},
{ErrorCode::ERR_BREP_SHELL_NOT_CLOSED, "壳不封闭"},
{ErrorCode::ERR_BREP_ORIENTATION, "方向不一致"},
{ErrorCode::ERR_BREP_SEWING_FAILED, "缝合失败"},
{ErrorCode::ERR_BREP_HEALING_FAILED, "修复失败"},
{ErrorCode::ERR_BREP_VALIDATION_FAILED, "验证失败"},
{ErrorCode::ERR_BOOLEAN_OPERATION_FAILED,"布尔操作失败"},
{ErrorCode::ERR_BOOLEAN_INTERSECT_FAILED,"布尔求交失败"},
{ErrorCode::ERR_BOOLEAN_SUBTRACT_FAILED,"布尔求差失败"},
{ErrorCode::ERR_BOOLEAN_UNION_FAILED, "布尔求并失败"},
{ErrorCode::ERR_BOOLEAN_DEGENERATE, "布尔操作产生退化"},
{ErrorCode::ERR_BOOLEAN_FALLBACK_USED, "布尔操作回退到备用方法"},
{ErrorCode::ERR_FEATURE_CREATION_FAILED,"特征创建失败"},
{ErrorCode::ERR_FEATURE_TREE_INVALID, "特征树无效"},
{ErrorCode::ERR_FEATURE_UPDATE_FAILED, "特征更新失败"},
{ErrorCode::ERR_FEATURE_RECOGNITION, "特征识别失败"},
{ErrorCode::ERR_ASSEMBLY_CONSTRAINT, "装配约束冲突"},
{ErrorCode::ERR_ASSEMBLY_INTERFERENCE, "装配干涉"},
{ErrorCode::ERR_ASSEMBLY_CYCLE, "装配循环引用"},
{ErrorCode::ERR_ASSEMBLY_MATE_FAILED, "配合失败"},
{ErrorCode::ERR_ASSEMBLY_LOD_INVALID, "LOD级别无效"},
{ErrorCode::ERR_SHEET_METAL_THICKNESS, "钣金厚度无效"},
{ErrorCode::ERR_SHEET_METAL_BEND_RADIUS,"弯曲半径无效"},
{ErrorCode::ERR_SHEET_METAL_UNFOLD, "展开失败"},
{ErrorCode::ERR_PMI_INVALID, "PMI无效"},
{ErrorCode::ERR_GDT_TOLERANCE, "GD&T公差错误"},
{ErrorCode::ERR_AUTO_DIM_FAILED, "自动标注失败"},
{ErrorCode::ERR_DRAWING_STANDARD, "制图标准错误"},
{ErrorCode::ERR_FFD_INVALID, "FFD变形无效"},
// 6xxxsdf SDF错误
{ErrorCode::ERR_SDF_EVALUATE_FAILED, "SDF求值失败"},
{ErrorCode::ERR_SDF_TREE_INVALID, "SDF树无效"},
{ErrorCode::ERR_SDF_TO_MESH_FAILED, "SDF转网格失败"},
{ErrorCode::ERR_SDF_OPTIMIZE_FAILED, "SDF优化失败"},
{ErrorCode::ERR_SDF_TORCH_ERROR, "SDF Torch推理错误"},
{ErrorCode::ERR_SDF_PRIMITIVE_INVALID, "SDF基元无效"},
// 7xxxcapi C API错误
{ErrorCode::ERR_CAPI_NULL_HANDLE, "空句柄"},
{ErrorCode::ERR_CAPI_INVALID_ARGUMENT, "无效参数"},
{ErrorCode::ERR_CAPI_TYPE_MISMATCH, "类型不匹配"},
{ErrorCode::ERR_CAPI_NOT_INITIALIZED, "引擎未初始化"},
{ErrorCode::ERR_CAPI_ALREADY_INITIALIZED,"引擎已初始化"},
{ErrorCode::ERR_CAPI_BUFFER_TOO_SMALL, "输出缓冲区太小"},
{ErrorCode::ERR_CAPI_FEATURE_NOT_AVAIL, "功能不可用"},
{ErrorCode::ERR_CAPI_UNKNOWN_ERROR, "未知C API错误"},
};
auto it = kMessages.find(code);
if (it != kMessages.end()) {
return it->second;
}
return "未知错误 (code=" + std::to_string(static_cast<int32_t>(code)) + ")";
}
// ═══════════════════════════════════════════════════════════════
// error_module — 模块名
// ═══════════════════════════════════════════════════════════════
std::string error_module(ErrorCode code) {
int32_t val = static_cast<int32_t>(code);
if (val == 0) return "core";
if (val >= 1000 && val < 2000) return "foundation";
if (val >= 2000 && val < 3000) return "core";
if (val >= 3000 && val < 4000) return "curves";
if (val >= 4000 && val < 5000) return "mesh";
if (val >= 5000 && val < 6000) return "brep";
if (val >= 6000 && val < 7000) return "sdf";
if (val >= 7000 && val < 8000) return "capi";
return "unknown";
}
} // namespace vde::foundation
+2
View File
@@ -13,3 +13,5 @@ add_vde_test(test_cam_optimization)
add_vde_test(test_performance)
add_vde_test(test_concurrent)
add_vde_test(test_transaction)
add_vde_test(test_version)
add_vde_test(test_license)
+396
View File
@@ -0,0 +1,396 @@
/**
* @file test_license.cpp
* @brief ViewDesignEngine 许可证管理单元测试
*/
#include <gtest/gtest.h>
#include "vde/core/license_manager.h"
#include <string>
#include <ctime>
using namespace vde::core;
// ═══════════════════════════════════════════════════════════════
// LicenseTier 测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseTier, NameConversion) {
EXPECT_STREQ(license_tier_name(LicenseTier::Community), "Community");
EXPECT_STREQ(license_tier_name(LicenseTier::Professional), "Professional");
EXPECT_STREQ(license_tier_name(LicenseTier::Enterprise), "Enterprise");
}
TEST(LicenseTier, ParseName) {
EXPECT_EQ(parse_license_tier("Community"), 0);
EXPECT_EQ(parse_license_tier("Professional"), 1);
EXPECT_EQ(parse_license_tier("Enterprise"), 2);
EXPECT_EQ(parse_license_tier("community"), 0);
EXPECT_EQ(parse_license_tier("unknown"), -1);
}
// ═══════════════════════════════════════════════════════════════
// Feature 枚举测试
// ═══════════════════════════════════════════════════════════════
TEST(FeatureEnum, FeatureName) {
EXPECT_STREQ(feature_name(Feature::BASIC_BREP), "BASIC_BREP");
EXPECT_STREQ(feature_name(Feature::CAM_5AXIS), "CAM_5AXIS");
EXPECT_STREQ(feature_name(Feature::TECHNICAL_SUPPORT), "TECHNICAL_SUPPORT");
}
TEST(FeatureEnum, ParseFeature) {
EXPECT_EQ(parse_feature("BASIC_BREP"), 0);
EXPECT_EQ(parse_feature("CAM_5AXIS"), 10);
EXPECT_EQ(parse_feature("TECHNICAL_SUPPORT"), 30);
EXPECT_EQ(parse_feature("NONEXISTENT"), -1);
}
// ═══════════════════════════════════════════════════════════════
// Tier → Feature 映射测试
// ═══════════════════════════════════════════════════════════════
TEST(TierFeatures, CommunityLevel) {
auto feats = features_for_tier(LicenseTier::Community);
EXPECT_GE(feats.size(), 6u); // 至少6个基础功能
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Community));
EXPECT_TRUE(feature_available_in_tier(Feature::STEP_IMPORT_EXPORT, LicenseTier::Community));
EXPECT_TRUE(feature_available_in_tier(Feature::IGES_IMPORT_EXPORT, LicenseTier::Community));
// Community 不应该有 Professional 功能
EXPECT_FALSE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Community));
EXPECT_FALSE(feature_available_in_tier(Feature::GD_T, LicenseTier::Community));
}
TEST(TierFeatures, ProfessionalLevel) {
auto feats = features_for_tier(LicenseTier::Professional);
EXPECT_GE(feats.size(), 12u);
// 包含 Community 的全部
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Professional));
// 包含 Professional 特有
EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::ADVANCED_SURFACING, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::GD_T, LicenseTier::Professional));
EXPECT_TRUE(feature_available_in_tier(Feature::SHEET_METAL, LicenseTier::Professional));
// 不应包含 Enterprise 功能
EXPECT_FALSE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Professional));
EXPECT_FALSE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Professional));
}
TEST(TierFeatures, EnterpriseLevel) {
auto feats = features_for_tier(LicenseTier::Enterprise);
EXPECT_GE(feats.size(), 18u);
// 全部功能
EXPECT_TRUE(feature_available_in_tier(Feature::BASIC_BREP, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::CAM_5AXIS, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::TECHNICAL_SUPPORT, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::CUSTOM_FORMATS, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::GPU_ACCELERATION, LicenseTier::Enterprise));
EXPECT_TRUE(feature_available_in_tier(Feature::LICENSE_MANAGER_SDK, LicenseTier::Enterprise));
}
// ═══════════════════════════════════════════════════════════════
// LicenseInfo 结构体测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseInfo, DefaultConstruction) {
LicenseInfo info;
EXPECT_EQ(info.tier, LicenseTier::Community);
EXPECT_EQ(info.licensee, "Trial User");
EXPECT_EQ(info.expiry, 0);
EXPECT_EQ(info.version, 1);
}
TEST(LicenseInfo, IsExpiredPermanent) {
LicenseInfo info;
info.expiry = 0; // 永久
EXPECT_FALSE(info.is_expired());
}
TEST(LicenseInfo, IsExpiredFuture) {
LicenseInfo info;
info.expiry = std::time(nullptr) + 86400 * 365; // 一年后
EXPECT_FALSE(info.is_expired());
}
TEST(LicenseInfo, IsExpiredPast) {
LicenseInfo info;
info.expiry = std::time(nullptr) - 86400; // 昨天
EXPECT_TRUE(info.is_expired());
}
TEST(LicenseInfo, DaysUntilExpiry) {
LicenseInfo info;
// 永久
info.expiry = 0;
EXPECT_EQ(info.days_until_expiry(), -1);
// 未来30天
info.expiry = std::time(nullptr) + 86400 * 30;
EXPECT_EQ(info.days_until_expiry(), 30);
// 未来1天
info.expiry = std::time(nullptr) + 86400;
EXPECT_EQ(info.days_until_expiry(), 1);
// 已过期
info.expiry = std::time(nullptr) - 86400;
EXPECT_EQ(info.days_until_expiry(), 0);
}
// ═══════════════════════════════════════════════════════════════
// LicenseKey 生成/解析/验证测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseKey, GenerateAndParse) {
// 生成一个 Community 永不过期的 Key
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "TestUser");
EXPECT_FALSE(key.empty());
EXPECT_EQ(key.substr(0, 4), "VDE-");
// 解析
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Community);
EXPECT_EQ(lk.info().expiry, 0);
}
TEST(LicenseKey, GenerateAndParseProfessional) {
std::time_t fut = std::time(nullptr) + 86400 * 365; // 一年后
std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "ProUser");
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Professional);
EXPECT_EQ(lk.info().expiry, fut);
}
TEST(LicenseKey, GenerateEnterprise) {
std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "EntUser");
LicenseKey lk;
EXPECT_TRUE(lk.parse(key));
EXPECT_TRUE(lk.signature_valid());
EXPECT_EQ(lk.info().tier, LicenseTier::Enterprise);
}
TEST(LicenseKey, Validate) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User");
EXPECT_TRUE(LicenseKey::validate(key));
}
TEST(LicenseKey, ValidateExpired) {
// 生成一个已过期的 Key
std::string key = LicenseKey::generate(LicenseTier::Community,
std::time(nullptr) - 86400, "User");
EXPECT_FALSE(LicenseKey::validate(key));
}
TEST(LicenseKey, InvalidFormat) {
EXPECT_FALSE(LicenseKey::validate(""));
EXPECT_FALSE(LicenseKey::validate("INVALID-KEY"));
EXPECT_FALSE(LicenseKey::validate("VDE-!!!!"));
}
TEST(LicenseKey, TamperedKey) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "User");
// 篡改 key(改一个字符)
if (key.size() > 6) {
key[6] = (key[6] == 'A') ? 'B' : 'A';
}
LicenseKey lk;
bool parsed = lk.parse(key);
if (parsed) {
// 签名验证应该失败
EXPECT_FALSE(lk.signature_valid());
}
// 无论如何,整体 validate 应该失败
EXPECT_FALSE(LicenseKey::validate(key));
}
TEST(LicenseKey, MultipleKeysAreDifferent) {
std::string k1 = LicenseKey::generate(LicenseTier::Community, 0, "U1");
std::string k2 = LicenseKey::generate(LicenseTier::Professional, 0, "U2");
EXPECT_NE(k1, k2);
}
TEST(LicenseKey, KeyStringStored) {
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "U");
LicenseKey lk;
lk.parse(key);
EXPECT_EQ(lk.key_string(), key);
}
// ═══════════════════════════════════════════════════════════════
// LicenseManager 单例测试
// ═══════════════════════════════════════════════════════════════
TEST(LicenseManager, Singleton) {
LicenseManager& mgr1 = LicenseManager::instance();
LicenseManager& mgr2 = LicenseManager::instance();
EXPECT_EQ(&mgr1, &mgr2);
}
TEST(LicenseManager, DefaultCommunity) {
auto& mgr = LicenseManager::instance();
EXPECT_EQ(mgr.current_tier(), LicenseTier::Community);
EXPECT_FALSE(mgr.is_expired());
EXPECT_FALSE(mgr.is_trial());
}
TEST(LicenseManager, HasFeatureBasic) {
auto& mgr = LicenseManager::instance();
// Community 默认应该有基础功能
EXPECT_TRUE(mgr.has_feature("BASIC_BREP"));
EXPECT_TRUE(mgr.has_feature("STEP_IMPORT_EXPORT"));
EXPECT_TRUE(mgr.has_feature(Feature::MESH_OPERATIONS));
}
TEST(LicenseManager, HasFeatureProfessional) {
auto& mgr = LicenseManager::instance();
// Community 不应该有 Professional 功能
EXPECT_FALSE(mgr.has_feature("CAM_5AXIS"));
EXPECT_FALSE(mgr.has_feature("GD_T"));
EXPECT_FALSE(mgr.has_feature("ADVANCED_SURFACING"));
}
TEST(LicenseManager, SetLicenseCommunity) {
auto& mgr = LicenseManager::instance();
// 保存原始 license
auto orig = mgr.current_license();
std::string key = LicenseKey::generate(LicenseTier::Community, 0, "Test");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Community);
EXPECT_FALSE(mgr.is_trial());
// 恢复原始
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseProfessional) {
auto& mgr = LicenseManager::instance();
std::time_t fut = std::time(nullptr) + 86400 * 365;
std::string key = LicenseKey::generate(LicenseTier::Professional, fut, "Pro");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional);
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
EXPECT_TRUE(mgr.has_feature("GD_T"));
// 恢复默认
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseEnterprise) {
auto& mgr = LicenseManager::instance();
std::string key = LicenseKey::generate(LicenseTier::Enterprise, 0, "Ent");
EXPECT_TRUE(mgr.set_license(key));
EXPECT_EQ(mgr.current_tier(), LicenseTier::Enterprise);
EXPECT_TRUE(mgr.has_feature("TECHNICAL_SUPPORT"));
EXPECT_TRUE(mgr.has_feature("GPU_ACCELERATION"));
EXPECT_TRUE(mgr.has_feature("CUSTOM_FORMATS"));
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, SetLicenseExpired) {
auto& mgr = LicenseManager::instance();
// 先生成一个有效 key 确保非过期状态
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
// 生成已过期的 key
std::string key = LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) - 86400, "Expired");
// 已过期 key 应该被拒绝
EXPECT_FALSE(mgr.set_license(key));
}
TEST(LicenseManager, SetLicenseInvalid) {
auto& mgr = LicenseManager::instance();
EXPECT_FALSE(mgr.set_license("INVALID-KEY"));
EXPECT_FALSE(mgr.set_license(""));
}
TEST(LicenseManager, TrialMode) {
auto& mgr = LicenseManager::instance();
// 先设置永久 Community key
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
mgr.start_trial(30);
EXPECT_TRUE(mgr.is_trial());
EXPECT_EQ(mgr.current_tier(), LicenseTier::Professional);
EXPECT_EQ(mgr.trial_days_left(), 30);
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
// 但也应该有基础功能
EXPECT_TRUE(mgr.has_feature("BASIC_BREP"));
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
EXPECT_FALSE(mgr.is_trial());
}
TEST(LicenseManager, TrialDaysLeftVarious) {
auto& mgr = LicenseManager::instance();
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
// 没有试用时
EXPECT_EQ(mgr.trial_days_left(), -1);
// 30天试用
mgr.start_trial(30);
EXPECT_GE(mgr.trial_days_left(), 29); // 可能刚好跨天
EXPECT_LE(mgr.trial_days_left(), 30);
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Reset"));
}
TEST(LicenseManager, CurrentLicense) {
auto& mgr = LicenseManager::instance();
std::string key = LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) + 86400 * 90, "TestCo");
mgr.set_license(key);
const auto& info = mgr.current_license();
EXPECT_EQ(info.tier, LicenseTier::Professional);
EXPECT_EQ(info.licensee, "Licensee"); // payload 中无 licensee 字段
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
TEST(LicenseManager, HasFeatureInvalidName) {
auto& mgr = LicenseManager::instance();
EXPECT_FALSE(mgr.has_feature("NONEXISTENT_FEATURE"));
EXPECT_FALSE(mgr.has_feature(""));
}
TEST(LicenseManager, ExpiredLicenseBlocksFeatures) {
auto& mgr = LicenseManager::instance();
// 恢复有效状态
mgr.set_license(LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) + 86400 * 365, "Reset"));
EXPECT_TRUE(mgr.has_feature("CAM_5AXIS"));
// 设置已过期的 license
mgr.set_license(LicenseKey::generate(LicenseTier::Professional,
std::time(nullptr) - 86400, "Expired"));
// 过期 license 不会被设置(set_license 返回 false),所以状态不变
// 但如果 is_expired() 返回 truehas_feature 也会 false
// 恢复
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
}
+130
View File
@@ -0,0 +1,130 @@
/**
* @file test_version.cpp
* @brief ViewDesignEngine 版本信息单元测试
*/
#include <gtest/gtest.h>
#include "vde/core/version.h"
#include <string>
using namespace vde::core;
// ═══════════════════════════════════════════════════════════════
// 编译时版本宏测试
// ═══════════════════════════════════════════════════════════════
TEST(VersionMacro, MajorVersion) {
EXPECT_EQ(VDE_VERSION_MAJOR, 1);
}
TEST(VersionMacro, MinorVersion) {
EXPECT_EQ(VDE_VERSION_MINOR, 0);
}
TEST(VersionMacro, PatchVersion) {
EXPECT_EQ(VDE_VERSION_PATCH, 1);
}
TEST(VersionMacro, VersionString) {
EXPECT_STREQ(VDE_VERSION_STRING, "1.0.1");
}
TEST(VersionMacro, VersionNum) {
EXPECT_EQ(VDE_VERSION_NUM, 10001); // 1*10000 + 0*100 + 1
}
TEST(VersionMacro, VersionCodeFunction) {
EXPECT_EQ(VDE_VERSION_CODE(2, 3, 4), 20304);
EXPECT_EQ(VDE_VERSION_CODE(0, 0, 1), 1);
EXPECT_EQ(VDE_VERSION_CODE(10, 20, 30), 102030);
}
// ═══════════════════════════════════════════════════════════════
// 构建信息测试
// ═══════════════════════════════════════════════════════════════
TEST(BuildInfo, BuildTypeDefined) {
EXPECT_TRUE(std::string(VDE_BUILD_TYPE).size() > 0);
}
TEST(BuildInfo, CompilerInfoDefined) {
EXPECT_TRUE(std::string(VDE_COMPILER_INFO).size() > 0);
}
TEST(BuildInfo, CppStandardString) {
std::string cpp = VDE_CPP_STANDARD_STRING;
EXPECT_TRUE(cpp == "C++20" || cpp == "C++17" || cpp == "C++14" ||
cpp == "C++11" || cpp == "C++03");
// 项目要求 C++20
EXPECT_EQ(cpp, "C++20");
}
// ═══════════════════════════════════════════════════════════════
// VersionInfo 结构体测试
// ═══════════════════════════════════════════════════════════════
TEST(VersionInfo, DefaultConstruction) {
VersionInfo info = vde_version();
EXPECT_EQ(info.major, 1);
EXPECT_EQ(info.minor, 0);
EXPECT_EQ(info.patch, 1);
EXPECT_EQ(info.version_string, "1.0.1");
EXPECT_FALSE(info.build_type.empty());
EXPECT_FALSE(info.compiler_info.empty());
EXPECT_EQ(info.cpp_standard, "C++20");
}
TEST(VersionInfo, BuildTimestamp) {
VersionInfo info = vde_version();
// 时间戳可能为 unknown 或 ISO 8601 格式
EXPECT_FALSE(info.build_timestamp.empty());
}
// ═══════════════════════════════════════════════════════════════
// vde_version_string 测试
// ═══════════════════════════════════════════════════════════════
TEST(VersionString, Format) {
std::string vs = vde_version_string();
// 格式:ViewDesignEngine v1.0.1 (C++20, GCC x.x)
EXPECT_TRUE(vs.find("ViewDesignEngine v1.0.1") != std::string::npos);
EXPECT_TRUE(vs.find("C++20") != std::string::npos);
EXPECT_TRUE(vs.find("(") != std::string::npos);
EXPECT_TRUE(vs.find(")") != std::string::npos);
}
TEST(VersionString, NonEmpty) {
EXPECT_FALSE(vde_version_string().empty());
EXPECT_GT(vde_version_string().size(), 20u);
}
// ═══════════════════════════════════════════════════════════════
// vde_version_compare 测试
// ═══════════════════════════════════════════════════════════════
TEST(VersionCompare, Equality) {
EXPECT_EQ(vde_version_compare(1, 0, 1, 1, 0, 1), 0);
EXPECT_EQ(vde_version_compare(2, 0, 0, 2, 0, 0), 0);
}
TEST(VersionCompare, Less) {
EXPECT_LT(vde_version_compare(1, 0, 0, 1, 0, 1), 0);
EXPECT_LT(vde_version_compare(1, 0, 0, 2, 0, 0), 0);
EXPECT_LT(vde_version_compare(1, 9, 9, 2, 0, 0), 0);
}
TEST(VersionCompare, Greater) {
EXPECT_GT(vde_version_compare(2, 0, 0, 1, 0, 0), 0);
EXPECT_GT(vde_version_compare(1, 0, 2, 1, 0, 1), 0);
EXPECT_GT(vde_version_compare(1, 1, 0, 1, 0, 99), 0);
}
TEST(VersionCompare, Transitivity) {
// 1.0.0 < 1.0.1 < 1.1.0 < 2.0.0
int cmp1 = vde_version_compare(1, 0, 0, 1, 0, 1);
int cmp2 = vde_version_compare(1, 0, 1, 1, 1, 0);
int cmp3 = vde_version_compare(1, 1, 0, 2, 0, 0);
EXPECT_LT(cmp1, 0);
EXPECT_LT(cmp2, 0);
EXPECT_LT(cmp3, 0);
}