feat(v6.0): FFD freeform deformation + auto-dimensioning + PMI/MBD + Web viewer
CI / Build & Test (push) Failing after 33s
CI / Release Build (push) Failing after 32s
Build & Test / build-and-test (push) Has been cancelled
Build & Test / python-bindings (push) Has been cancelled

v6.0.1 — FFD Freeform Deformation:
- ffd_deformation.h/.cpp: Bezier volume lattice, deform_brep/deform_mesh
- Bernstein polynomial mapping, topology-preserving
- Cage-based + lattice-based FFD, create_cage_lattice()

v6.0.2 — Auto-Dimensioning + PMI/MBD + GD&T:
- auto_dimensioning.h/.cpp: linear/angular/radial/diameter detection
- ISO/ANSI/JIS drawing standards, smart label placement
- gdt.h/.cpp: 14 GD&T symbols (ASME Y14.5), MMC/LMC/RFS material conditions
- pmi_mbd.h/.cpp: PMIAnnotation, attach_pmi, PMIView, STEP AP242 export
- 70/70 tests passing (23 auto-dim + 25 PMI + 22 GD&T regression)

v6.0.3 — Web 3D Viewer Enhancement:
- viewer/index.html + app.js: Three.js GLB/STL loading
- OrbitControls, face coloring, dimension overlay, GD&T display
- Measure tool (raycaster), clip plane slider, explode animation
- Drag-and-drop file upload, responsive layout
This commit is contained in:
茂之钳
2026-07-26 21:55:16 +08:00
parent b874741739
commit 11b606bd39
16 changed files with 5256 additions and 118 deletions
+230
View File
@@ -0,0 +1,230 @@
#pragma once
/**
* @file auto_dimensioning.h
* @brief Automatic dimensioning and drawing standards
*
* Automatically generates linear, angular, radius/diameter dimensions
* from B-Rep models. Supports ISO, ANSI, and JIS drawing standards.
*
* @ingroup brep
*/
#include "vde/brep/brep.h"
#include "vde/core/point.h"
#include <vector>
#include <string>
#include <map>
namespace vde::brep {
// ═══════════════════════════════════════════════════════════
// Drawing Standard
// ═══════════════════════════════════════════════════════════
/// Drawing standard enumeration
enum class DrawingStandard {
ISO, ///< ISO 129 / ISO 1101
ANSI, ///< ANSI Y14.5 / ASME Y14.5
JIS, ///< JIS B 0001 / JIS B 0021
};
// ═══════════════════════════════════════════════════════════
// Dimension Types
// ═══════════════════════════════════════════════════════════
/// Dimension type enumeration
enum class DimensionType {
Linear, ///< Distance between parallel edges
Angular, ///< Angle between non-parallel edges
Radius, ///< Arc/circle radius
Diameter, ///< Circle diameter
Ordinate, ///< Ordinate dimension (baseline)
};
/// Material condition modifier for dimensions
enum class MaterialCondition {
RFS, ///< Regardless of Feature Size (default)
MMC, ///< Maximum Material Condition
LMC, ///< Least Material Condition
};
// ═══════════════════════════════════════════════════════════
// Dimension Data Structures
// ═══════════════════════════════════════════════════════════
/// A single dimension annotation
struct Dimension {
DimensionType type = DimensionType::Linear;
double value = 0.0; ///< Nominal dimension value
double tolerance_upper = 0.0; ///< Upper tolerance deviation
double tolerance_lower = 0.0; ///< Lower tolerance deviation
MaterialCondition mat_condition = MaterialCondition::RFS;
/// 2D placement position (in view coordinates)
core::Point3D text_position;
/// Reference points (2 in view coordinates for linear/angular, center for radial)
std::vector<core::Point3D> reference_points;
/// IDs of associated B-Rep edges/faces
std::vector<int> associated_edges;
std::vector<int> associated_faces;
/// Dimension text override (empty = auto-generated from value)
std::string text_override;
/// View this dimension belongs to
std::string view_name;
/// Prefix/suffix for dimension text
std::string prefix; ///< e.g. "Ø", "R", "2×"
std::string suffix; ///< e.g. " THRU", " TYP"
/// Check if this dimension has tolerance
[[nodiscard]] bool has_tolerance() const {
return tolerance_upper != 0.0 || tolerance_lower != 0.0;
}
/// Get formatted dimension text
[[nodiscard]] std::string to_text() const;
/// Get dimension text suitable for DXF
[[nodiscard]] std::string to_dxf_text() const;
};
// ═══════════════════════════════════════════════════════════
// Drawing Configuration
// ═══════════════════════════════════════════════════════════
/// Standard-specific configuration parameters
struct StandardConfig {
DrawingStandard standard = DrawingStandard::ISO;
double text_height = 3.5; ///< Default text height (mm)
double arrow_size = 3.0; ///< Arrow size (mm)
double extension_line_offset = 1.0; ///< Offset from feature to extension line start
double extension_line_overshoot = 2.0; ///< Overshoot beyond dimension line
double dimension_line_spacing = 10.0; ///< Spacing between dimension lines
double baseline_spacing = 7.0; ///< Spacing for baseline dimensions
std::string unit = "mm"; ///< Default unit
int decimal_places = 2; ///< Number of decimal places
bool trailing_zeros = false; ///< Show trailing zeros (ANSI)
bool leading_zero = true; ///< Show leading zero (ISO)
/// Get standard name
[[nodiscard]] std::string standard_name() const;
};
/// Drawing settings container for standard compliance
struct DrawingSettings {
StandardConfig config;
std::vector<Dimension> dimensions;
std::string title;
std::string drawing_number;
std::string revision;
double scale = 1.0;
std::string projection_method = "first"; ///< "first" or "third" angle projection
};
// ═══════════════════════════════════════════════════════════
// Auto-Dimensioning API
// ═══════════════════════════════════════════════════════════
/**
* @brief Automatically generate dimensions from a B-Rep model
*
* Detects linear distances (parallel edges), angles (non-parallel edges),
* radius/diameter (arcs/circles), and places them intelligently to avoid
* overlap based on the given view direction.
*
* @param model B-Rep model to dimension
* @param view_direction View direction for dimension placement
* @return Vector of auto-generated dimensions
*/
[[nodiscard]] std::vector<Dimension> auto_dimension(
const BrepModel& model,
const core::Vector3D& view_direction);
/**
* @brief Auto-dimension with configuration control
*
* @param model B-Rep model
* @param view_direction View direction
* @param config Drawing standard configuration
* @return Vector of dimensions
*/
[[nodiscard]] std::vector<Dimension> auto_dimension_with_config(
const BrepModel& model,
const core::Vector3D& view_direction,
const StandardConfig& config);
/**
* @brief Apply a drawing standard to a set of dimensions
*
* Formats dimensions according to ISO/ANSI/JIS standards,
* adjusting tolerances, decimal places, text positions, etc.
*
* @param drawing Drawing settings to modify
* @param standard Target standard
*/
void apply_standard(DrawingSettings& drawing, DrawingStandard standard);
/**
* @brief Apply standard with custom overrides
*
* @param drawing Drawing settings to modify
* @param standard Target standard
* @param overrides Custom config values merged with standard defaults
*/
void apply_standard_with_overrides(DrawingSettings& drawing,
DrawingStandard standard,
const StandardConfig& overrides);
/**
* @brief Get default configuration for a drawing standard
*
* @param standard The standard
* @return Default StandardConfig
*/
[[nodiscard]] StandardConfig standard_defaults(DrawingStandard standard);
// ═══════════════════════════════════════════════════════════
// Dimension Placement Helpers
// ═══════════════════════════════════════════════════════════
/**
* @brief Place dimensions avoiding overlaps (smart placement)
*
* Rearranges dimension text positions so they don't overlap.
*
* @param dimensions Dimensions to arrange (modified in-place)
* @param view_direction View direction for projection
*/
void smart_placement(std::vector<Dimension>& dimensions,
const core::Vector3D& view_direction);
/**
* @brief Create baseline dimensions from reference face
*
* @param model B-Rep model
* @param reference_face_id Reference face for baseline
* @param view_direction View direction
* @return Vector of ordinate dimensions from baseline
*/
[[nodiscard]] std::vector<Dimension> baseline_dimensions(
const BrepModel& model,
int reference_face_id,
const core::Vector3D& view_direction);
/**
* @brief Create chain dimensions along a feature
*
* @param model B-Rep model
* @param face_ids Sequence of face IDs to chain
* @param view_direction View direction
* @return Vector of chained dimensions
*/
[[nodiscard]] std::vector<Dimension> chain_dimensions(
const BrepModel& model,
const std::vector<int>& face_ids,
const core::Vector3D& view_direction);
} // namespace vde::brep
+3
View File
@@ -285,6 +285,9 @@ public:
/** @brief 按数组索引访问顶点 */
[[nodiscard]] const TopoVertex& vertex(int id) const { return vertices_[id]; }
/** @brief 更新顶点位置(按数组索引) */
void set_vertex_point(int idx, const Point3D& p) { vertices_[idx].point = p; }
/** @brief 按唯一 ID 访问顶点(适用于边数据中的 ID 引用) */
[[nodiscard]] const TopoVertex& vertex_by_id(int id) const;
+200
View File
@@ -0,0 +1,200 @@
#pragma once
/**
* @file ffd_deformation.h
* @brief Freeform Deformation (FFD) — 自由变形
*
* FFD 是一种基于控制格 (lattice) 的体积变形技术,可以将任意
* B-Rep 实体或网格进行拉伸、压缩、扭转等自由变形,保持拓扑不变。
*
* ## 核心概念
*
* - **FFD Lattice**: nx×ny×nz 的 Bézier 体积控制点网格。
* - **参数空间映射**: 将世界坐标点映射到 lattice 的 (s,t,u) 参数空间。
* - **Bernstein 多项式**: 用 Bernstein 基函数计算变形后的位置。
*
* ## FFD 类型
*
* | 类型 | 描述 |
* |--------------|----------------------------------------|
* | Lattice-based | 规则矩形网格,参数空间 [0,1]³ |
* | Cage-based | 任意包围多面体,自动生成 lattice 控制点 |
*
* ## 使用示例
*
* @code{.cpp}
* auto box = make_box(10, 5, 3);
* auto bbox = box.bounds();
* auto lattice = create_lattice(bbox, 3, 3, 3); // 3×3×3 控制点
*
* // 拉伸:移动顶面控制点
* std::vector<Vector3D> displacements(27);
* for (int i = 0; i < 3; ++i)
* for (int j = 0; j < 3; ++j)
* displacements[i*9 + j*3 + 2] = Vector3D(0, 0, 3.0); // z方向位移
*
* auto deformed = deform_brep(box, lattice, displacements);
* @endcode
*
* @ingroup brep
*/
#include "vde/core/point.h"
#include "vde/core/aabb.h"
#include "vde/brep/brep.h"
#include "vde/mesh/halfedge_mesh.h"
#include <vector>
namespace vde::brep {
using core::Point3D;
using core::Vector3D;
using core::AABB3D;
// ═══════════════════════════════════════════════
// FFD Lattice
// ═══════════════════════════════════════════════
/**
* @brief FFD 控制格结构
*
* 一个 nx×ny×nz 的 Bézier 体积控制点网格。
* 控制点存储在 control_points 中,按 k-major、j-major、i-minor 排列:
* index = (k * ny + j) * nx + i
*
* bbox 定义了 lattice 在世界空间中的边界,用于世界坐标 → 参数空间的映射。
*/
struct FFDLattice {
int nx = 0; ///< X 方向控制点数
int ny = 0; ///< Y 方向控制点数
int nz = 0; ///< Z 方向控制点数
std::vector<Point3D> control_points; ///< 控制点坐标 [nx*ny*nz]
AABB3D bbox; ///< 世界空间包围盒
/**
* @brief 总控制点数
*/
[[nodiscard]] size_t num_points() const { return control_points.size(); }
/**
* @brief 获取控制点
* @param i X 索引 (0..nx-1)
* @param j Y 索引 (0..ny-1)
* @param k Z 索引 (0..nz-1)
*/
[[nodiscard]] const Point3D& control_point(int i, int j, int k) const {
return control_points[(k * ny + j) * nx + i];
}
/** @brief 检查 lattice 是否有效 */
[[nodiscard]] bool is_valid() const {
return nx >= 2 && ny >= 2 && nz >= 2 &&
control_points.size() == static_cast<size_t>(nx * ny * nz);
}
};
/**
* @brief 创建规则格子 lattice
*
* 在给定包围盒内生成 nx×ny×nz 均匀分布的控制点网格。
*
* @param bbox 世界空间包围盒
* @param nx X 方向控制点数(≥ 2)
* @param ny Y 方向控制点数(≥ 2)
* @param nz Z 方向控制点数(≥ 2)
* @return FFDLattice 均匀控制格
*/
[[nodiscard]] FFDLattice create_lattice(const AABB3D& bbox, int nx, int ny, int nz);
/**
* @brief 从包围多面体生成 lattice (Cage-based FFD)
*
* 从一组顶点(包围多面体/cage 的顶点)计算包围盒,
* 生成 resolution 大小的均匀控制格。
*
* @param cage_vertices 包围多面体的顶点坐标
* @param nx X 方向分辨率(≥ 2
* @param ny Y 方向分辨率(≥ 2
* @param nz Z 方向分辨率(≥ 2
* @return FFDLattice 包围多面体的控制格
*/
[[nodiscard]] FFDLattice create_cage_lattice(const std::vector<Point3D>& cage_vertices,
int nx, int ny, int nz);
// ═══════════════════════════════════════════════
// Deformation API
// ═══════════════════════════════════════════════
/**
* @brief 对 B-Rep 实体进行 FFD 变形
*
* 将 B-Rep 的所有顶点映射到 lattice 参数空间,使用 Bernstein 多项式
* 计算变形后的位置。拓扑结构和面/边/壳关系保持不变。
*
* 算法:
* 1. 复制 BrepModel(保留拓扑结构)
* 2. 对每个顶点:
* a. 映射世界坐标到 lattice 参数空间 (s,t,u) ∈ [0,1]³
* b. 用 Bernstein 基函数求值,计算变形位移
* c. 应用位移到顶点位置
* 3. 返回变形后的 BrepModel
*
* @param body 输入 B-Rep 实体
* @param lattice FFD 控制格
* @param displacements 每个控制点的位移向量 [nx*ny*nz]
* @return 变形后的 B-Rep 实体(拓扑不变)
*
* @pre displacements.size() == lattice.num_points()
* @pre lattice.is_valid()
*/
[[nodiscard]] BrepModel deform_brep(const BrepModel& body,
const FFDLattice& lattice,
const std::vector<Vector3D>& displacements);
/**
* @brief 对三角网格进行 FFD 变形
*
* 将网格的所有顶点映射到 lattice 参数空间,用 Bernstein 多项式
* 计算变形后的新位置。
*
* @param mesh 输入三角网格
* @param lattice FFD 控制格
* @param displacements 每个控制点的位移向量 [nx*ny*nz]
* @return 变形后的网格(拓扑不变)
*
* @pre displacements.size() == lattice.num_points()
* @pre lattice.is_valid()
*/
[[nodiscard]] mesh::HalfedgeMesh deform_mesh(const mesh::HalfedgeMesh& mesh,
const FFDLattice& lattice,
const std::vector<Vector3D>& displacements);
// ═══════════════════════════════════════════════
// Internal: Bernstein evaluation
// ═══════════════════════════════════════════════
/**
* @brief Bernstein 基函数 B_{i,n}(t)
*
* B_{i,n}(t) = C(n,i) * t^i * (1-t)^{n-i}
*
* @param i 基函数索引 (0..n)
* @param n 多项式阶数
* @param t 参数值 ∈ [0,1]
* @return 基函数值
*/
[[nodiscard]] double bernstein(int i, int n, double t);
/**
* @brief 用 FFD lattice 对单个点进行变形计算
*
* 将世界坐标点映射到 lattice 参数空间,计算变形位移并应用。
*
* @param point 输入世界坐标点
* @param lattice FFD 控制格
* @param displacements 控制点位移向量
* @return 变形后的点坐标
*/
[[nodiscard]] Point3D deform_point(const Point3D& point,
const FFDLattice& lattice,
const std::vector<Vector3D>& displacements);
} // namespace vde::brep
+208 -12
View File
@@ -48,20 +48,29 @@ enum class GDTType {
TotalRunout, ///< 全跳动
};
// ═══════════════════════════════════════════════════════════
// Datum Reference
// ═══════════════════════════════════════════════════════════
/// 基准特征引用
struct DatumReference {
std::string label; ///< 基准标签(A, B, C...
int face_id = -1; ///< 关联的 B-Rep 面 ID
bool is_primary = false; ///< 是否主基准
/// Material condition for tolerance / datum modifiers
enum class GDTMaterialCondition {
RFS, ///< Regardless of Feature Size — no modifier
MMC, ///< Maximum Material Condition — Ⓜ
LMC, ///< Least Material Condition — Ⓛ
};
// ═══════════════════════════════════════════════════════════
// GD&T Feature Control Frame
// ═══════════════════════════════════════════════════════════
/// Datum reference with material condition
struct DatumReference {
std::string label; ///< Datum label (A, B, C...)
int face_id = -1; ///< Associated B-Rep face ID
bool is_primary = false; ///< Primary datum
GDTMaterialCondition material_condition = GDTMaterialCondition::RFS;
/// Constructor for backward compat (label, face_id, is_primary)
DatumReference() = default;
DatumReference(const std::string& l, int f, bool p)
: label(l), face_id(f), is_primary(p) {}
DatumReference(const std::string& l, int f, bool p, GDTMaterialCondition mc)
: label(l), face_id(f), is_primary(p), material_condition(mc) {}
[[nodiscard]] std::string to_string() const;
};
/// 特征控制框(一个完整的 GD&T 标注)
struct GDTFeature {
@@ -200,4 +209,191 @@ struct GDTAnnotation {
[[nodiscard]] std::string export_gdt_annotations_dxf(
const std::vector<GDTAnnotation>& annotations);
// ═══════════════════════════════════════════════════════════
// GDTCallout — Complete feature control frame
// ═══════════════════════════════════════════════════════════
/// Tolerance value with optional material condition modifier
struct GDTToleranceValue {
double value = 0.1; ///< Tolerance value
GDTMaterialCondition material_condition = GDTMaterialCondition::RFS;
bool diameter_symbol = false; ///< Ø prefix (cylindrical zone)
bool spherical_diameter = false; ///< SØ prefix
/// Format as string e.g. "Ø0.1Ⓜ", "0.05", "SØ0.2"
[[nodiscard]] std::string to_string() const;
};
/// Feature control frame — complete GD&T callout
struct GDTCallout {
GDTType type = GDTType::Flatness; ///< GD&T characteristic type
GDTToleranceValue tolerance; ///< Tolerance value + modifiers
std::vector<DatumReference> datums; ///< Datum references (0-3)
int target_face_id = -1; ///< Target face ID
int target_edge_id = -1; ///< Target edge ID
bool projected_tolerance = false; ///< Projected tolerance zone Ⓟ
double projected_height = 0.0; ///< Projected height
GDTFeature::ZoneShape zone_shape = GDTFeature::ZoneShape::ParallelPlanes;
/// Generate full feature control frame text
/// e.g. "⟂ | Ø0.1Ⓜ | A | BⓂ | C"
[[nodiscard]] std::string to_frame_string() const;
/// Get the GD&T symbol character
[[nodiscard]] std::string symbol() const;
/// Convert to legacy GDTFeature for backward compatibility
[[nodiscard]] GDTFeature to_feature() const;
/// Create from legacy GDTFeature
[[nodiscard]] static GDTCallout from_feature(const GDTFeature& f);
};
// ═══════════════════════════════════════════════════════════
// GD&T Frame Builder API
// ═══════════════════════════════════════════════════════════
/**
* @brief Create a complete GD&T control frame for a set of features
*
* Given a B-Rep model and a list of target face/edge IDs, generates
* appropriate GD&T callouts based on feature geometry analysis.
*
* @param model B-Rep model
* @param face_ids Target face IDs to annotate
* @param datum_face_ids Datum face IDs for reference
* @return Vector of GD&T callouts
*/
[[nodiscard]] std::vector<GDTCallout> create_gdt_frame(
const BrepModel& model,
const std::vector<int>& face_ids,
const std::vector<int>& datum_face_ids = {});
/**
* @brief Create a GD&T callout with full parameter control
*
* @param type GD&T type
* @param tolerance_value Tolerance value
* @param datums Datum references (can be empty for form tolerances)
* @param target_face_id Target face
* @return Complete GDTCallout
*/
[[nodiscard]] GDTCallout make_gdt_callout(
GDTType type,
double tolerance_value,
const std::vector<DatumReference>& datums = {},
int target_face_id = -1);
/**
* @brief Check if GD&T type requires a datum reference
*
* Form tolerances (Flatness, Straightness, Circularity, Cylindricity)
* and Profile do not require datums.
*
* @param type GD&T type
* @return true if datums are required
*/
[[nodiscard]] bool gdt_requires_datum(GDTType type);
/**
* @brief Get recommended datum count for a GD&T type
*
* @param type GD&T type
* @return Expected number of datums (0-3)
*/
[[nodiscard]] int gdt_expected_datums(GDTType type);
/**
* @brief Validate a GD&T callout for correctness
*
* Checks: datum requirements, tolerance positivity, face ID validity.
*
* @param model B-Rep model
* @param callout GD&T callout to validate
* @return Error messages (empty = valid)
*/
[[nodiscard]] std::vector<std::string> validate_gdt_callout(
const BrepModel& model, const GDTCallout& callout);
/**
* @brief Compute circularity (roundness) of a cylindrical face
*
* @param body B-Rep model
* @param face_id Cylindrical face ID
* @return Circularity deviation
*/
[[nodiscard]] double compute_circularity(const BrepModel& body, int face_id);
/**
* @brief Compute cylindricity of a cylindrical face
*
* @param body B-Rep model
* @param face_id Cylindrical face ID
* @return Cylindricity deviation
*/
[[nodiscard]] double compute_cylindricity(const BrepModel& body, int face_id);
/**
* @brief Compute straightness of an edge
*
* @param body B-Rep model
* @param edge_id Edge ID
* @return Straightness deviation from best-fit line
*/
[[nodiscard]] double compute_straightness(const BrepModel& body, int edge_id);
/**
* @brief Compute angularity between face and datum
*
* @param body B-Rep model
* @param face_id Target face
* @param datum_face_id Datum face
* @param nominal_angle_deg Nominal angle in degrees
* @return Angularity deviation
*/
[[nodiscard]] double compute_angularity(const BrepModel& body,
int face_id, int datum_face_id, double nominal_angle_deg);
/**
* @brief Compute symmetry between face and datum
*
* @param body B-Rep model
* @param face_id Target face
* @param datum_face_id Datum face
* @return Symmetry deviation
*/
[[nodiscard]] double compute_symmetry(const BrepModel& body,
int face_id, int datum_face_id);
/**
* @brief Compute profile of a surface (uniform tolerance zone)
*
* @param body B-Rep model
* @param face_id Target face
* @return Maximum deviation from nominal surface
*/
[[nodiscard]] double compute_profile_of_surface(const BrepModel& body, int face_id);
/**
* @brief Compute circular runout
*
* @param body B-Rep model
* @param face_id Target cylindrical face
* @param datum_axis_face_id Datum axis face
* @return Circular runout value
*/
[[nodiscard]] double compute_circular_runout(const BrepModel& body,
int face_id, int datum_axis_face_id);
/**
* @brief Compute total runout
*
* @param body B-Rep model
* @param face_id Target cylindrical face
* @param datum_axis_face_id Datum axis face
* @return Total runout value
*/
[[nodiscard]] double compute_total_runout(const BrepModel& body,
int face_id, int datum_axis_face_id);
} // namespace vde::brep
+331
View File
@@ -0,0 +1,331 @@
#pragma once
/**
* @file pmi_mbd.h
* @brief Product Manufacturing Information (PMI) and Model-Based Definition (MBD)
*
* Attaches semantic PMI annotations (dimensions, tolerances, GD&T, surface finish,
* notes) directly to 3D B-Rep models. Supports STEP AP242 export with embedded PMI.
*
* PMI annotations are the digital equivalent of 2D drawing annotations placed
* directly on the 3D model, enabling paperless manufacturing workflows.
*
* @ingroup brep
*/
#include "vde/brep/brep.h"
#include "vde/brep/gdt.h"
#include "vde/brep/auto_dimensioning.h"
#include "vde/core/point.h"
#include <vector>
#include <string>
#include <memory>
#include <map>
#include <set>
namespace vde::brep {
// ═══════════════════════════════════════════════════════════
// PMI Annotation Types
// ═══════════════════════════════════════════════════════════
/// PMI annotation category
enum class PMIType {
Dimension, ///< Linear/angular/radial dimension
GDTCalloutType, ///< Geometric dimensioning & tolerancing
DatumTarget, ///< Datum target symbol
SurfaceFinish, ///< Surface finish / roughness
Note, ///< General note / text
WeldSymbol, ///< Welding symbol
DatumFeature, ///< Datum feature identifier
Balloon, ///< Balloon callout (for BOM)
Custom, ///< Custom annotation
};
/// PMI annotation display properties
struct PMIDisplayProps {
bool visible = true; ///< Visibility toggle
std::string color = "#000000"; ///< Text/line color (hex)
double text_height = 3.5; ///< Text height in mm
double line_width = 0.35; ///< Line width in mm
std::string font = "ISOCP"; ///< Font name (ISO standard)
bool show_leader = true; ///< Show leader line
bool show_frame = false; ///< Show bounding frame
int layer = 0; ///< Display layer for organization
};
/// PMI annotation — semantic 3D annotation attached to model geometry
struct PMIAnnotation {
int id = -1; ///< Unique annotation ID
PMIType type = PMIType::Note; ///< Annotation category
std::string label; ///< Display label / identifier
/// Associated geometry elements (face IDs, edge IDs, vertex IDs)
std::vector<int> associated_faces;
std::vector<int> associated_edges;
std::vector<int> associated_vertices;
/// 3D position of the annotation text
core::Point3D text_position;
/// Leader attachment point on the model surface
core::Point3D attachment_point;
/// Plane normal for annotation orientation (text plane normal)
core::Vector3D annotation_normal;
/// Text content
std::string content; ///< Main annotation text
std::string sub_content; ///< Secondary line text
std::string prefix; ///< Text prefix (Ø, R, etc.)
std::string suffix; ///< Text suffix
/// For dimension-type PMI
double nominal_value = 0.0;
double tolerance_upper = 0.0;
double tolerance_lower = 0.0;
/// For GD&T-type PMI
GDTType gdt_type = GDTType::Flatness;
std::vector<DatumReference> datum_refs;
/// Display properties
PMIDisplayProps display;
/// Custom metadata key-value pairs
std::map<std::string, std::string> metadata;
/// Get annotation type name string
[[nodiscard]] std::string type_name() const;
/// Generate STEP AP242 representation string for this annotation
[[nodiscard]] std::string to_step_ap242(int& next_id) const;
};
// ═══════════════════════════════════════════════════════════
// PMI View Management
// ═══════════════════════════════════════════════════════════
/// PMI view — a named view with specific annotation visibility
class PMIView {
public:
PMIView() = default;
explicit PMIView(const std::string& name) : name_(name) {}
/// Set view name
void set_name(const std::string& name) { name_ = name; }
[[nodiscard]] const std::string& name() const { return name_; }
/// Set view direction
void set_direction(const core::Vector3D& dir) { direction_ = dir; }
[[nodiscard]] const core::Vector3D& direction() const { return direction_; }
/// Add a visible annotation to this view
void add_annotation(int annotation_id);
/// Remove an annotation from this view
void remove_annotation(int annotation_id);
/// Check if an annotation is visible in this view
[[nodiscard]] bool is_visible(int annotation_id) const;
/// Get all visible annotation IDs
[[nodiscard]] const std::set<int>& visible_annotations() const { return visible_ids_; }
/// Set all annotations visible (except hidden)
void set_all_visible(const std::vector<int>& all_ids);
/// Set all annotations hidden
void clear_all();
/// Hide specific annotations
void hide_annotations(const std::vector<int>& ids);
/// Add a note to this view
void set_note(const std::string& note) { note_ = note; }
[[nodiscard]] const std::string& note() const { return note_; }
/// View scale
void set_scale(double s) { scale_ = s; }
[[nodiscard]] double scale() const { return scale_; }
private:
std::string name_;
core::Vector3D direction_{0, 0, -1}; ///< Default: front view
std::set<int> visible_ids_;
std::string note_;
double scale_ = 1.0;
};
// ═══════════════════════════════════════════════════════════
// PMI Model — container for all PMI on a model
// ═══════════════════════════════════════════════════════════
/// PMI model — collection of all PMI annotations and views for a B-Rep model
class PMIModel {
public:
PMIModel() = default;
// ── Annotation management ──
/// Attach a PMI annotation to the model
/// @return The assigned annotation ID
int attach_pmi(const PMIAnnotation& annotation);
/// Attach PMI and return reference
int attach_pmi(PMIAnnotation&& annotation);
/// Remove an annotation by ID
/// @return true if found and removed
bool remove_pmi(int annotation_id);
/// Get annotation by ID
[[nodiscard]] const PMIAnnotation* get_annotation(int id) const;
/// Get all annotations
[[nodiscard]] const std::vector<PMIAnnotation>& annotations() const { return annotations_; }
/// Get annotations associated with a specific face
[[nodiscard]] std::vector<const PMIAnnotation*> annotations_for_face(int face_id) const;
/// Get annotations associated with a specific edge
[[nodiscard]] std::vector<const PMIAnnotation*> annotations_for_edge(int edge_id) const;
/// Get annotations of a specific type
[[nodiscard]] std::vector<const PMIAnnotation*> annotations_of_type(PMIType type) const;
/// Total annotation count
[[nodiscard]] size_t count() const { return annotations_.size(); }
// ── View management ──
/// Add or update a PMI view
void set_view(const std::string& name, const PMIView& view);
void set_view(const std::string& name, PMIView&& view);
/// Get a view by name
[[nodiscard]] const PMIView* get_view(const std::string& name) const;
/// Get all views
[[nodiscard]] const std::map<std::string, PMIView>& views() const { return views_; }
/// Remove a view
void remove_view(const std::string& name);
/// Create default views (front, top, right, iso)
void create_default_views();
// ── STEP AP242 export ──
/// Export PMI as STEP AP242 embedded annotations
[[nodiscard]] std::string to_step_ap242() const;
/// Total size of STEP AP242 output (approximate)
[[nodiscard]] size_t step_ap242_byte_count() const;
private:
std::vector<PMIAnnotation> annotations_;
std::map<std::string, PMIView> views_;
int next_annotation_id_ = 1;
};
// ═══════════════════════════════════════════════════════════
// Top-level API
// ═══════════════════════════════════════════════════════════
/**
* @brief Attach PMI annotations to a B-Rep model
*
* Convenience function: adds annotation to a PMIModel associated with the body.
*
* @param body B-Rep model
* @param annotation PMI annotation to attach
* @param pmi_model PMI model container (modified in-place)
* @return Assigned annotation ID
*/
int attach_pmi(BrepModel& body, const PMIAnnotation& annotation,
PMIModel& pmi_model);
/**
* @brief Export B-Rep model with PMI as STEP AP242
*
* Produces a STEP AP242 file containing both geometry and embedded PMI.
*
* @param body B-Rep model
* @param pmi_model PMI annotations and views
* @return STEP AP242 string with PMI
*/
[[nodiscard]] std::string export_pmi_step_ap242(
const BrepModel& body, const PMIModel& pmi_model);
/**
* @brief Export to STEP AP242 file
*
* @param filepath Output file path (.stp, .step, .p21)
* @param body B-Rep model
* @param pmi_model PMI annotations and views
*/
void export_pmi_step_ap242_file(const std::string& filepath,
const BrepModel& body, const PMIModel& pmi_model);
/**
* @brief Auto-generate PMI annotations from B-Rep model
*
* Creates dimension PMI, GD&T PMI, and surface finish annotations.
*
* @param body B-Rep model
* @param pmi_model PMI model to populate (modified in-place)
* @param standard Drawing standard for format
* @return Number of annotations generated
*/
int auto_generate_pmi(const BrepModel& body, PMIModel& pmi_model,
DrawingStandard standard = DrawingStandard::ISO);
/**
* @brief Create a PMI dimension annotation
*
* @param type PMI type
* @param value Dimension value
* @param position 3D text position
* @param face_ids Associated face IDs
* @return PMIAnnotation ready to attach
*/
[[nodiscard]] PMIAnnotation make_pmi_dimension(
PMIType type, double value,
const core::Point3D& position,
const std::vector<int>& face_ids);
/**
* @brief Create a PMI GD&T annotation from a GDTCallout
*
* @param callout GD&T callout
* @param position 3D text position
* @return PMIAnnotation ready to attach
*/
[[nodiscard]] PMIAnnotation make_pmi_gdt(
const GDTCallout& callout,
const core::Point3D& position);
/**
* @brief Create a PMI surface finish annotation
*
* @param roughness_ra Ra value in μm
* @param face_id Associated face ID
* @param position 3D text position
* @return PMIAnnotation ready to attach
*/
[[nodiscard]] PMIAnnotation make_pmi_surface_finish(
double roughness_ra, int face_id,
const core::Point3D& position);
/**
* @brief Create a PMI datum target annotation
*
* @param label Datum label (A, B, C...)
* @param face_id Associated face ID
* @param position 3D position
* @return PMIAnnotation ready to attach
*/
[[nodiscard]] PMIAnnotation make_pmi_datum_target(
const std::string& label, int face_id,
const core::Point3D& position);
} // namespace vde::brep