znlgis 博客

GIS开发与技术分享

第二章:核心架构解析

2.1 整体架构设计

Chili3D采用模块化的分层架构设计,将复杂的CAD应用程序分解为多个相互协作的功能模块。这种设计不仅使代码结构清晰、易于维护,也为二次开发提供了良好的扩展点。本章将深入分析Chili3D的核心架构,帮助读者理解系统的运作机制。

2.1.1 分层架构概述

Chili3D的架构可以分为以下几个层次:

┌─────────────────────────────────────────────────────┐
│                    应用层 (chili)                    │
│     命令实现、几何体、业务逻辑、选择系统               │
├─────────────────────────────────────────────────────┤
│                   构建层 (chili-builder)             │
│         应用组装、功能区配置、数据交换                 │
├─────────────────────────────────────────────────────┤
│                   界面层 (chili-ui)                  │
│      主窗口、功能区、属性面板、视口、对话框            │
├─────────────────────────────────────────────────────┤
│                  可视化层 (chili-vis)                │
│         视觉对象抽象、显示状态管理                     │
├─────────────────────────────────────────────────────┤
│               渲染层 (chili-three)                   │
│     Three.js集成、视图管理、相机控制、渲染              │
├─────────────────────────────────────────────────────┤
│                 核心层 (chili-core)                  │
│    接口定义、数学库、形状接口、命令系统、序列化          │
├─────────────────────────────────────────────────────┤
│               几何层 (chili-wasm)                    │
│       OpenCascade绑定、几何操作、形状创建              │
├─────────────────────────────────────────────────────┤
│              存储层 (chili-storage)                  │
│         IndexedDB、文件操作、文档持久化               │
└─────────────────────────────────────────────────────┘

2.1.2 依赖关系

各包之间的依赖关系遵循自下而上的原则,上层包可以依赖下层包,但下层包不能依赖上层包:

这种依赖结构确保了核心库的稳定性和可复用性。

2.1.3 接口与实现分离

Chili3D大量采用了接口与实现分离的设计模式。在chili-core中定义接口,在具体包中提供实现:

// chili-core中定义接口
interface IApplication {
    readonly documents: IDocument[];
    readonly activeDocument: IDocument | undefined;
    // ...
}

// chili中实现接口
class Application implements IApplication {
    private _documents: Document[] = [];
    
    get documents(): IDocument[] {
        return this._documents;
    }
    // ...
}

这种设计使得系统具有良好的可扩展性和可测试性。

2.2 应用程序生命周期

2.2.1 Application类

Application类是整个应用程序的入口点和核心管理器,负责协调各个子系统的工作。

主要职责

核心接口定义(chili-core/src/application.ts):

export interface IApplication {
    readonly documents: IDocument[];
    readonly activeDocument: IDocument | undefined;
    readonly visualFactory: IVisualFactory;
    readonly shapeFactory: IShapeFactory;
    
    openDocument(document: IDocument): void;
    closeDocument(document: IDocument): void;
    setActiveDocument(document: IDocument | undefined): void;
    // ...
}

实现类(chili/src/application.ts):

export class Application extends Observable implements IApplication {
    private static _instance: Application;
    private _documents: Document[] = [];
    private _activeDocument: Document | undefined;
    
    static get instance(): Application {
        return Application._instance;
    }
    
    constructor(
        readonly visualFactory: IVisualFactory,
        readonly shapeFactory: IShapeFactory,
        // ...
    ) {
        super();
        Application._instance = this;
    }
    
    get documents(): IDocument[] {
        return this._documents;
    }
    
    get activeDocument(): IDocument | undefined {
        return this._activeDocument;
    }
    
    openDocument(document: IDocument): void {
        this._documents.push(document as Document);
        this.setActiveDocument(document);
    }
    
    closeDocument(document: IDocument): void {
        const index = this._documents.indexOf(document as Document);
        if (index >= 0) {
            this._documents.splice(index, 1);
            if (this._activeDocument === document) {
                this.setActiveDocument(this._documents[0]);
            }
        }
    }
}

2.2.2 应用程序初始化流程

应用程序的初始化由AppBuilder类负责,采用Builder模式组装各个组件:

// chili-builder/src/appBuilder.ts
export class AppBuilder {
    private _storage?: IStorage;
    private _mainWindow?: MainWindow;
    
    useIndexedDB(): AppBuilder {
        this._storage = new IndexedDBStorage();
        return this;
    }
    
    useMainWindow(): AppBuilder {
        this._mainWindow = new MainWindow();
        return this;
    }
    
    async build(): Promise<IApplication> {
        // 初始化WASM模块
        await initChiliWasm();
        
        // 创建工厂类
        const shapeFactory = new ShapeFactory();
        const visualFactory = new ThreeVisualFactory();
        
        // 创建应用实例
        const app = new Application(
            visualFactory,
            shapeFactory,
            this._storage,
            // ...
        );
        
        // 初始化UI
        if (this._mainWindow) {
            document.body.appendChild(this._mainWindow);
        }
        
        return app;
    }
}

使用示例(chili-web入口):

const app = await new AppBuilder()
    .useIndexedDB()
    .useMainWindow()
    .build();

2.2.3 事件系统

Chili3D使用Observable模式实现事件通知机制:

// chili-core/src/foundation/observable.ts
export class Observable {
    private _observers: Map<string, Set<Observer>> = new Map();
    
    addObserver(eventName: string, observer: Observer): void {
        if (!this._observers.has(eventName)) {
            this._observers.set(eventName, new Set());
        }
        this._observers.get(eventName)!.add(observer);
    }
    
    removeObserver(eventName: string, observer: Observer): void {
        this._observers.get(eventName)?.delete(observer);
    }
    
    protected notify(eventName: string, ...args: any[]): void {
        this._observers.get(eventName)?.forEach(observer => {
            observer(...args);
        });
    }
}

2.3 文档模型

2.3.1 Document类

Document类代表一个CAD文档,包含了所有的几何数据、材质、视图配置等信息。

核心接口(chili-core/src/document.ts):

export interface IDocument {
    readonly id: string;
    readonly name: string;
    readonly application: IApplication;
    readonly rootNode: INode;
    readonly selection: ISelection;
    readonly history: IHistory;
    readonly materials: IMaterial[];
    readonly visual: IVisual;
    
    addNode(...nodes: INode[]): void;
    removeNode(...nodes: INode[]): void;
    save(): Promise<void>;
    // ...
}

实现类(chili/src/document.ts):

export class Document extends Observable implements IDocument {
    readonly id: string;
    private _name: string;
    readonly rootNode: GroupNode;
    readonly selection: Selection;
    readonly history: History;
    private _materials: Material[] = [];
    private _visual: IVisual;
    
    constructor(
        readonly application: IApplication,
        name: string,
        id?: string
    ) {
        super();
        this.id = id ?? crypto.randomUUID();
        this._name = name;
        this.rootNode = new GroupNode(this, "root");
        this.selection = new Selection(this);
        this.history = new History();
        
        // 创建可视化上下文
        this._visual = application.visualFactory.create(this);
        
        // 添加默认材质
        this._materials.push(Material.createDefault(this));
    }
    
    get name(): string {
        return this._name;
    }
    
    set name(value: string) {
        if (this._name !== value) {
            this._name = value;
            this.notify("nameChanged", value);
        }
    }
    
    addNode(...nodes: INode[]): void {
        nodes.forEach(node => {
            this.rootNode.addChild(node);
        });
    }
}

2.3.2 节点树结构

文档中的对象以树形结构组织,每个节点都实现INode接口:

// chili-core/src/model/node.ts
export interface INode {
    readonly id: string;
    readonly document: IDocument;
    name: string;
    parent: INode | undefined;
    readonly children: readonly INode[];
    visible: boolean;
    
    addChild(node: INode): void;
    removeChild(node: INode): void;
    clone(): INode;
}

主要节点类型

  1. GroupNode:分组节点,用于组织子节点
  2. GeometryNode:几何节点,包含形状数据
  3. FolderNode:文件夹节点,用于层级组织
export class GeometryNode extends Node {
    private _body: IBody;
    private _matrix: Matrix4;
    
    get shape(): IShape | undefined {
        return this._body.shape;
    }
    
    get matrix(): Matrix4 {
        return this._matrix;
    }
    
    set matrix(value: Matrix4) {
        if (!this._matrix.equals(value)) {
            this._matrix = value;
            this.notify("matrixChanged", value);
        }
    }
}

2.3.3 材质系统

材质系统管理文档中的所有材质定义:

// chili-core/src/material.ts
export interface IMaterial {
    readonly id: string;
    name: string;
    color: number;
    opacity: number;
    roughness: number;
    metalness: number;
    // ...
}

export class Material extends Observable implements IMaterial {
    readonly id: string;
    private _name: string;
    private _color: number;
    private _opacity: number = 1;
    private _roughness: number = 0.5;
    private _metalness: number = 0;
    
    static createDefault(document: IDocument): Material {
        return new Material(document, "Default", 0x808080);
    }
    
    constructor(
        readonly document: IDocument,
        name: string,
        color: number
    ) {
        super();
        this.id = crypto.randomUUID();
        this._name = name;
        this._color = color;
    }
    
    get color(): number {
        return this._color;
    }
    
    set color(value: number) {
        if (this._color !== value) {
            this._color = value;
            this.notify("colorChanged", value);
        }
    }
}

2.4 命令系统

2.4.1 命令模式概述

Chili3D采用命令模式(Command Pattern)来实现所有用户操作。命令模式的优点包括:

2.4.2 命令接口定义

// chili-core/src/command/command.ts
export interface ICommand {
    execute(document: IDocument): Promise<void>;
}

export interface IReversibleCommand extends ICommand {
    undo(): void;
    redo(): void;
}

命令装饰器

Chili3D使用装饰器来注册命令:

// 命令装饰器
export function command(options: CommandOptions) {
    return function (target: CommandConstructor) {
        CommandRegistry.register(options.name, target, options);
    };
}

// 使用示例
@command({
    name: "Create.Box",
    icon: "icon-box",
    display: "command.box"
})
export class BoxCommand extends CreateCommand {
    // ...
}

2.4.3 多步骤命令

对于需要多次用户交互的命令,Chili3D提供了MultistepCommand基类:

// chili/src/commands/multistepCommand.ts
export abstract class MultistepCommand implements ICommand {
    protected document: IDocument;
    protected stepDatas: Map<string, any> = new Map();
    
    abstract getSteps(): IStep[];
    
    async execute(document: IDocument): Promise<void> {
        this.document = document;
        const steps = this.getSteps();
        
        for (const step of steps) {
            const result = await step.execute(this);
            if (!result.success) {
                this.cancel();
                return;
            }
            this.stepDatas.set(step.name, result.data);
        }
        
        this.complete();
    }
    
    protected abstract complete(): void;
    protected cancel(): void {}
}

步骤接口

export interface IStep {
    readonly name: string;
    execute(command: MultistepCommand): Promise<StepResult>;
}

export interface StepResult {
    success: boolean;
    data?: any;
}

2.4.4 创建命令示例

以BoxCommand为例,展示完整的命令实现:

// chili/src/commands/create/box.ts
@command({
    name: "Create.Box",
    icon: "icon-box",
    display: "command.box"
})
export class BoxCommand extends CreateCommand {
    protected override getSteps(): IStep[] {
        return [
            new PointStep("origin", "prompt.selectFirstPoint"),
            new PointStep("corner", "prompt.selectSecondPoint"),
            new LengthStep("height", "prompt.inputHeight")
        ];
    }
    
    protected override createBody(): BoxBody | undefined {
        const origin = this.stepDatas.get("origin") as XYZ;
        const corner = this.stepDatas.get("corner") as XYZ;
        const height = this.stepDatas.get("height") as number;
        
        const dx = corner.x - origin.x;
        const dy = corner.y - origin.y;
        
        return new BoxBody(this.document, origin, dx, dy, height);
    }
}

2.4.5 命令执行器

命令执行器负责调用命令并管理历史记录:

export class CommandExecutor {
    async execute(command: ICommand, document: IDocument): Promise<void> {
        try {
            await command.execute(document);
            
            if (isReversibleCommand(command)) {
                document.history.add(command);
            }
        } catch (error) {
            console.error("Command execution failed:", error);
            throw error;
        }
    }
}

2.5 撤销/重做机制

2.5.1 历史记录管理

History类管理命令历史,支持撤销和重做操作:

export class History {
    private _undoStack: IReversibleCommand[] = [];
    private _redoStack: IReversibleCommand[] = [];
    private _maxSize: number = 100;
    
    get canUndo(): boolean {
        return this._undoStack.length > 0;
    }
    
    get canRedo(): boolean {
        return this._redoStack.length > 0;
    }
    
    add(command: IReversibleCommand): void {
        this._undoStack.push(command);
        this._redoStack = []; // 清空重做栈
        
        // 限制历史记录大小
        if (this._undoStack.length > this._maxSize) {
            this._undoStack.shift();
        }
    }
    
    undo(): void {
        const command = this._undoStack.pop();
        if (command) {
            command.undo();
            this._redoStack.push(command);
        }
    }
    
    redo(): void {
        const command = this._redoStack.pop();
        if (command) {
            command.redo();
            this._undoStack.push(command);
        }
    }
}

2.5.2 事务管理

对于需要原子性操作的场景,Chili3D提供事务支持:

export class Transaction {
    private _commands: IReversibleCommand[] = [];
    private _document: IDocument;
    
    constructor(document: IDocument) {
        this._document = document;
    }
    
    add(command: IReversibleCommand): void {
        this._commands.push(command);
    }
    
    commit(): void {
        // 将所有命令作为一个整体添加到历史记录
        const compositeCommand = new CompositeCommand(this._commands);
        this._document.history.add(compositeCommand);
    }
    
    rollback(): void {
        // 按逆序撤销所有命令
        for (let i = this._commands.length - 1; i >= 0; i--) {
            this._commands[i].undo();
        }
    }
}

2.6 序列化系统

2.6.1 序列化接口

Chili3D使用自定义的序列化系统来保存和加载文档:

// chili-core/src/serialize/serializer.ts
export interface ISerializable {
    serialize(): SerializedData;
}

export interface IDeserializer {
    deserialize(data: SerializedData): any;
}

export interface SerializedData {
    classKey: string;
    properties: Record<string, any>;
}

2.6.2 装饰器驱动的序列化

使用装饰器标记需要序列化的属性:

// 类装饰器
export function Serializable(classKey: string) {
    return function (target: any) {
        target.classKey = classKey;
        SerializerRegistry.register(classKey, target);
    };
}

// 属性装饰器
export function Property(options?: PropertyOptions) {
    return function (target: any, propertyKey: string) {
        const metadata = getSerializeMetadata(target);
        metadata.properties.push({
            key: propertyKey,
            options: options ?? {}
        });
    };
}

使用示例

@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;
    }
}

2.6.3 文档序列化流程

export class DocumentSerializer {
    serialize(document: IDocument): DocumentData {
        return {
            id: document.id,
            name: document.name,
            version: DOCUMENT_VERSION,
            rootNode: this.serializeNode(document.rootNode),
            materials: document.materials.map(m => this.serializeMaterial(m)),
            // ...
        };
    }
    
    private serializeNode(node: INode): NodeData {
        const data: NodeData = {
            classKey: node.constructor.classKey,
            id: node.id,
            name: node.name,
            visible: node.visible,
            children: node.children.map(c => this.serializeNode(c))
        };
        
        if (node instanceof GeometryNode) {
            data.body = this.serializeBody(node.body);
            data.matrix = node.matrix.toArray();
        }
        
        return data;
    }
}

2.7 依赖注入与服务定位

2.7.1 服务注册

Chili3D使用简单的服务定位模式来管理全局服务:

// chili-core/src/service.ts
export class Services {
    private static _services: Map<string, any> = new Map();
    
    static register<T>(key: string, service: T): void {
        this._services.set(key, service);
    }
    
    static get<T>(key: string): T {
        const service = this._services.get(key);
        if (!service) {
            throw new Error(`Service not found: ${key}`);
        }
        return service;
    }
    
    static tryGet<T>(key: string): T | undefined {
        return this._services.get(key);
    }
}

2.7.2 服务使用示例

// 注册服务
Services.register("shapeFactory", new ShapeFactory());
Services.register("visualFactory", new ThreeVisualFactory());

// 获取服务
const shapeFactory = Services.get<IShapeFactory>("shapeFactory");
const shape = shapeFactory.box(origin, dx, dy, dz);

2.8 配置系统

2.8.1 全局配置

Chili3D提供了灵活的配置系统:

// chili-core/src/config.ts
export class Config {
    private static _instance: Config;
    private _settings: Map<string, any> = new Map();
    
    static get instance(): Config {
        if (!this._instance) {
            this._instance = new Config();
            this._instance.loadDefaults();
        }
        return this._instance;
    }
    
    private loadDefaults(): void {
        this._settings.set("snapTolerance", 10);
        this._settings.set("gridSize", 1);
        this._settings.set("precision", 1e-6);
        this._settings.set("language", "zh");
        this._settings.set("theme", "light");
        // ...
    }
    
    get<T>(key: string): T {
        return this._settings.get(key);
    }
    
    set<T>(key: string, value: T): void {
        this._settings.set(key, value);
        this.save();
    }
    
    private save(): void {
        localStorage.setItem("chili3d_config", 
            JSON.stringify(Object.fromEntries(this._settings)));
    }
    
    private load(): void {
        const saved = localStorage.getItem("chili3d_config");
        if (saved) {
            const parsed = JSON.parse(saved);
            Object.entries(parsed).forEach(([key, value]) => {
                this._settings.set(key, value);
            });
        }
    }
}

2.8.2 配置使用

// 获取配置
const snapTolerance = Config.instance.get<number>("snapTolerance");

// 设置配置
Config.instance.set("language", "en");

2.9 国际化支持

2.9.1 I18n实现

// chili-core/src/i18n/i18n.ts
export class I18n {
    private static _currentLanguage: string = "zh";
    private static _translations: Map<string, Record<string, string>> = new Map();
    
    static setLanguage(lang: string): void {
        this._currentLanguage = lang;
    }
    
    static translate(key: string): string {
        const translations = this._translations.get(this._currentLanguage);
        return translations?.[key] ?? key;
    }
    
    static registerTranslations(lang: string, translations: Record<string, string>): void {
        this._translations.set(lang, translations);
    }
}

// 简写函数
export function t(key: string): string {
    return I18n.translate(key);
}

2.9.2 语言资源

// 中文资源
I18n.registerTranslations("zh", {
    "command.box": "长方体",
    "command.sphere": "球体",
    "command.cylinder": "圆柱体",
    "prompt.selectFirstPoint": "选择第一个点",
    "prompt.selectSecondPoint": "选择第二个点",
    // ...
});

// 英文资源
I18n.registerTranslations("en", {
    "command.box": "Box",
    "command.sphere": "Sphere",
    "command.cylinder": "Cylinder",
    "prompt.selectFirstPoint": "Select first point",
    "prompt.selectSecondPoint": "Select second point",
    // ...
});

2.10 本章小结

本章深入分析了Chili3D的核心架构,包括:

  1. 分层架构设计:清晰的模块划分和依赖关系
  2. 应用程序生命周期:Application类的职责和初始化流程
  3. 文档模型:Document类、节点树结构和材质系统
  4. 命令系统:命令模式的实现和多步骤命令支持
  5. 撤销/重做机制:历史记录管理和事务支持
  6. 序列化系统:装饰器驱动的序列化实现
  7. 依赖注入:服务定位模式
  8. 配置系统:全局配置管理
  9. 国际化支持:多语言翻译机制

理解这些核心概念对于进行Chili3D的二次开发至关重要。在后续章节中,我们将基于这些基础知识,深入探讨几何建模、用户界面和扩展开发等高级主题。


下一章预告:第三章将详细介绍Chili3D的几何建模系统,包括OpenCascade内核的使用、形状创建与操作、布尔运算等核心几何功能。