第二章:核心架构解析
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的二次开发至关重要。在后续章节中,我们将基于这些基础知识,深入探讨几何建模、用户界面和扩展开发等高级主题。
下一章预告:第三章将详细介绍Chili3D的几何建模系统,包括OpenCascade内核的使用、形状创建与操作、布尔运算等核心几何功能。