惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

T
Threatpost
W
WeLiveSecurity
V
Vulnerabilities – Threatpost
P
Privacy & Cybersecurity Law Blog
Cisco Talos Blog
Cisco Talos Blog
Blog — PlanetScale
Blog — PlanetScale
博客园 - 叶小钗
爱范儿
爱范儿
C
CERT Recently Published Vulnerability Notes
Hugging Face - Blog
Hugging Face - Blog
P
Proofpoint News Feed
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Know Your Adversary
Know Your Adversary
Latest news
Latest news
T
Tor Project blog
NISL@THU
NISL@THU
The Hacker News
The Hacker News
IT之家
IT之家
Last Week in AI
Last Week in AI
T
Tenable Blog
C
Cybersecurity and Infrastructure Security Agency CISA
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V
Visual Studio Blog
Apple Machine Learning Research
Apple Machine Learning Research
T
Threat Research - Cisco Blogs
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园 - 聂微东
T
The Exploit Database - CXSecurity.com
博客园 - 三生石上(FineUI控件)
Spread Privacy
Spread Privacy
S
Secure Thoughts
博客园 - 司徒正美
A
About on SuperTechFans
Attack and Defense Labs
Attack and Defense Labs
Microsoft Security Blog
Microsoft Security Blog
N
News and Events Feed by Topic
O
OpenAI News
V
V2EX
aimingoo的专栏
aimingoo的专栏
L
LangChain Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security Affairs
MyScale Blog
MyScale Blog
S
Schneier on Security
宝玉的分享
宝玉的分享
Hacker News - Newest:
Hacker News - Newest: "LLM"
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

博客园 - 我才是银古

第16章:常见问题、排错与最佳实践 第15章:扩展生态、MCAD 与外部集成 第12章:实战案例:机械结构与 3D 打印零件 第14章:构建、测试、调试与贡献流程 第13章:OpenSCAD 源码架构与核心执行流程 第11章:预览、渲染、网格精度与性能优化 第09章:列表推导、递归与算法建模 第08章:参数化零件库与复用设计 第10章:导入导出、命令行与自动化 第06章:CSG 布尔建模方法 第07章:二维图形、拉伸、旋转与投影 第05章:基础几何、坐标系与变换 第04章:参数、变量、函数、模块与作用域 OpenSCAD 教程目录 第03章:OpenSCAD 语言基础 第02章:安装、环境配置与开发工作流 第01章:OpenSCAD 项目全景与学习路线 第02章:源码获取、编译与开发环境配置 第01章:OCCT项目全景与学习路线 第18章:二次开发实战与综合案例 第18章:综合实战案例 第17章:数据交换与协同 第16章:源码架构与二次开发 第15章:插件与自定义工作台开发 第14章:Python脚本宏与自动化 第13章:FEM仿真分析 第12章:CAM数控加工 第11章:SurfaceMesh与逆向工程 第10章:Draft二维绘图与BIM建筑 第09章:工程图TechDraw 第07章:参数化表达式与Spreadsheet 第08章:装配设计Assembly 第06章:Part工作台与几何内核 第05章:PartDesign实体特征建模 第04章:草图Sketcher约束建模 第02章:安装版本与工作环境配置 第03章:界面工作台与基础操作 第01章:项目全景与学习路线 第十二章:插件开发、研究功能与最佳实践 第十章:定时任务与自动化(Cron) 第七章:技能、记忆与自学习闭环 第八章:MCP 集成与上下文文件 第六章:工具系统与终端后端 第五章:模型供应商与配置体系 Hermes Agent 教程目录 第十一章:语音、视觉、浏览器与子代理协作 第四章:CLI/TUI 与会话管理 第十二章:学习路线、实战方案与最佳实践 第十一章:源码结构、开发调试与插件开发 第十章:自动化、远程访问、日志与排障 第九章:Control UI、节点、Canvas 与语音能力 第七章:工具、技能、插件与能力扩展 第八章:安全模型、访问控制与沙箱实践 第六章:Agent 工作区、会话与多智能体路由 第五章:多通道消息接入与聊天平台配置 第四章:配置体系、模型接入与认证管理 第三章:Gateway 架构、协议与运行机制 第二章:安装、环境准备与快速上手 第一章:OpenClaw 项目概览与核心定位 oh-my-openagent 教程目录 09-命令模型回退与配置参考 10-实战案例最佳实践与故障排除 05-工作模式-Ultrawork-Prometheus-Atlas 08-Hooks与MCP系统 06-Category与Skill系统 07-核心工具链 04-智能体全景详解 03-安装与环境配置 02-整体架构与多模型编排机制 01-项目简介与核心理念 01-项目概览与学习路线 02-安装部署与工具适配 03-Skill机制与using-superpowers 05-TDD系统化调试与完成前验证 04-需求澄清方案设计与计划编写 07-并行智能体子智能体与Git-Worktree 第六章:代码审查、反馈处理与分支收尾 08-中国特色Skills与本土团队落地 09-MCP构建工作流执行与自定义Skill 第23章:FreeCAD-Python-API Clipper2 C# 源码解读教程 第19章:PolyTree 多边形树结构 第20章:实际应用与最佳实践 第18章:Minkowski 和与差 第17章:RectClip 矩形裁剪优化 第16章:ClipperOffset 偏移类详解 第15章:填充规则详解 第14章:布尔运算执行流程 第13章:ClipperD 浮点裁剪类 第11章:OutRec 与 OutPt 输出结构 第9章:Active 活动边结构 第10章:Vertex 顶点与 LocalMinima 局部极小值 第12章:Clipper64 裁剪类详解 第7章:高精度运算与128位整数 第8章:ClipperBase 基类详解 第5章:枚举类型与常量定义 第6章:InternalClipper 内部工具类 第2章:核心数据结构 - Point64、PointD 第3章:路径与多边形表示 - Path64、PathD、Paths64、PathsD 第4章:矩形边界 - Rect64、RectD
实体建模系统
我才是银古 · 2026-06-19 · via 博客园 - 我才是银古

第八章:实体建模系统

8.1 实体建模概述

8.1.1 什么是实体建模

实体建模(Solid Modeling)是三维CAD系统的核心能力,通过对二维轮廓进行拉伸、旋转、放样等操作来创建三维实体对象。LightCAD的实体建模系统位于LightCAD.Core/Elements/Solid/目录下,提供了完整的参数化实体建模功能。

8.1.2 支持的建模操作

操作类型 类名 说明
拉伸 LcExtrude3d 沿方向拉伸轮廓
旋转 LcRevolve3d 绕轴旋转轮廓
放样 LcLoft3d 在多个截面之间过渡
融合 LcBlend3d 在实体边缘创建圆角或倒角
扫掠融合 LcSweptBlend3d 沿路径扫掠的融合体
推拉 LcPushPull3d 直接推拉面
网格 LcMesh3d 网格体表示
布尔运算 LcCombination 并集/交集/差集

8.2 实体基类(LcSolid3d)

8.2.1 数据结构

LcSolid3d是所有实体建模操作的基类,文件大小达26KB,是Core模块中最复杂的类之一:

public abstract class LcSolid3d : LcEntity, IUpdateObject
{
    /// <summary>
    /// 实体的变换矩阵
    /// </summary>
    public Matrix4d Transform { get; set; } = Matrix4d.Identity;

    /// <summary>
    /// 材质
    /// </summary>
    public MaterialInfo Material { get; set; }

    /// <summary>
    /// 三维包围盒
    /// </summary>
    public BoundingBox3d Bounds3d { get; protected set; }

    /// <summary>
    /// 体积
    /// </summary>
    public abstract double Volume { get; }

    /// <summary>
    /// 表面积
    /// </summary>
    public abstract double SurfaceArea { get; }

    /// <summary>
    /// 质心
    /// </summary>
    public abstract Point3d Centroid { get; }

    /// <summary>
    /// 生成三角网格(用于渲染)
    /// </summary>
    public abstract MeshData GenerateMesh(int quality = 1);

    /// <summary>
    /// 获取实体的面集合
    /// </summary>
    public abstract List<SolidFace> GetFaces();

    /// <summary>
    /// 获取实体的边集合
    /// </summary>
    public abstract List<SolidEdge> GetEdges();

    /// <summary>
    /// 获取实体的顶点集合
    /// </summary>
    public abstract List<Point3d> GetVertices();

    /// <summary>
    /// 参数是否有效
    /// </summary>
    public abstract bool IsValid { get; }

    /// <summary>
    /// 重新生成几何(参数改变后调用)
    /// </summary>
    public abstract void Regenerate();
}

/// <summary>
/// 网格数据(用于渲染)
/// </summary>
public class MeshData
{
    public List<Point3d> Vertices { get; set; } = new();
    public List<Vector3d> Normals { get; set; } = new();
    public List<int> Indices { get; set; } = new();
    public List<Point2d> UVCoordinates { get; set; } = new();
}

/// <summary>
/// 实体面
/// </summary>
public class SolidFace
{
    public List<Point3d> Vertices { get; set; }
    public Vector3d Normal { get; set; }
    public FaceType Type { get; set; }
}

public enum FaceType
{
    Planar,       // 平面
    Cylindrical,  // 圆柱面
    Conical,      // 圆锥面
    Spherical,    // 球面
    Toroidal,     // 环面
    FreeForm      // 自由曲面
}

8.3 拉伸体(LcExtrude3d)

8.3.1 数据结构

拉伸是最基本的实体建模操作,将二维轮廓沿指定方向拉伸为三维实体:

public class LcExtrude3d : LcSolid3d
{
    /// <summary>
    /// 拉伸轮廓
    /// </summary>
    public LcProfile3d Profile { get; set; }

    /// <summary>
    /// 拉伸方向
    /// </summary>
    public Vector3d Direction { get; set; } = Vector3d.ZAxis;

    /// <summary>
    /// 拉伸距离
    /// </summary>
    public double Distance { get; set; }

    /// <summary>
    /// 拔模角度(拉伸时的锥度)
    /// </summary>
    public double DraftAngle { get; set; } = 0;

    /// <summary>
    /// 是否双向拉伸
    /// </summary>
    public bool IsBidirectional { get; set; } = false;

    /// <summary>
    /// 反向拉伸距离(双向拉伸时使用)
    /// </summary>
    public double ReverseDistance { get; set; }

    /// <summary>
    /// 拉伸终止类型
    /// </summary>
    public ExtrudeEndType EndType { get; set; } = ExtrudeEndType.Distance;

    public override string TypeName => "Extrude3d";

    public override double Volume
    {
        get
        {
            if (Math.Abs(DraftAngle) < 1e-10)
            {
                // 无拔模角,体积 = 面积 × 距离
                return Profile.Area * Distance;
            }
            else
            {
                // 有拔模角,需要数值积分
                return CalculateVolume();
            }
        }
    }

    public override MeshData GenerateMesh(int quality = 1)
    {
        var mesh = new MeshData();
        var profilePoints = Profile.OuterLoop.Tessellate();
        int segments = profilePoints.Count;

        // 生成底面和顶面
        GenerateCapMesh(mesh, profilePoints, 0, false);        // 底面
        GenerateCapMesh(mesh, profilePoints, Distance, true);  // 顶面

        // 生成侧面
        GenerateSideMesh(mesh, profilePoints);

        return mesh;
    }

    private void GenerateCapMesh(MeshData mesh, List<Point2d> profile,
                                  double height, bool isTop)
    {
        var baseIndex = mesh.Vertices.Count;
        var normal = isTop ? Direction : Direction * -1;

        foreach (var p in profile)
        {
            var worldPoint = Profile.WorkPlane.LocalToWorld(p);
            mesh.Vertices.Add(new Point3d(
                worldPoint.X + Direction.X * height,
                worldPoint.Y + Direction.Y * height,
                worldPoint.Z + Direction.Z * height
            ));
            mesh.Normals.Add(normal);
        }

        // 三角化(扇形三角化)
        for (int i = 1; i < profile.Count - 1; i++)
        {
            if (isTop)
            {
                mesh.Indices.Add(baseIndex);
                mesh.Indices.Add(baseIndex + i + 1);
                mesh.Indices.Add(baseIndex + i);
            }
            else
            {
                mesh.Indices.Add(baseIndex);
                mesh.Indices.Add(baseIndex + i);
                mesh.Indices.Add(baseIndex + i + 1);
            }
        }
    }

    private void GenerateSideMesh(MeshData mesh, List<Point2d> profile)
    {
        var baseIndex = mesh.Vertices.Count;

        for (int i = 0; i < profile.Count; i++)
        {
            int next = (i + 1) % profile.Count;

            // 底部顶点
            var bottomPoint = Profile.WorkPlane.LocalToWorld(profile[i]);
            // 顶部顶点
            var topPoint = new Point3d(
                bottomPoint.X + Direction.X * Distance,
                bottomPoint.Y + Direction.Y * Distance,
                bottomPoint.Z + Direction.Z * Distance
            );

            // 计算法向量
            var edge = Profile.WorkPlane.LocalToWorld(profile[next]);
            var edgeDir = new Vector3d(
                edge.X - bottomPoint.X,
                edge.Y - bottomPoint.Y,
                edge.Z - bottomPoint.Z).Normalize();
            var sideNormal = edgeDir.Cross(Direction).Normalize();

            mesh.Vertices.Add(bottomPoint);
            mesh.Normals.Add(sideNormal);
            mesh.Vertices.Add(topPoint);
            mesh.Normals.Add(sideNormal);
        }

        // 侧面三角形索引
        for (int i = 0; i < profile.Count; i++)
        {
            int next = (i + 1) % profile.Count;
            int bi = baseIndex + i * 2;
            int ni = baseIndex + next * 2;

            mesh.Indices.Add(bi);
            mesh.Indices.Add(bi + 1);
            mesh.Indices.Add(ni);

            mesh.Indices.Add(ni);
            mesh.Indices.Add(bi + 1);
            mesh.Indices.Add(ni + 1);
        }
    }
}

public enum ExtrudeEndType
{
    Distance,       // 指定距离
    ToFace,         // 延伸到面
    ThroughAll,     // 贯穿全部
    ToNextFace      // 延伸到下一个面
}

8.3.2 使用示例

// 创建一个矩形轮廓并拉伸
var profile = new LcProfile3d
{
    WorkPlane = new LcWorkPlane3d
    {
        Origin = Point3d.Origin,
        Normal = Vector3d.ZAxis
    },
    OuterLoop = CreateRectangle(0, 0, 100, 50)
};

var extrude = new LcExtrude3d
{
    Profile = profile,
    Direction = Vector3d.ZAxis,
    Distance = 30,
    DraftAngle = 0
};

// 带拔模角的拉伸(创建锥台)
var tapered = new LcExtrude3d
{
    Profile = profile,
    Direction = Vector3d.ZAxis,
    Distance = 50,
    DraftAngle = AngleUtils.DegToRad(5) // 5度拔模角
};

// 双向拉伸
var bidirectional = new LcExtrude3d
{
    Profile = profile,
    Direction = Vector3d.ZAxis,
    Distance = 20,
    IsBidirectional = true,
    ReverseDistance = 10
};

8.4 旋转体(LcRevolve3d)

8.4.1 数据结构

public class LcRevolve3d : LcSolid3d
{
    /// <summary>
    /// 旋转轮廓
    /// </summary>
    public LcProfile3d Profile { get; set; }

    /// <summary>
    /// 旋转轴上的一点
    /// </summary>
    public Point3d AxisPoint { get; set; }

    /// <summary>
    /// 旋转轴方向
    /// </summary>
    public Vector3d AxisDirection { get; set; } = Vector3d.YAxis;

    /// <summary>
    /// 旋转角度(弧度)
    /// </summary>
    public double Angle { get; set; } = 2 * Math.PI;

    /// <summary>
    /// 是否为完整旋转(360度)
    /// </summary>
    public bool IsFullRevolution => Math.Abs(Angle - 2 * Math.PI) < 1e-10;

    public override string TypeName => "Revolve3d";

    public override MeshData GenerateMesh(int quality = 1)
    {
        var mesh = new MeshData();
        var profilePoints = Profile.OuterLoop.Tessellate();
        int radialSegments = Math.Max(16, 32 * quality);

        // 对每个轮廓点,绕轴旋转生成顶点环
        for (int i = 0; i <= radialSegments; i++)
        {
            var t = (double)i / radialSegments;
            var currentAngle = Angle * t;
            var rotationMatrix = RotateAroundAxis(
                AxisPoint, AxisDirection, currentAngle);

            foreach (var p in profilePoints)
            {
                var worldPoint = Profile.WorkPlane.LocalToWorld(p);
                var rotated = rotationMatrix.Transform(worldPoint);
                mesh.Vertices.Add(rotated);

                // 计算法向量
                var normal = CalculateNormal(worldPoint, rotated, AxisPoint);
                mesh.Normals.Add(normal);
            }
        }

        // 生成三角形索引
        int profileCount = profilePoints.Count;
        for (int i = 0; i < radialSegments; i++)
        {
            for (int j = 0; j < profileCount - 1; j++)
            {
                int current = i * profileCount + j;
                int next = (i + 1) * profileCount + j;

                mesh.Indices.Add(current);
                mesh.Indices.Add(next);
                mesh.Indices.Add(current + 1);

                mesh.Indices.Add(current + 1);
                mesh.Indices.Add(next);
                mesh.Indices.Add(next + 1);
            }
        }

        return mesh;
    }
}

8.4.2 使用示例

// 创建一个花瓶形状(旋转体)
var vaseProfile = new LcPolyline();
vaseProfile.AddVertex(new Point2d(20, 0));    // 底部
vaseProfile.AddVertex(new Point2d(25, 10));
vaseProfile.AddVertex(new Point2d(15, 30));   // 收窄
vaseProfile.AddVertex(new Point2d(20, 50));
vaseProfile.AddVertex(new Point2d(30, 80));   // 口沿
vaseProfile.AddVertex(new Point2d(28, 85));
vaseProfile.AddVertex(new Point2d(0, 85));    // 内壁
vaseProfile.IsClosed = true;

var profile = new LcProfile3d
{
    WorkPlane = new LcWorkPlane3d { Origin = Point3d.Origin },
    OuterLoop = vaseProfile
};

var vase = new LcRevolve3d
{
    Profile = profile,
    AxisPoint = Point3d.Origin,
    AxisDirection = Vector3d.YAxis,
    Angle = 2 * Math.PI  // 完整旋转
};

8.5 放样体(LcLoft3d)

8.5.1 数据结构

放样操作在两个或多个截面轮廓之间创建平滑过渡的实体。LcLoft3d是建模系统中最复杂的操作之一(源代码文件达40KB):

public class LcLoft3d : LcSolid3d
{
    /// <summary>
    /// 截面轮廓集合(至少2个)
    /// </summary>
    public List<LcProfile3d> Profiles { get; set; } = new();

    /// <summary>
    /// 引导线(可选,用于控制过渡形状)
    /// </summary>
    public List<LcCurve3d> GuideCurves { get; set; } = new();

    /// <summary>
    /// 路径曲线(可选,截面沿路径放样)
    /// </summary>
    public LcCurve3d PathCurve { get; set; }

    /// <summary>
    /// 起始条件
    /// </summary>
    public LoftEndCondition StartCondition { get; set; } = LoftEndCondition.Free;

    /// <summary>
    /// 终止条件
    /// </summary>
    public LoftEndCondition EndCondition { get; set; } = LoftEndCondition.Free;

    /// <summary>
    /// 过渡类型
    /// </summary>
    public LoftTransitionType Transition { get; set; } = LoftTransitionType.Smooth;

    public override string TypeName => "Loft3d";

    public override MeshData GenerateMesh(int quality = 1)
    {
        var mesh = new MeshData();
        int steps = Math.Max(16, 32 * quality);

        // 对每个步骤,在截面之间插值
        for (int step = 0; step <= steps; step++)
        {
            var t = (double)step / steps;
            var interpolatedProfile = InterpolateProfiles(t);

            foreach (var point in interpolatedProfile)
            {
                mesh.Vertices.Add(point);
            }
        }

        // 生成三角形索引(连接相邻步骤的顶点)
        // ...

        return mesh;
    }

    /// <summary>
    /// 在截面之间进行插值
    /// </summary>
    private List<Point3d> InterpolateProfiles(double t)
    {
        if (Profiles.Count < 2)
            throw new InvalidOperationException("至少需要2个截面");

        // 确定当前t值对应的两个截面
        var sectionT = t * (Profiles.Count - 1);
        int index = Math.Min((int)sectionT, Profiles.Count - 2);
        var localT = sectionT - index;

        var profile1 = Profiles[index].GetWorldPoints();
        var profile2 = Profiles[index + 1].GetWorldPoints();

        // 确保两个轮廓的顶点数一致
        AlignVertexCounts(ref profile1, ref profile2);

        // 线性插值(可替换为样条插值以获得更平滑的效果)
        var result = new List<Point3d>();
        for (int i = 0; i < profile1.Count; i++)
        {
            result.Add(new Point3d(
                profile1[i].X * (1 - localT) + profile2[i].X * localT,
                profile1[i].Y * (1 - localT) + profile2[i].Y * localT,
                profile1[i].Z * (1 - localT) + profile2[i].Z * localT
            ));
        }

        return result;
    }
}

public enum LoftEndCondition
{
    Free,           // 自由
    Tangent,        // 与相邻面相切
    Perpendicular,  // 垂直于截面
    Smooth          // 平滑过渡
}

public enum LoftTransitionType
{
    Linear,         // 线性过渡
    Smooth,         // 平滑过渡
    RuleSurface     // 直纹面
}

8.6 融合体(LcBlend3d)

8.6.1 数据结构

融合操作用于在实体的边缘创建圆角或倒角:

public class LcBlend3d : LcSolid3d
{
    /// <summary>
    /// 要进行融合的目标实体
    /// </summary>
    public LcSolid3d TargetSolid { get; set; }

    /// <summary>
    /// 要融合的边
    /// </summary>
    public List<SolidEdge> SelectedEdges { get; set; } = new();

    /// <summary>
    /// 融合类型
    /// </summary>
    public BlendType Type { get; set; } = BlendType.Fillet;

    /// <summary>
    /// 圆角半径
    /// </summary>
    public double Radius { get; set; }

    /// <summary>
    /// 倒角距离1
    /// </summary>
    public double ChamferDistance1 { get; set; }

    /// <summary>
    /// 倒角距离2
    /// </summary>
    public double ChamferDistance2 { get; set; }

    /// <summary>
    /// 是否为变截面融合
    /// </summary>
    public bool IsVariable { get; set; } = false;

    public override string TypeName => "Blend3d";
}

public enum BlendType
{
    Fillet,     // 圆角
    Chamfer     // 倒角
}

8.7 推拉体(LcPushPull3d)

8.7.1 数据结构

推拉操作允许直接拉伸或收缩实体的面:

public class LcPushPull3d : LcSolid3d
{
    /// <summary>
    /// 目标实体
    /// </summary>
    public LcSolid3d TargetSolid { get; set; }

    /// <summary>
    /// 选中的面
    /// </summary>
    public SolidFace SelectedFace { get; set; }

    /// <summary>
    /// 推拉距离(正值向外,负值向内)
    /// </summary>
    public double Distance { get; set; }

    /// <summary>
    /// 拔模角度
    /// </summary>
    public double DraftAngle { get; set; }

    public override string TypeName => "PushPull3d";
}

8.8 网格体(LcMesh3d)

8.8.1 数据结构

LcMesh3d直接以三角网格的形式表示三维实体:

public class LcMesh3d : LcSolid3d
{
    /// <summary>
    /// 顶点列表
    /// </summary>
    public List<Point3d> Vertices { get; set; } = new();

    /// <summary>
    /// 法向量列表
    /// </summary>
    public List<Vector3d> Normals { get; set; } = new();

    /// <summary>
    /// 三角形面索引(每3个为一组)
    /// </summary>
    public List<int> FaceIndices { get; set; } = new();

    /// <summary>
    /// UV纹理坐标
    /// </summary>
    public List<Point2d> UVs { get; set; } = new();

    /// <summary>
    /// 三角形数量
    /// </summary>
    public int TriangleCount => FaceIndices.Count / 3;

    /// <summary>
    /// 顶点数量
    /// </summary>
    public int VertexCount => Vertices.Count;

    public override string TypeName => "Mesh3d";

    public override double Volume
    {
        get
        {
            // 使用Divergence Theorem计算网格体积
            double volume = 0;
            for (int i = 0; i < FaceIndices.Count; i += 3)
            {
                var v0 = Vertices[FaceIndices[i]];
                var v1 = Vertices[FaceIndices[i + 1]];
                var v2 = Vertices[FaceIndices[i + 2]];

                volume += SignedTriangleVolume(v0, v1, v2);
            }
            return Math.Abs(volume);
        }
    }

    private static double SignedTriangleVolume(Point3d a, Point3d b, Point3d c)
    {
        return (a.X * (b.Y * c.Z - b.Z * c.Y) +
                a.Y * (b.Z * c.X - b.X * c.Z) +
                a.Z * (b.X * c.Y - b.Y * c.X)) / 6.0;
    }

    public override MeshData GenerateMesh(int quality = 1)
    {
        return new MeshData
        {
            Vertices = new List<Point3d>(Vertices),
            Normals = new List<Vector3d>(Normals),
            Indices = new List<int>(FaceIndices),
            UVCoordinates = new List<Point2d>(UVs)
        };
    }

    /// <summary>
    /// 计算平滑法向量
    /// </summary>
    public void RecalculateNormals()
    {
        Normals = new List<Vector3d>(new Vector3d[Vertices.Count]);

        for (int i = 0; i < FaceIndices.Count; i += 3)
        {
            var i0 = FaceIndices[i];
            var i1 = FaceIndices[i + 1];
            var i2 = FaceIndices[i + 2];

            var v0 = Vertices[i0];
            var v1 = Vertices[i1];
            var v2 = Vertices[i2];

            var edge1 = new Vector3d(v1.X - v0.X, v1.Y - v0.Y, v1.Z - v0.Z);
            var edge2 = new Vector3d(v2.X - v0.X, v2.Y - v0.Y, v2.Z - v0.Z);
            var faceNormal = edge1.Cross(edge2);

            Normals[i0] = Normals[i0] + faceNormal;
            Normals[i1] = Normals[i1] + faceNormal;
            Normals[i2] = Normals[i2] + faceNormal;
        }

        for (int i = 0; i < Normals.Count; i++)
        {
            Normals[i] = Normals[i].Normalize();
        }
    }
}

8.9 布尔运算(LcCombination)

8.9.1 数据结构

布尔运算是实体建模中最强大的工具,可以通过并集、交集和差集来组合实体:

public class LcCombination : LcSolid3d
{
    /// <summary>
    /// 第一个操作实体
    /// </summary>
    public LcSolid3d SolidA { get; set; }

    /// <summary>
    /// 第二个操作实体
    /// </summary>
    public LcSolid3d SolidB { get; set; }

    /// <summary>
    /// 布尔运算类型
    /// </summary>
    public BooleanOperationType Operation { get; set; }

    public override string TypeName => "Combination";

    public override MeshData GenerateMesh(int quality = 1)
    {
        var meshA = SolidA.GenerateMesh(quality);
        var meshB = SolidB.GenerateMesh(quality);

        return Operation switch
        {
            BooleanOperationType.Union =>
                BooleanMesh.Union(meshA, meshB),
            BooleanOperationType.Intersect =>
                BooleanMesh.Intersect(meshA, meshB),
            BooleanOperationType.Subtract =>
                BooleanMesh.Subtract(meshA, meshB),
            _ => meshA
        };
    }
}

public enum BooleanOperationType
{
    Union,       // 并集:A ∪ B
    Intersect,   // 交集:A ∩ B
    Subtract     // 差集:A - B
}

8.9.2 使用示例

// 创建一个带孔的方块(差集运算)
var box = CreateBox(100, 100, 50);
var cylinder = CreateCylinder(
    new Point3d(50, 50, 0), 15, 50);

var boxWithHole = new LcCombination
{
    SolidA = box,
    SolidB = cylinder,
    Operation = BooleanOperationType.Subtract
};

// 创建两个圆柱的交集
var cyl1 = CreateCylinder(Point3d.Origin, 30, 100);
var cyl2 = CreateCylinder(
    new Point3d(50, 0, 0), 30, 100);

var intersection = new LcCombination
{
    SolidA = cyl1,
    SolidB = cyl2,
    Operation = BooleanOperationType.Intersect
};

8.10 组件操作(Component.Actions)

8.10.1 操作系统概述

LightCAD.Component.Actions模块提供了实际的建模操作实现,每个操作对应用户界面中的一个命令:

LightCAD.Component.Actions/
├── ComponentActionLoader.cs   # 操作加载器
├── CubeAction.cs              # 立方体操作
├── CuboidAction.cs            # 长方体操作(7KB)
├── ExtrudeAction.cs           # 拉伸操作(15KB)
└── LoftAction.cs              # 放样操作(40KB)

8.10.2 CuboidAction示例

public class CuboidAction : IComponentAction
{
    public string Name => "Cuboid";
    public string DisplayName => "长方体";
    public string Category => "基本实体";

    /// <summary>
    /// 执行创建长方体的操作
    /// </summary>
    public LcSolid3d Execute(ActionContext context)
    {
        // 获取用户输入的参数
        var origin = context.GetPoint("指定基点");
        var width = context.GetDistance("指定宽度");
        var height = context.GetDistance("指定高度");
        var depth = context.GetDistance("指定深度");

        // 创建矩形轮廓
        var profile = new LcProfile3d
        {
            WorkPlane = new LcWorkPlane3d { Origin = origin },
            OuterLoop = CreateRectangle(0, 0, width, height)
        };

        // 拉伸创建长方体
        return new LcExtrude3d
        {
            Profile = profile,
            Direction = Vector3d.ZAxis,
            Distance = depth
        };
    }
}

8.10.3 ExtrudeAction示例

public class ExtrudeAction : IComponentAction
{
    public string Name => "Extrude";
    public string DisplayName => "拉伸";

    public LcSolid3d Execute(ActionContext context)
    {
        // 选择要拉伸的轮廓
        var profile = context.SelectProfile("选择拉伸轮廓");

        // 获取拉伸参数
        var distance = context.GetDistance("指定拉伸距离");
        var direction = context.GetDirection("指定拉伸方向",
            profile.WorkPlane.Normal); // 默认法线方向

        var draftAngle = context.GetAngle("拔模角度(可选)", 0);

        return new LcExtrude3d
        {
            Profile = profile,
            Direction = direction,
            Distance = distance,
            DraftAngle = draftAngle
        };
    }
}

8.11 参数化建模

8.11.1 参数化特性

LightCAD的实体建模系统是参数化的,这意味着:

  1. 参数可修改:创建实体后可以随时修改参数
  2. 自动更新:修改参数后实体自动重新生成
  3. 历史记录:保留完整的建模操作历史
// 修改参数示例
var extrude = new LcExtrude3d { ... };

// 修改拉伸距离
extrude.Distance = 50;
extrude.Regenerate(); // 重新生成几何

// 修改轮廓
extrude.Profile.OuterLoop.Vertices[2] =
    new PolylineVertex(new Point2d(150, 50));
extrude.Regenerate();

8.11.2 特征树

LightCAD支持特征树(Feature Tree)来管理建模历史:

public class FeatureTree
{
    public List<Feature> Features { get; } = new();

    public void AddFeature(Feature feature)
    {
        Features.Add(feature);
        Rebuild();
    }

    public void RemoveFeature(int index)
    {
        Features.RemoveAt(index);
        Rebuild();
    }

    /// <summary>
    /// 重建所有特征
    /// </summary>
    public void Rebuild()
    {
        LcSolid3d currentSolid = null;

        foreach (var feature in Features)
        {
            currentSolid = feature.Apply(currentSolid);
        }
    }
}

public abstract class Feature
{
    public string Name { get; set; }
    public bool IsEnabled { get; set; } = true;
    public abstract LcSolid3d Apply(LcSolid3d input);
}

8.12 本章小结

本章全面介绍了LightCAD的实体建模系统。从基础的拉伸和旋转,到复杂的放样和布尔运算,LightCAD提供了完整的参数化实体建模能力。所有实体类型都继承自LcSolid3d基类,支持网格生成、体积计算和几何变换。组件操作模块(Component.Actions)将建模操作封装为可交互的命令,方便用户通过界面进行操作。参数化特性使得实体可以在创建后随时修改参数并自动更新。


上一章第七章:三维图元系统

下一章第九章:渲染系统与Three.js集成