feat(v1.0.1): tangent_pull + error codes + version + license management
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:
@@ -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)
|
||||
|
||||
@@ -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@"
|
||||
@@ -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
|
||||
|
||||
@@ -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-XXXX(Base32编码)
|
||||
* 包含:版本号 + 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, ///< 基础格式IO(OBJ/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
|
||||
@@ -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
|
||||
@@ -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 — brep(B-Rep错误)
|
||||
* 6xxx — sdf(SDF错误)
|
||||
* 7xxx — capi(C 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,
|
||||
|
||||
// ── 1xxx:foundation 格式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, // 点云噪声过大
|
||||
|
||||
// ── 5xxx:brep 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基元无效
|
||||
|
||||
// ── 7xxx:capi 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
|
||||
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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, "成功"},
|
||||
|
||||
// 1xxx:foundation 格式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解析错误"},
|
||||
|
||||
// 2xxx:core 几何计算错误
|
||||
{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, "事务回滚"},
|
||||
|
||||
// 3xxx:curves 曲线曲面错误
|
||||
{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细化失败"},
|
||||
|
||||
// 4xxx:mesh 网格错误
|
||||
{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, "点云噪声过大"},
|
||||
|
||||
// 5xxx:brep 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变形无效"},
|
||||
|
||||
// 6xxx:sdf 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基元无效"},
|
||||
|
||||
// 7xxx:capi 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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() 返回 true,has_feature 也会 false
|
||||
|
||||
// 恢复
|
||||
mgr.set_license(LicenseKey::generate(LicenseTier::Community, 0, "Community User"));
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user