第二章:核心架構解析
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 依賴關係
各包之間的依賴關係遵循自下而上的原則,上層包可以依賴下層包,但下層包不能依賴上層包:
- chili-core:無外部依賴,是整個系統的基礎
- chili-wasm:依賴chili-core
- chili-geo:依賴chili-core
- chili-vis:依賴chili-core
- chili-three:依賴chili-core、chili-vis
- chili-storage:依賴chili-core
- chili-controls:依賴chili-core
- chili-ui:依賴chili-core、chili-controls
- chili:依賴chili-core、chili-wasm
- chili-builder:依賴所有其他包
這種依賴結構確保了核心庫的穩定性和可複用性。
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;
}
主要節點類型:
- GroupNode:分組節點,用於組織子節點
- GeometryNode:幾何節點,包含形狀數據
- 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的核心架構,包括:
- 分層架構設計:清晰的模塊劃分和依賴關係
- 應用程序生命週期:Application類的職責和初始化流程
- 文檔模型:Document類、節點樹結構和材質系統
- 命令系統:命令模式的實現和多步驟命令支持
- 撤銷/重做機制:歷史記錄管理和事務支持
- 序列化系統:裝飾器驅動的序列化實現
- 依賴注入:服務定位模式
- 配置系統:全局配置管理
- 國際化支持:多語言翻譯機制
理解這些核心概念對於進行Chili3D的二次開發至關重要。在後續章節中,我們將基於這些基礎知識,深入探討幾何建模、用户界面和擴展開發等高級主題。