znlgis 博客

GIS开发与技术分享

第三章:几何建模基础

3.1 OpenCascade内核介绍

3.1.1 什么是OpenCascade

OpenCascade Technology(简称OCCT或OCC)是一个开源的3D CAD/CAM/CAE内核,由法国Matra Datavision公司最初开发,后来开源并由OpenCascade公司维护。它是世界上最著名的开源几何建模内核之一,被广泛应用于工业设计、仿真分析、数控加工等领域。

OpenCascade的核心特性包括:

  1. 边界表示法(B-Rep)建模:采用精确的边界表示法来描述3D几何形状,支持任意复杂的曲面和实体
  2. 参数化曲线和曲面:支持NURBS曲线、Bezier曲线、圆锥曲线等多种参数化几何元素
  3. 布尔运算:提供高效可靠的布尔运算算法,包括并集、差集、交集
  4. 曲面操作:支持曲面偏移、缝合、填充等高级操作
  5. 数据交换:支持STEP、IGES、BREP等标准格式的导入导出

3.1.2 WebAssembly集成

Chili3D通过Emscripten工具链将OpenCascade编译为WebAssembly(WASM),使其能够在浏览器中高效运行。这种方式具有以下优势:

  1. 接近原生性能:WebAssembly提供接近原生代码的执行速度
  2. 无需安装:用户无需安装任何本地软件
  3. 跨平台兼容:在所有支持WebAssembly的浏览器中都能运行
  4. 安全沙箱:在浏览器沙箱中运行,安全性更高

WASM初始化流程

// chili-wasm/src/wasm.ts
export async function initChiliWasm(): Promise<void> {
    // 加载WASM模块
    const wasmModule = await import("./chili_wasm.js");
    
    // 初始化模块
    await wasmModule.default();
    
    // 注册到全局服务
    Services.register("wasmModule", wasmModule);
}

3.1.3 C++绑定层

Chili3D使用Embind(Emscripten的C++绑定工具)来暴露OpenCascade的API给JavaScript:

// cpp/src/bindings.cpp
#include <emscripten/bind.h>
#include <BRepPrimAPI_MakeBox.hxx>

using namespace emscripten;

TopoDS_Shape makeBox(double x, double y, double z, double dx, double dy, double dz) {
    gp_Pnt origin(x, y, z);
    TopoDS_Shape box = BRepPrimAPI_MakeBox(origin, dx, dy, dz).Shape();
    return box;
}

EMSCRIPTEN_BINDINGS(chili_wasm) {
    function("makeBox", &makeBox);
    // 更多绑定...
}

3.2 形状类型体系

3.2.1 拓扑结构

OpenCascade使用拓扑结构来描述形状的组成关系。在Chili3D中,这些概念被封装到TypeScript接口中:

// chili-core/src/shape/shapeType.ts
export enum ShapeType {
    Compound = 1,      // 复合体:多个形状的集合
    CompoundSolid = 2, // 复合实体:多个实体的集合
    Solid = 4,         // 实体:封闭的3D体积
    Shell = 8,         // 壳:多个面的集合
    Face = 16,         // 面:有界的曲面
    Wire = 32,         // 线框:多条边的连续序列
    Edge = 64,         // 边:有界的曲线
    Vertex = 128,      // 顶点:空间中的点
    Shape = 256        // 通用形状类型
}

拓扑层次关系

Compound(复合体)
    └── Solid(实体)
        └── Shell(壳)
            └── Face(面)
                └── Wire(线框)
                    └── Edge(边)
                        └── Vertex(顶点)

3.2.2 形状接口

// chili-core/src/shape/shape.ts
export interface IShape {
    readonly shapeType: ShapeType;
    readonly isNull: boolean;
    readonly isValid: boolean;
    
    // 获取子形状
    findSubShapes(type: ShapeType): IShape[];
    findAncestors(subShape: IShape, ancestorType: ShapeType): IShape[];
    
    // 几何信息
    getBoundingBox(): BoundingBox;
    getCenter(): XYZ;
    
    // 变换
    transform(matrix: Matrix4): IShape;
    mirror(plane: Plane): IShape;
    
    // 网格化
    mesh(deflection: number): MeshData;
    
    // 序列化
    toBrep(): string;
    static fromBrep(brep: string): IShape;
}

3.2.3 形状实现

// chili-wasm/src/shape.ts
export class Shape implements IShape {
    private _handle: number; // WASM对象句柄
    private _shapeType?: ShapeType;
    
    constructor(handle: number) {
        this._handle = handle;
    }
    
    get shapeType(): ShapeType {
        if (this._shapeType === undefined) {
            this._shapeType = wasm.getShapeType(this._handle);
        }
        return this._shapeType;
    }
    
    get isNull(): boolean {
        return wasm.isShapeNull(this._handle);
    }
    
    get isValid(): boolean {
        return !this.isNull && wasm.isShapeValid(this._handle);
    }
    
    findSubShapes(type: ShapeType): IShape[] {
        const handles = wasm.findSubShapes(this._handle, type);
        return handles.map(h => new Shape(h));
    }
    
    getBoundingBox(): BoundingBox {
        const [xmin, ymin, zmin, xmax, ymax, zmax] = wasm.getBoundingBox(this._handle);
        return new BoundingBox(
            new XYZ(xmin, ymin, zmin),
            new XYZ(xmax, ymax, zmax)
        );
    }
    
    transform(matrix: Matrix4): IShape {
        const newHandle = wasm.transformShape(this._handle, matrix.toArray());
        return new Shape(newHandle);
    }
    
    mesh(deflection: number = 0.1): MeshData {
        return wasm.meshShape(this._handle, deflection);
    }
    
    // 清理资源
    dispose(): void {
        wasm.disposeShape(this._handle);
        this._handle = 0;
    }
}

3.3 形状工厂

3.3.1 工厂接口

形状工厂负责创建各种几何形状:

// chili-core/src/shape/shapeFactory.ts
export interface IShapeFactory {
    // 基本几何体
    box(origin: XYZ, dx: number, dy: number, dz: number): Result<IShape>;
    sphere(center: XYZ, radius: number): Result<IShape>;
    cylinder(axis: Ray, radius: number, height: number): Result<IShape>;
    cone(axis: Ray, r1: number, r2: number, height: number): Result<IShape>;
    
    // 曲线
    line(start: XYZ, end: XYZ): Result<IEdge>;
    arc(center: XYZ, start: XYZ, end: XYZ): Result<IEdge>;
    circle(center: XYZ, normal: XYZ, radius: number): Result<IEdge>;
    bezier(poles: XYZ[], weights?: number[]): Result<IEdge>;
    
    // 线框和面
    wire(edges: IEdge[]): Result<IWire>;
    face(wire: IWire): Result<IFace>;
    
    // 实体操作
    extrude(profile: IShape, direction: XYZ, length: number): Result<IShape>;
    revolve(profile: IShape, axis: Ray, angle: number): Result<IShape>;
    sweep(profile: IShape, path: IWire): Result<IShape>;
    loft(sections: IWire[], solid: boolean): Result<IShape>;
    
    // 布尔运算
    booleanUnion(shape1: IShape, shape2: IShape): Result<IShape>;
    booleanCut(shape1: IShape, shape2: IShape): Result<IShape>;
    booleanIntersect(shape1: IShape, shape2: IShape): Result<IShape>;
    
    // 修改操作
    fillet(shape: IShape, edges: IEdge[], radius: number): Result<IShape>;
    chamfer(shape: IShape, edges: IEdge[], distance: number): Result<IShape>;
    offset(shape: IShape, distance: number): Result<IShape>;
}

3.3.2 工厂实现

// chili-wasm/src/factory.ts
export class ShapeFactory implements IShapeFactory {
    box(origin: XYZ, dx: number, dy: number, dz: number): Result<IShape> {
        try {
            const handle = wasm.makeBox(origin.x, origin.y, origin.z, dx, dy, dz);
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Failed to create box: ${e}`);
        }
    }
    
    sphere(center: XYZ, radius: number): Result<IShape> {
        if (radius <= 0) {
            return Result.error("Radius must be positive");
        }
        try {
            const handle = wasm.makeSphere(center.x, center.y, center.z, radius);
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Failed to create sphere: ${e}`);
        }
    }
    
    cylinder(axis: Ray, radius: number, height: number): Result<IShape> {
        if (radius <= 0 || height <= 0) {
            return Result.error("Radius and height must be positive");
        }
        try {
            const handle = wasm.makeCylinder(
                axis.origin.x, axis.origin.y, axis.origin.z,
                axis.direction.x, axis.direction.y, axis.direction.z,
                radius, height
            );
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Failed to create cylinder: ${e}`);
        }
    }
    
    extrude(profile: IShape, direction: XYZ, length: number): Result<IShape> {
        if (length === 0) {
            return Result.error("Extrusion length cannot be zero");
        }
        try {
            const normalized = direction.normalize();
            const handle = wasm.makePrism(
                (profile as Shape).handle,
                normalized.x * length,
                normalized.y * length,
                normalized.z * length
            );
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Failed to extrude: ${e}`);
        }
    }
    
    booleanUnion(shape1: IShape, shape2: IShape): Result<IShape> {
        try {
            const handle = wasm.booleanFuse(
                (shape1 as Shape).handle,
                (shape2 as Shape).handle
            );
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Boolean union failed: ${e}`);
        }
    }
    
    booleanCut(shape1: IShape, shape2: IShape): Result<IShape> {
        try {
            const handle = wasm.booleanCut(
                (shape1 as Shape).handle,
                (shape2 as Shape).handle
            );
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Boolean cut failed: ${e}`);
        }
    }
    
    fillet(shape: IShape, edges: IEdge[], radius: number): Result<IShape> {
        if (radius <= 0) {
            return Result.error("Fillet radius must be positive");
        }
        try {
            const edgeHandles = edges.map(e => (e as Shape).handle);
            const handle = wasm.makeFillet(
                (shape as Shape).handle,
                edgeHandles,
                radius
            );
            return Result.ok(new Shape(handle));
        } catch (e) {
            return Result.error(`Fillet failed: ${e}`);
        }
    }
}

3.4 曲线与曲面

3.4.1 曲线类型

Chili3D支持多种曲线类型:

// chili-core/src/shape/curve.ts
export enum CurveType {
    Line = 0,           // 直线
    Circle = 1,         // 圆
    Ellipse = 2,        // 椭圆
    Hyperbola = 3,      // 双曲线
    Parabola = 4,       // 抛物线
    BezierCurve = 5,    // Bezier曲线
    BSplineCurve = 6,   // B样条曲线
    TrimmedCurve = 7,   // 裁剪曲线
    OffsetCurve = 8,    // 偏移曲线
    OtherCurve = 9      // 其他曲线
}

export interface ICurve {
    readonly curveType: CurveType;
    readonly isClosed: boolean;
    readonly isPeriodic: boolean;
    
    // 参数化接口
    parameter(point: XYZ): number;
    point(parameter: number): XYZ;
    tangent(parameter: number): XYZ;
    
    // 范围
    firstParameter(): number;
    lastParameter(): number;
    
    // 转换
    toEdge(): IEdge;
    trim(u1: number, u2: number): ICurve;
    reverse(): ICurve;
}

3.4.2 曲线实现

// chili-wasm/src/curve.ts
export class Curve implements ICurve {
    private _handle: number;
    
    constructor(handle: number) {
        this._handle = handle;
    }
    
    get curveType(): CurveType {
        return wasm.getCurveType(this._handle);
    }
    
    get isClosed(): boolean {
        return wasm.isCurveClosed(this._handle);
    }
    
    parameter(point: XYZ): number {
        return wasm.curveParameter(this._handle, point.x, point.y, point.z);
    }
    
    point(parameter: number): XYZ {
        const [x, y, z] = wasm.curvePoint(this._handle, parameter);
        return new XYZ(x, y, z);
    }
    
    tangent(parameter: number): XYZ {
        const [x, y, z] = wasm.curveTangent(this._handle, parameter);
        return new XYZ(x, y, z);
    }
    
    trim(u1: number, u2: number): ICurve {
        const handle = wasm.trimCurve(this._handle, u1, u2);
        return new Curve(handle);
    }
}

// 具体曲线类型
export class LineCurve extends Curve {
    get start(): XYZ {
        return this.point(this.firstParameter());
    }
    
    get end(): XYZ {
        return this.point(this.lastParameter());
    }
    
    get direction(): XYZ {
        return this.end.sub(this.start).normalize();
    }
    
    get length(): number {
        return this.start.distanceTo(this.end);
    }
}

export class CircleCurve extends Curve {
    get center(): XYZ {
        const [x, y, z] = wasm.circleCenter(this._handle);
        return new XYZ(x, y, z);
    }
    
    get radius(): number {
        return wasm.circleRadius(this._handle);
    }
    
    get normal(): XYZ {
        const [x, y, z] = wasm.circleNormal(this._handle);
        return new XYZ(x, y, z);
    }
}

3.4.3 曲面类型

// chili-core/src/shape/surface.ts
export enum SurfaceType {
    Plane = 0,              // 平面
    Cylinder = 1,           // 圆柱面
    Cone = 2,               // 圆锥面
    Sphere = 3,             // 球面
    Torus = 4,              // 圆环面
    BezierSurface = 5,      // Bezier曲面
    BSplineSurface = 6,     // B样条曲面
    SurfaceOfRevolution = 7,// 旋转曲面
    SurfaceOfExtrusion = 8, // 拉伸曲面
    OffsetSurface = 9,      // 偏移曲面
    OtherSurface = 10       // 其他曲面
}

export interface ISurface {
    readonly surfaceType: SurfaceType;
    readonly isUClosed: boolean;
    readonly isVClosed: boolean;
    
    // 参数化接口
    point(u: number, v: number): XYZ;
    normal(u: number, v: number): XYZ;
    
    // 范围
    uRange(): [number, number];
    vRange(): [number, number];
    
    // 转换
    toFace(): IFace;
}

3.5 布尔运算

3.5.1 布尔运算类型

export enum BooleanType {
    Union = 0,      // 并集
    Cut = 1,        // 差集
    Intersect = 2,  // 交集
    Common = 3      // 公共部分
}

3.5.2 布尔运算实现

// chili/src/bodys/boolean.ts
@Serializable("BooleanBody")
export class BooleanBody extends Body {
    @Property()
    private _operationType: BooleanType;
    
    @Property()
    private _target: IBody;
    
    @Property()
    private _tool: IBody;
    
    constructor(
        document: IDocument,
        operationType: BooleanType,
        target: IBody,
        tool: IBody
    ) {
        super(document);
        this._operationType = operationType;
        this._target = target;
        this._tool = tool;
    }
    
    protected generateShape(): Result<IShape> {
        const targetShape = this._target.shape;
        const toolShape = this._tool.shape;
        
        if (!targetShape || !toolShape) {
            return Result.error("Invalid input shapes");
        }
        
        const factory = this.document.application.shapeFactory;
        
        switch (this._operationType) {
            case BooleanType.Union:
                return factory.booleanUnion(targetShape, toolShape);
            case BooleanType.Cut:
                return factory.booleanCut(targetShape, toolShape);
            case BooleanType.Intersect:
                return factory.booleanIntersect(targetShape, toolShape);
            default:
                return Result.error("Unknown boolean type");
        }
    }
}

3.5.3 布尔运算命令

// chili/src/commands/boolean.ts
@command({
    name: "Modify.Boolean.Union",
    icon: "icon-union",
    display: "command.union"
})
export class BooleanUnionCommand implements ICommand {
    async execute(document: IDocument): Promise<void> {
        // 选择目标对象
        const targetResult = await document.selection.pickShape({
            prompt: t("prompt.selectTarget"),
            filter: ShapeType.Solid | ShapeType.Shell
        });
        
        if (!targetResult.success) return;
        
        // 选择工具对象
        const toolResult = await document.selection.pickShape({
            prompt: t("prompt.selectTool"),
            filter: ShapeType.Solid | ShapeType.Shell
        });
        
        if (!toolResult.success) return;
        
        // 创建布尔体
        const body = new BooleanBody(
            document,
            BooleanType.Union,
            targetResult.data.body,
            toolResult.data.body
        );
        
        // 添加到文档
        const node = new GeometryNode(document, "Union", body);
        document.addNode(node);
        
        // 隐藏原始对象
        targetResult.data.node.visible = false;
        toolResult.data.node.visible = false;
    }
}

3.6 几何体实现

3.6.1 Body基类

所有几何体都继承自Body基类:

// chili/src/bodys/body.ts
export abstract class Body extends Observable implements IBody {
    private _shape?: IShape;
    private _needsUpdate: boolean = true;
    
    constructor(readonly document: IDocument) {
        super();
    }
    
    get shape(): IShape | undefined {
        if (this._needsUpdate || !this._shape) {
            const result = this.generateShape();
            if (result.isOk) {
                this._shape = result.value;
            } else {
                console.error(result.error);
                this._shape = undefined;
            }
            this._needsUpdate = false;
        }
        return this._shape;
    }
    
    protected abstract generateShape(): Result<IShape>;
    
    protected invalidate(): void {
        this._needsUpdate = true;
        this._shape?.dispose();
        this._shape = undefined;
        this.notify("shapeChanged");
    }
}

3.6.2 长方体

// chili/src/bodys/box.ts
@Serializable("BoxBody")
export class BoxBody extends Body {
    @Property()
    private _origin: XYZ;
    
    @Property()
    private _dx: number;
    
    @Property()
    private _dy: number;
    
    @Property()
    private _dz: number;
    
    constructor(
        document: IDocument,
        origin: XYZ,
        dx: number,
        dy: number,
        dz: number
    ) {
        super(document);
        this._origin = origin;
        this._dx = dx;
        this._dy = dy;
        this._dz = dz;
    }
    
    get origin(): XYZ { return this._origin; }
    set origin(value: XYZ) {
        if (!this._origin.equals(value)) {
            this._origin = value;
            this.invalidate();
        }
    }
    
    get dx(): number { return this._dx; }
    set dx(value: number) {
        if (this._dx !== value) {
            this._dx = value;
            this.invalidate();
        }
    }
    
    protected generateShape(): Result<IShape> {
        return this.document.application.shapeFactory.box(
            this._origin, this._dx, this._dy, this._dz
        );
    }
}

3.6.3 球体

// chili/src/bodys/sphere.ts
@Serializable("SphereBody")
export class SphereBody extends Body {
    @Property()
    private _center: XYZ;
    
    @Property()
    private _radius: number;
    
    constructor(document: IDocument, center: XYZ, radius: number) {
        super(document);
        this._center = center;
        this._radius = radius;
    }
    
    get center(): XYZ { return this._center; }
    set center(value: XYZ) {
        if (!this._center.equals(value)) {
            this._center = value;
            this.invalidate();
        }
    }
    
    get radius(): number { return this._radius; }
    set radius(value: number) {
        if (this._radius !== value && value > 0) {
            this._radius = value;
            this.invalidate();
        }
    }
    
    protected generateShape(): Result<IShape> {
        return this.document.application.shapeFactory.sphere(
            this._center, this._radius
        );
    }
}

3.6.4 拉伸体

// chili/src/bodys/prism.ts
@Serializable("PrismBody")
export class PrismBody extends Body {
    @Property()
    private _profile: IBody;
    
    @Property()
    private _direction: XYZ;
    
    @Property()
    private _length: number;
    
    constructor(
        document: IDocument,
        profile: IBody,
        direction: XYZ,
        length: number
    ) {
        super(document);
        this._profile = profile;
        this._direction = direction;
        this._length = length;
        
        // 监听轮廓变化
        profile.addObserver("shapeChanged", () => this.invalidate());
    }
    
    protected generateShape(): Result<IShape> {
        const profileShape = this._profile.shape;
        if (!profileShape) {
            return Result.error("Invalid profile shape");
        }
        
        return this.document.application.shapeFactory.extrude(
            profileShape,
            this._direction,
            this._length
        );
    }
}

3.6.5 旋转体

// chili/src/bodys/revolve.ts
@Serializable("RevolveBody")
export class RevolveBody extends Body {
    @Property()
    private _profile: IBody;
    
    @Property()
    private _axis: Ray;
    
    @Property()
    private _angle: number;
    
    constructor(
        document: IDocument,
        profile: IBody,
        axis: Ray,
        angle: number
    ) {
        super(document);
        this._profile = profile;
        this._axis = axis;
        this._angle = angle;
        
        profile.addObserver("shapeChanged", () => this.invalidate());
    }
    
    protected generateShape(): Result<IShape> {
        const profileShape = this._profile.shape;
        if (!profileShape) {
            return Result.error("Invalid profile shape");
        }
        
        return this.document.application.shapeFactory.revolve(
            profileShape,
            this._axis,
            this._angle
        );
    }
}

3.7 网格化与显示

3.7.1 网格数据结构

将几何形状转换为可显示的三角网格:

// chili-core/src/shape/meshData.ts
export interface MeshData {
    // 顶点位置 (x, y, z)
    positions: Float32Array;
    
    // 顶点法向量 (nx, ny, nz)
    normals: Float32Array;
    
    // 顶点UV坐标 (u, v)
    uvs?: Float32Array;
    
    // 三角形索引
    indices: Uint32Array;
    
    // 边数据
    edges?: EdgeMeshData;
}

export interface EdgeMeshData {
    // 边线顶点
    positions: Float32Array;
    
    // 边线索引 (起点, 终点)
    indices: Uint32Array;
}

3.7.2 网格生成

// chili-wasm/src/mesher.ts
export class Mesher {
    /**
     * 将形状转换为网格
     * @param shape 输入形状
     * @param deflection 网格精度(弦偏差)
     * @param angularDeflection 角度偏差
     */
    static meshShape(
        shape: IShape,
        deflection: number = 0.1,
        angularDeflection: number = 0.5
    ): MeshData {
        const handle = (shape as Shape).handle;
        
        // 调用WASM进行网格化
        const result = wasm.meshShape(handle, deflection, angularDeflection);
        
        return {
            positions: new Float32Array(result.positions),
            normals: new Float32Array(result.normals),
            indices: new Uint32Array(result.indices),
            edges: result.edges ? {
                positions: new Float32Array(result.edges.positions),
                indices: new Uint32Array(result.edges.indices)
            } : undefined
        };
    }
}

3.7.3 与Three.js集成

// chili-three/src/threeGeometry.ts
export class ThreeGeometry {
    static fromMeshData(meshData: MeshData): THREE.BufferGeometry {
        const geometry = new THREE.BufferGeometry();
        
        // 设置顶点属性
        geometry.setAttribute(
            "position",
            new THREE.BufferAttribute(meshData.positions, 3)
        );
        geometry.setAttribute(
            "normal",
            new THREE.BufferAttribute(meshData.normals, 3)
        );
        
        if (meshData.uvs) {
            geometry.setAttribute(
                "uv",
                new THREE.BufferAttribute(meshData.uvs, 2)
            );
        }
        
        // 设置索引
        geometry.setIndex(new THREE.BufferAttribute(meshData.indices, 1));
        
        return geometry;
    }
    
    static createEdgeGeometry(edgeData: EdgeMeshData): THREE.BufferGeometry {
        const geometry = new THREE.BufferGeometry();
        
        geometry.setAttribute(
            "position",
            new THREE.BufferAttribute(edgeData.positions, 3)
        );
        geometry.setIndex(new THREE.BufferAttribute(edgeData.indices, 1));
        
        return geometry;
    }
}

3.8 几何计算

3.8.1 交点计算

export class GeometryHelper {
    /**
     * 计算两条曲线的交点
     */
    static curveIntersections(curve1: ICurve, curve2: ICurve): XYZ[] {
        return wasm.curveCurveIntersection(
            (curve1 as Curve).handle,
            (curve2 as Curve).handle
        ).map(([x, y, z]) => new XYZ(x, y, z));
    }
    
    /**
     * 计算曲线与曲面的交点
     */
    static curveSurfaceIntersections(curve: ICurve, surface: ISurface): XYZ[] {
        return wasm.curveSurfaceIntersection(
            (curve as Curve).handle,
            (surface as Surface).handle
        ).map(([x, y, z]) => new XYZ(x, y, z));
    }
    
    /**
     * 计算点到曲线的最近点
     */
    static nearestPointOnCurve(point: XYZ, curve: ICurve): XYZ {
        const [x, y, z] = wasm.nearestPointOnCurve(
            point.x, point.y, point.z,
            (curve as Curve).handle
        );
        return new XYZ(x, y, z);
    }
    
    /**
     * 计算点到曲面的最近点
     */
    static nearestPointOnSurface(point: XYZ, surface: ISurface): XYZ {
        const [x, y, z] = wasm.nearestPointOnSurface(
            point.x, point.y, point.z,
            (surface as Surface).handle
        );
        return new XYZ(x, y, z);
    }
}

3.8.2 距离计算

export class DistanceHelper {
    /**
     * 计算两个形状之间的最小距离
     */
    static shapeDistance(shape1: IShape, shape2: IShape): number {
        return wasm.shapeDistance(
            (shape1 as Shape).handle,
            (shape2 as Shape).handle
        );
    }
    
    /**
     * 计算点到形状的最小距离
     */
    static pointShapeDistance(point: XYZ, shape: IShape): number {
        return wasm.pointShapeDistance(
            point.x, point.y, point.z,
            (shape as Shape).handle
        );
    }
}

3.8.3 测量工具

// chili/src/commands/measure/measureDistance.ts
@command({
    name: "Measure.Distance",
    icon: "icon-measure-distance",
    display: "command.measureDistance"
})
export class MeasureDistanceCommand implements ICommand {
    async execute(document: IDocument): Promise<void> {
        // 选择第一个点
        const point1Result = await document.selection.pickPoint({
            prompt: t("prompt.selectFirstPoint")
        });
        if (!point1Result.success) return;
        
        // 选择第二个点
        const point2Result = await document.selection.pickPoint({
            prompt: t("prompt.selectSecondPoint")
        });
        if (!point2Result.success) return;
        
        // 计算距离
        const distance = point1Result.data.distanceTo(point2Result.data);
        
        // 显示结果
        Toast.show(`${t("measure.distance")}: ${distance.toFixed(3)}`);
    }
}

3.9 数据交换

3.9.1 支持的格式

Chili3D支持多种标准CAD格式:

3.9.2 导入实现

// chili-builder/src/defaultDataExchange.ts
export class DefaultDataExchange implements IDataExchange {
    async import(file: File): Promise<Result<IShape[]>> {
        const extension = file.name.split('.').pop()?.toLowerCase();
        const buffer = await file.arrayBuffer();
        
        switch (extension) {
            case 'step':
            case 'stp':
                return this.importStep(buffer);
            case 'iges':
            case 'igs':
                return this.importIges(buffer);
            case 'brep':
                return this.importBrep(buffer);
            default:
                return Result.error(`Unsupported format: ${extension}`);
        }
    }
    
    private importStep(buffer: ArrayBuffer): Result<IShape[]> {
        try {
            const shapes = wasm.readStep(new Uint8Array(buffer));
            return Result.ok(shapes.map(h => new Shape(h)));
        } catch (e) {
            return Result.error(`STEP import failed: ${e}`);
        }
    }
}

3.9.3 导出实现

export class DefaultDataExchange implements IDataExchange {
    async export(
        shapes: IShape[],
        format: ExportFormat,
        filename: string
    ): Promise<void> {
        const handles = shapes.map(s => (s as Shape).handle);
        let data: Uint8Array;
        
        switch (format) {
            case ExportFormat.STEP:
                data = wasm.writeStep(handles);
                break;
            case ExportFormat.IGES:
                data = wasm.writeIges(handles);
                break;
            case ExportFormat.BREP:
                data = wasm.writeBrep(handles);
                break;
            default:
                throw new Error(`Unsupported format: ${format}`);
        }
        
        // 创建下载链接
        const blob = new Blob([data], { type: "application/octet-stream" });
        const url = URL.createObjectURL(blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
    }
}

3.10 本章小结

本章深入介绍了Chili3D的几何建模基础,包括:

  1. OpenCascade内核:介绍了OCCT的特性和WebAssembly集成方式
  2. 形状类型体系:详细说明了拓扑结构和形状接口设计
  3. 形状工厂:展示了各种几何形状的创建方法
  4. 曲线与曲面:介绍了参数化几何元素的表示和操作
  5. 布尔运算:说明了并集、差集、交集等布尔操作的实现
  6. 几何体实现:展示了各种具体几何体的实现方式
  7. 网格化与显示:介绍了将几何形状转换为可显示网格的过程
  8. 几何计算:介绍了交点、距离等几何计算功能
  9. 数据交换:说明了STEP、IGES等格式的导入导出

掌握这些几何建模知识,是进行Chili3D二次开发的重要基础。在下一章中,我们将深入探讨用户界面与交互系统。


下一章预告:第四章将详细介绍Chili3D的用户界面系统,包括UI组件库、功能区设计、视口交互、选择系统等核心内容。