diff --git a/packages/chili-core/src/application.ts b/packages/chili-core/src/application.ts index 6e5d929c..61014019 100644 --- a/packages/chili-core/src/application.ts +++ b/packages/chili-core/src/application.ts @@ -3,9 +3,9 @@ import { ICommand } from "./command"; import { IDocument } from "./document"; import { IStorage, ObservableCollection } from "./foundation"; -import { IShapeFactory } from "./geometry"; import { Serialized } from "./serialize"; import { IService } from "./service"; +import { IShapeFactory } from "./shape"; import { IWindow } from "./ui/window"; import { IView, IVisualFactory } from "./visual"; diff --git a/packages/chili-core/src/i18n/en.ts b/packages/chili-core/src/i18n/en.ts index bcd884ad..d2881e55 100644 --- a/packages/chili-core/src/i18n/en.ts +++ b/packages/chili-core/src/i18n/en.ts @@ -6,6 +6,7 @@ export default { display: "English", code: "en", translation: { + "common.general": "General", "common.color": "Color", "common.opacity": "Opacity", "common.name": "Name", @@ -42,9 +43,9 @@ export default { "material.texture": "Texture", "material.repeatU": "U repeat", "material.repeatV": "V repeat", - "model.translation": "Translation", - "model.rotation": "Rotation", - "model.scale": "Scale", + "transform.translation": "Translation", + "transform.rotation": "Rotation", + "transform.scale": "Scale", "model.visible": "Visible", "vertex.point": "Point", "line.type.line": "Line", diff --git a/packages/chili-core/src/i18n/keys.ts b/packages/chili-core/src/i18n/keys.ts index 0fa185d2..810c2214 100644 --- a/packages/chili-core/src/i18n/keys.ts +++ b/packages/chili-core/src/i18n/keys.ts @@ -1,6 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. export type I18nKeys = + | "common.general" | "common.color" | "common.name" | "common.matrix" @@ -51,9 +52,6 @@ export type I18nKeys = | "material.texture" | "material.repeatU" | "material.repeatV" - | "model.translation" - | "model.rotation" - | "model.scale" | "model.visible" | "vertex.point" | "line.type.line" @@ -118,6 +116,9 @@ export type I18nKeys = | "axis.x" | "axis.y" | "axis.z" + | "transform.translation" + | "transform.rotation" + | "transform.scale" | "toast.command.{0}excuting" | "toast.document.saved" | "toast.document.noActived" diff --git a/packages/chili-core/src/i18n/zh-cn.ts b/packages/chili-core/src/i18n/zh-cn.ts index 43c7af5b..9580cc64 100644 --- a/packages/chili-core/src/i18n/zh-cn.ts +++ b/packages/chili-core/src/i18n/zh-cn.ts @@ -6,6 +6,7 @@ export default { display: "简体中文", code: "zh-CN", translation: { + "common.general": "常规", "common.color": "颜色", "common.opacity": "不透明度", "common.cancel": "取消", @@ -42,9 +43,9 @@ export default { "material.texture": "贴图", "material.repeatU": "U 重复", "material.repeatV": "V 重复", - "model.translation": "位移", - "model.rotation": "旋转", - "model.scale": "缩放", + "transform.translation": "位移", + "transform.rotation": "旋转", + "transform.scale": "缩放", "model.visible": "可见", "vertex.point": "点", "line.type.line": "直线", diff --git a/packages/chili-core/src/index.ts b/packages/chili-core/src/index.ts index 8eec21e2..c7680302 100644 --- a/packages/chili-core/src/index.ts +++ b/packages/chili-core/src/index.ts @@ -7,7 +7,6 @@ export * from "./constants"; export * from "./document"; export * from "./editor"; export * from "./foundation"; -export * from "./geometry"; export * from "./i18n"; export * from "./material"; export * from "./math"; @@ -17,6 +16,7 @@ export * from "./selection"; export * from "./selectionFilter"; export * from "./serialize"; export * from "./service"; +export * from "./shape"; export * from "./snapType"; export * from "./ui"; export * from "./visual"; diff --git a/packages/chili-core/src/model/body.ts b/packages/chili-core/src/model/body.ts deleted file mode 100644 index 813b5582..00000000 --- a/packages/chili-core/src/model/body.ts +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. - -import { Property } from "../property"; -import { Serializer } from "../serialize"; -import { Entity } from "./entity"; - -export abstract class Body extends Entity {} - -export abstract class FaceableBody extends Body { - protected _isFace: boolean = false; - @Serializer.serialze() - @Property.define("command.faceable.isFace") - get isFace() { - return this._isFace; - } - set isFace(value: boolean) { - this.setPropertyAndUpdate("isFace", value); - } -} diff --git a/packages/chili-core/src/model/entity.ts b/packages/chili-core/src/model/entity.ts index 5ef32037..230e626e 100644 --- a/packages/chili-core/src/model/entity.ts +++ b/packages/chili-core/src/model/entity.ts @@ -1,22 +1,17 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { HistoryObservable, IEqualityComparer, Result } from "../foundation"; -import { IShape } from "../geometry"; import { I18nKeys } from "../i18n"; +import { IShape } from "../shape"; export abstract class Entity extends HistoryObservable { - protected readonly shapeChangeHandler: Set<(source: Entity) => void> = new Set(); - - private _retryCount: number = 0; protected shouldRegenerate: boolean = true; abstract display: I18nKeys; - private _shape: Result = Result.err("Not initialised"); - + protected _shape: Result = Result.err("Not initialised"); get shape(): Result { - if (this.shouldRegenerate || (!this._shape.isOk && this._retryCount < 3)) { + if (this.shouldRegenerate) { this._shape = this.generateShape(); - this._retryCount = this._shape.isOk ? 0 : this._retryCount + 1; this.shouldRegenerate = false; } return this._shape; @@ -35,15 +30,7 @@ export abstract class Entity extends HistoryObservable { } protected emitShapeChanged() { - this.shapeChangeHandler.forEach((handler) => handler(this)); - } - - onShapeChanged(handler: (source: Entity) => void) { - this.shapeChangeHandler.add(handler); - } - - removeShapeChanged(handler: (source: Entity) => void) { - this.shapeChangeHandler.delete(handler); + this.emitPropertyChanged("shape", this._shape); } protected abstract generateShape(): Result; diff --git a/packages/chili-core/src/model/feature.ts b/packages/chili-core/src/model/feature.ts index 7da9001a..bd00c09b 100644 --- a/packages/chili-core/src/model/feature.ts +++ b/packages/chili-core/src/model/feature.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IShape } from "../geometry"; +import { IShape } from "../shape"; import { Entity } from "./entity"; export abstract class Feature extends Entity { diff --git a/packages/chili-core/src/model/geometry.ts b/packages/chili-core/src/model/geometry.ts new file mode 100644 index 00000000..67be2a43 --- /dev/null +++ b/packages/chili-core/src/model/geometry.ts @@ -0,0 +1,124 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { IDocument } from "../document"; +import { Matrix4 } from "../math"; +import { Property } from "../property"; +import { Serializer } from "../serialize"; +import { Entity } from "./entity"; + +export abstract class GeometryObject extends Entity { + private _materialId: string; + @Serializer.serialze() + @Property.define("common.material", { type: "materialId" }) + get materialId(): string { + return this._materialId; + } + set materialId(value: string) { + this.setProperty("materialId", value); + } + + protected _matrix: Matrix4 = Matrix4.identity(); + @Serializer.serialze() + get matrix(): Matrix4 { + return this._matrix; + } + set matrix(value: Matrix4) { + this.setProperty( + "matrix", + value, + () => { + if (this.shape.isOk) { + this.shape.value.matrix = value; + } + }, + { + equals: (left, right) => left.equals(right), + }, + ); + } + + constructor(document: IDocument, materialId?: string) { + super(document); + this._materialId = materialId ?? document.materials.at(0)!.id; + } +} + +/* +export abstract class HistoryBody extends Body { + private readonly _features: Feature[] = []; + + private onShapeChanged = (entity: Entity) => { + if (entity === this) { + this.drawShape(); + } else { + let editor = entity as Feature; + let i = this._features.indexOf(editor); + this.applyFeatures(i); + } + this.redrawModel(); + }; + + drawShape() { + this.applyFeatures(0); + } + + private applyFeatures(startIndex: number) { + if (startIndex < 0) return; + for (let i = startIndex; i < this._features.length; i++) { + this._shape = this._features[i].shape; + if (!this._shape.isOk) { + return; + } + } + if (this._shape) { + this._shape.matrix = this._matrix; + } + } + + removeFeature(feature: Feature) { + const index = this._features.indexOf(feature, 0); + if (index > -1) { + this._features.splice(index, 1); + feature.removeShapeChanged(this.onShapeChanged); + this._features[index].origin = + index === 0 ? this.body.shape.unwrap() : this._features[index - 1].shape.unwrap(); // todo + this.applyFeatures(index); + this.redrawModel(); + } + } + + addFeature(feature: Feature) { + if (this._features.indexOf(feature) > -1) return; + this._features.push(feature); + feature.onShapeChanged(this.onShapeChanged); + if (this._shape !== undefined) { + feature.origin = this._shape; + this.applyFeatures(this._features.length - 1); + this.redrawModel(); + } + } + + getFeature(index: number) { + if (index < this._features.length) { + return this._features[index]; + } + return undefined; + } + + features() { + return [...this._features]; + } +} +*/ + +export abstract class FaceableGeometry extends GeometryObject { + protected _isFace: boolean = false; + @Serializer.serialze() + @Property.define("command.faceable.isFace") + get isFace() { + return this._isFace; + } + set isFace(value: boolean) { + this.setPropertyAndUpdate("isFace", value); + } +} diff --git a/packages/chili-core/src/model/index.ts b/packages/chili-core/src/model/index.ts index cd014e09..46698152 100644 --- a/packages/chili-core/src/model/index.ts +++ b/packages/chili-core/src/model/index.ts @@ -1,8 +1,8 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -export * from "./body"; export * from "./entity"; export * from "./feature"; +export * from "./geometry"; export * from "./model"; export * from "./node"; export * from "./nodeLinkedList"; diff --git a/packages/chili-core/src/model/model.ts b/packages/chili-core/src/model/model.ts index 9e4ce765..9048f2d9 100644 --- a/packages/chili-core/src/model/model.ts +++ b/packages/chili-core/src/model/model.ts @@ -1,101 +1,25 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDocument } from "../document"; -import { Id, Logger } from "../foundation"; -import { ICompound, IShape } from "../geometry"; -import { Matrix4 } from "../math"; -import { Property } from "../property"; +import { Id } from "../foundation"; import { Serializer } from "../serialize"; -import { Body } from "./body"; -import { Entity } from "./entity"; -import { Feature } from "./feature"; +import { GeometryObject } from "./geometry"; import { IModel, IModelGroup, Node } from "./node"; -export abstract class Model extends Node implements IModel { +export abstract class Model extends Node implements IModel { @Serializer.serialze() - readonly body: Body; + readonly geometry: GeometryObject; - protected _shape: T | undefined; - - shape(): T | undefined { - return this._shape; - } - - protected _matrix: Matrix4 = Matrix4.identity(); - - @Serializer.serialze() - get matrix(): Matrix4 { - return this._matrix; - } - - set matrix(value: Matrix4) { - this.setProperty( - "matrix", - value, - () => { - if (this._shape) { - this._shape.matrix = value; - } - }, - { - equals: (left, right) => left.equals(right), - }, - ); - } - - private _materialId: string; - @Serializer.serialze() - @Property.define("common.material") - get materialId(): string { - return this._materialId; - } - - set materialId(value: string) { - this.setProperty("materialId", value); - } - - constructor( - document: IDocument, - name: string, - body: Body, - materialId: string, - id: string = Id.generate(), - ) { + constructor(document: IDocument, name: string, body: GeometryObject, id: string = Id.generate()) { super(document, name, id); - this.body = body; - this._materialId = materialId; + this.geometry = body; } } -@Serializer.register("GeometryModel", ["document", "name", "body", "id"]) +@Serializer.register("GeometryModel", ["document", "name", "geometry", "id"]) export class GeometryModel extends Model { - private readonly _features: Feature[] = []; - - private _error: string | undefined; - error() { - return this._error; - } - - constructor(document: IDocument, name: string, body: Body, id: string = Id.generate()) { - super(document, name, body, document.materials.at(0)!.id, id); - this.drawShape(); - body.onShapeChanged(this.onShapeChanged); - } - - private onShapeChanged = (entity: Entity) => { - if (entity === this.body) { - this.drawShape(); - } else { - let editor = entity as Feature; - let i = this._features.indexOf(editor); - this.applyFeatures(i); - } - this.redrawModel(); - }; - - drawShape() { - this._shape = this.body.shape.value; - this.applyFeatures(0); + constructor(document: IDocument, name: string, geometry: GeometryObject, id: string = Id.generate()) { + super(document, name, geometry, id); } protected onVisibleChanged(): void { @@ -105,63 +29,9 @@ export class GeometryModel extends Model { protected onParentVisibleChanged(): void { this.document.visual.context.setVisible(this, this.visible && this.parentVisible); } - - private redrawModel() { - this.document.visual.context.redrawModel([this]); - Logger.debug(`model ${this.name} redraw`); - } - - private applyFeatures(startIndex: number) { - if (startIndex < 0) return; - for (let i = startIndex; i < this._features.length; i++) { - let shape = this._features[i].shape; - if (!shape.isOk) { - this._error = shape.error; - return; - } - this._shape = shape.value; - } - if (this._shape) { - this._shape.matrix = this._matrix; - } - } - - removeFeature(feature: Feature) { - const index = this._features.indexOf(feature, 0); - if (index > -1) { - this._features.splice(index, 1); - feature.removeShapeChanged(this.onShapeChanged); - this._features[index].origin = - index === 0 ? this.body.shape.unwrap() : this._features[index - 1].shape.unwrap(); // todo - this.applyFeatures(index); - this.redrawModel(); - } - } - - addFeature(feature: Feature) { - if (this._features.indexOf(feature) > -1) return; - this._features.push(feature); - feature.onShapeChanged(this.onShapeChanged); - if (this._shape !== undefined) { - feature.origin = this._shape; - this.applyFeatures(this._features.length - 1); - this.redrawModel(); - } - } - - getFeature(index: number) { - if (index < this._features.length) { - return this._features[index]; - } - return undefined; - } - - features() { - return [...this._features]; - } } -export class ModelGroup extends Model implements IModelGroup { +export class ModelGroup extends Model implements IModelGroup { private readonly _children: IModel[] = []; get children(): ReadonlyArray { @@ -169,11 +39,11 @@ export class ModelGroup extends Model implements IModelGroup { } protected onVisibleChanged(): void { - this._children.forEach((x) => (x.parentVisible = this.visible && this.parentVisible)); + this.document.visual.context.setVisible(this, this.visible && this.parentVisible); } protected onParentVisibleChanged(): void { - this._children.forEach((x) => (x.parentVisible = this.visible && this.parentVisible)); + this.document.visual.context.setVisible(this, this.visible && this.parentVisible); } override clone(): this { diff --git a/packages/chili-core/src/model/node.ts b/packages/chili-core/src/model/node.ts index 5de52261..e53647ea 100644 --- a/packages/chili-core/src/model/node.ts +++ b/packages/chili-core/src/model/node.ts @@ -2,11 +2,9 @@ import { IDocument } from "../document"; import { HistoryObservable, IDisposable, IPropertyChanged, Id } from "../foundation"; -import { IShape } from "../geometry"; -import { Matrix4 } from "../math"; import { Property } from "../property"; import { Serialized, Serializer } from "../serialize"; -import { Entity } from "./entity"; +import { GeometryObject } from "./geometry"; export interface INode extends IPropertyChanged, IDisposable { readonly id: string; @@ -32,10 +30,7 @@ export interface INodeLinkedList extends INode { export interface IModel extends INode { readonly document: IDocument; - readonly body: Entity; - materialId: string; - matrix: Matrix4; - shape(): IShape | undefined; + readonly geometry: GeometryObject; } export interface IModelGroup extends IModel { @@ -48,12 +43,12 @@ export namespace INode { } export function isModelNode(node: INode): node is IModel { - return (node as IModel).matrix !== undefined; + return (node as IModel).geometry !== undefined; } export function isModelGroup(node: INode): node is IModelGroup { let group = node as IModelGroup; - return group.matrix !== undefined && group.children !== undefined; + return group.geometry !== undefined && group.children !== undefined; } } diff --git a/packages/chili-core/src/property.ts b/packages/chili-core/src/property.ts index c5c2bbba..0bbf4661 100644 --- a/packages/chili-core/src/property.ts +++ b/packages/chili-core/src/property.ts @@ -3,7 +3,7 @@ import { IConverter } from "./foundation"; import { I18nKeys } from "./i18n"; -export type PropertyType = "color"; +export type PropertyType = "color" | "materialId"; export interface Property { name: string; @@ -30,18 +30,24 @@ export namespace Property { }; } - export function getProperties(target: any): Property[] { + export function getProperties(target: any, until?: object): Property[] { let result: Property[] = []; - getAllKeysOfPrototypeChain(target, result); + getAllKeysOfPrototypeChain(target, result, until); return result; } - function getAllKeysOfPrototypeChain(target: any, properties: Property[]) { - if (!target) return; + export function getOwnProperties(target: any): Property[] { + let properties = PropertyKeyMap.get(target); + if (!properties) return []; + return [...properties.values()]; + } + + function getAllKeysOfPrototypeChain(target: any, properties: Property[], until?: object) { + if (!target || target === until) return; if (PropertyKeyMap.has(target)) { properties.splice(0, 0, ...PropertyKeyMap.get(target)!.values()); } - getAllKeysOfPrototypeChain(Object.getPrototypeOf(target), properties); + getAllKeysOfPrototypeChain(Object.getPrototypeOf(target), properties, until); } export function getProperty(target: T, property: keyof T): Property | undefined { diff --git a/packages/chili-core/src/selection.ts b/packages/chili-core/src/selection.ts index 760293d9..28bb4f19 100644 --- a/packages/chili-core/src/selection.ts +++ b/packages/chili-core/src/selection.ts @@ -1,10 +1,10 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { AsyncController, IDisposable } from "./foundation"; -import { ShapeType } from "./geometry"; import { I18nKeys } from "./i18n"; import { IModel, INode } from "./model"; import { IShapeFilter } from "./selectionFilter"; +import { ShapeType } from "./shape"; import { CursorType, IEventHandler, VisualShapeData } from "./visual"; export interface ISelection extends IDisposable { diff --git a/packages/chili-core/src/selectionFilter.ts b/packages/chili-core/src/selectionFilter.ts index 89c5abeb..4ffb806d 100644 --- a/packages/chili-core/src/selectionFilter.ts +++ b/packages/chili-core/src/selectionFilter.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IShape } from "./geometry"; +import { IShape } from "./shape"; export interface IShapeFilter { allow(shape: IShape): boolean; diff --git a/packages/chili-core/src/geometry/geometry.ts b/packages/chili-core/src/shape/curve.ts similarity index 100% rename from packages/chili-core/src/geometry/geometry.ts rename to packages/chili-core/src/shape/curve.ts diff --git a/packages/chili-core/src/geometry/index.ts b/packages/chili-core/src/shape/index.ts similarity index 90% rename from packages/chili-core/src/geometry/index.ts rename to packages/chili-core/src/shape/index.ts index b3cf4b04..dd2b283b 100644 --- a/packages/chili-core/src/geometry/index.ts +++ b/packages/chili-core/src/shape/index.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -export * from "./geometry"; +export * from "./curve"; export * from "./lineType"; export * from "./meshData"; export * from "./shape"; diff --git a/packages/chili-core/src/geometry/lineType.ts b/packages/chili-core/src/shape/lineType.ts similarity index 100% rename from packages/chili-core/src/geometry/lineType.ts rename to packages/chili-core/src/shape/lineType.ts diff --git a/packages/chili-core/src/geometry/meshData.ts b/packages/chili-core/src/shape/meshData.ts similarity index 100% rename from packages/chili-core/src/geometry/meshData.ts rename to packages/chili-core/src/shape/meshData.ts diff --git a/packages/chili-core/src/geometry/shape.ts b/packages/chili-core/src/shape/shape.ts similarity index 98% rename from packages/chili-core/src/geometry/shape.ts rename to packages/chili-core/src/shape/shape.ts index 182f8132..1e00f9af 100644 --- a/packages/chili-core/src/geometry/shape.ts +++ b/packages/chili-core/src/shape/shape.ts @@ -2,7 +2,7 @@ import { Result } from "../foundation"; import { Matrix4, Ray, XYZ } from "../math"; -import { ICurve } from "./geometry"; +import { ICurve } from "./curve"; import { IShapeMeshData } from "./meshData"; import { ShapeType } from "./shapeType"; diff --git a/packages/chili-core/src/geometry/shapeConverter.ts b/packages/chili-core/src/shape/shapeConverter.ts similarity index 100% rename from packages/chili-core/src/geometry/shapeConverter.ts rename to packages/chili-core/src/shape/shapeConverter.ts diff --git a/packages/chili-core/src/geometry/shapeFactory.ts b/packages/chili-core/src/shape/shapeFactory.ts similarity index 100% rename from packages/chili-core/src/geometry/shapeFactory.ts rename to packages/chili-core/src/shape/shapeFactory.ts diff --git a/packages/chili-core/src/geometry/shapeType.ts b/packages/chili-core/src/shape/shapeType.ts similarity index 100% rename from packages/chili-core/src/geometry/shapeType.ts rename to packages/chili-core/src/shape/shapeType.ts diff --git a/packages/chili-core/src/visual/detectedData.ts b/packages/chili-core/src/visual/detectedData.ts index 4af4e3a5..1c3c93ac 100644 --- a/packages/chili-core/src/visual/detectedData.ts +++ b/packages/chili-core/src/visual/detectedData.ts @@ -1,10 +1,10 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IShape } from "../geometry"; -import { IVisualShape } from "./visualShape"; +import { IShape } from "../shape"; +import { IVisualGeometry } from "./visualShape"; export interface VisualShapeData { shape: IShape; - owner: IVisualShape; + owner: IVisualGeometry; indexes: number[]; } diff --git a/packages/chili-core/src/visual/highlighter.ts b/packages/chili-core/src/visual/highlighter.ts index 83c889d1..e9d2668a 100644 --- a/packages/chili-core/src/visual/highlighter.ts +++ b/packages/chili-core/src/visual/highlighter.ts @@ -1,13 +1,13 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { ShapeType } from "../geometry"; -import { IVisualShape, VisualState } from "./visualShape"; +import { ShapeType } from "../shape"; +import { IVisualGeometry, VisualState } from "./visualShape"; export interface IHighlighter { clear(): void; - removeAllStates(shape: IVisualShape, resetState: boolean): void; + removeAllStates(shape: IVisualGeometry, resetState: boolean): void; updateStateData( - shape: IVisualShape, + shape: IVisualGeometry, mode: "add" | "remove", state: VisualState, type: ShapeType, diff --git a/packages/chili-core/src/visual/view.ts b/packages/chili-core/src/visual/view.ts index d6641d09..75b947ec 100644 --- a/packages/chili-core/src/visual/view.ts +++ b/packages/chili-core/src/visual/view.ts @@ -2,9 +2,9 @@ import { IDocument } from "../document"; import { IDisposable, IPropertyChanged } from "../foundation"; -import { ShapeType } from "../geometry"; import { Plane, Ray, XY, XYZ } from "../math"; import { IShapeFilter } from "../selectionFilter"; +import { ShapeType } from "../shape"; import { ICameraController } from "./cameraController"; import { VisualShapeData } from "./detectedData"; diff --git a/packages/chili-core/src/visual/visualContext.ts b/packages/chili-core/src/visual/visualContext.ts index d1674cf2..29874410 100644 --- a/packages/chili-core/src/visual/visualContext.ts +++ b/packages/chili-core/src/visual/visualContext.ts @@ -1,10 +1,10 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDisposable } from "../foundation"; -import { ShapeMeshData } from "../geometry"; import { IModel } from "../model"; +import { ShapeMeshData } from "../shape"; import { IVisualObject } from "./visualObject"; -import { IVisualShape } from "./visualShape"; +import { IVisualGeometry } from "./visualShape"; export interface IVisualContext extends IDisposable { get shapeCount(): number; @@ -13,11 +13,11 @@ export interface IVisualContext extends IDisposable { removeVisualObject(object: IVisualObject): void; addModel(models: IModel[]): void; removeModel(models: IModel[]): void; - getShape(model: IModel): IVisualShape | undefined; - getModel(shape: IVisualShape): IModel | undefined; + getShape(model: IModel): IVisualGeometry | undefined; + getModel(shape: IVisualGeometry): IModel | undefined; redrawModel(models: IModel[]): void; setVisible(model: IModel, visible: boolean): void; - shapes(): IVisualShape[]; + shapes(): IVisualGeometry[]; displayShapeMesh(...datas: ShapeMeshData[]): number; removeShapeMesh(id: number): void; } diff --git a/packages/chili-core/src/visual/visualShape.ts b/packages/chili-core/src/visual/visualShape.ts index 7c960c05..8d5e66c0 100644 --- a/packages/chili-core/src/visual/visualShape.ts +++ b/packages/chili-core/src/visual/visualShape.ts @@ -1,6 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IShape, ShapeType } from "../geometry"; +import { GeometryObject } from "../model"; +import { ShapeType } from "../shape"; import { IVisualObject } from "./visualObject"; export enum VisualState { @@ -29,8 +30,8 @@ export interface VisualGroup { materialIndex?: number; } -export interface IVisualShape extends IVisualObject { - get shape(): IShape; +export interface IVisualGeometry extends IVisualObject { + get geometry(): GeometryObject; addState(state: VisualState, type: ShapeType, ...indexes: number[]): void; removeState(state: VisualState, type: ShapeType, ...indexes: number[]): void; resetState(): void; diff --git a/packages/chili-three/src/cameraController.ts b/packages/chili-three/src/cameraController.ts index 10d8ebfe..532917de 100644 --- a/packages/chili-three/src/cameraController.ts +++ b/packages/chili-three/src/cameraController.ts @@ -2,8 +2,8 @@ import { ICameraController, Point, ShapeType } from "chili-core"; import { Box3, Matrix4, OrthographicCamera, PerspectiveCamera, Sphere, Vector3 } from "three"; +import { ThreeGeometry } from "./threeGeometry"; import { ThreeHelper } from "./threeHelper"; -import { ThreeShape } from "./threeShape"; import { ThreeView } from "./threeView"; import { ThreeVisualContext } from "./threeVisualContext"; @@ -85,7 +85,7 @@ export class CameraController implements ICameraController { startRotate(x: number, y: number): void { let shape = this.view.detected(ShapeType.Shape, x, y).at(0)?.owner; - if (shape instanceof ThreeShape) { + if (shape instanceof ThreeGeometry) { this._rotateCenter = new Vector3(); let box = new Box3(); box.setFromObject(shape); diff --git a/packages/chili-three/src/threeShape.ts b/packages/chili-three/src/threeGeometry.ts similarity index 88% rename from packages/chili-three/src/threeShape.ts rename to packages/chili-three/src/threeGeometry.ts index 1efc76ee..7fa9e472 100644 --- a/packages/chili-three/src/threeShape.ts +++ b/packages/chili-three/src/threeGeometry.ts @@ -3,9 +3,9 @@ import { EdgeMeshData, FaceMeshData, + GeometryObject, IHighlighter, - IShape, - IVisualShape, + IVisualGeometry, Matrix4, ShapeMeshData, ShapeType, @@ -26,6 +26,7 @@ import { } from "three"; import { ThreeHelper } from "./threeHelper"; +import { ThreeVisualContext } from "./threeVisualContext"; const hilightEdgeMaterial = new LineBasicMaterial({ color: ThreeHelper.fromColor(VisualConfig.highlightEdgeColor), @@ -61,7 +62,7 @@ const selectedFaceMaterial = new MeshLambertMaterial({ polygonOffsetUnits: -1, }); -export class ThreeShape extends Object3D implements IVisualShape { +export class ThreeGeometry extends Object3D implements IVisualGeometry { private readonly _highlightedFaces: Map = new Map(); private readonly _highlightedEdges: Map = new Map(); @@ -91,18 +92,29 @@ export class ThreeShape extends Object3D implements IVisualShape { } constructor( - readonly shape: IShape, + readonly geometry: GeometryObject, readonly highlighter: IHighlighter, - material: Material, + readonly context: ThreeVisualContext, ) { super(); - let mesh = this.shape.mesh; - this._faceMaterial = material; + let mesh = this.geometry.shape.value?.mesh; + this.transform = geometry.matrix; + this._faceMaterial = context.getMaterial(geometry.materialId); this.matrixAutoUpdate = false; - if (mesh.faces?.positions.length) this.add(this.initFaces(mesh.faces)); - if (mesh.edges?.positions.length) this.add(this.initEdges(mesh.edges)); + if (mesh?.faces?.positions.length) this.add(this.initFaces(mesh.faces)); + if (mesh?.edges?.positions.length) this.add(this.initEdges(mesh.edges)); + geometry.onPropertyChanged(this.handleGeometryPropertyChanged); } + private handleGeometryPropertyChanged = (property: keyof GeometryObject) => { + if (property === "matrix") { + this.transform = this.geometry.matrix; + } else if (property === "materialId") { + let material = this.context.getMaterial(this.geometry.materialId)!; + this.setFaceMaterial(material); + } + }; + dispose() { if (this._edges) { this._edges.geometry.dispose(); @@ -110,6 +122,7 @@ export class ThreeShape extends Object3D implements IVisualShape { if (this._faces) { this._faces.geometry.dispose(); } + this.geometry.removePropertyChanged(this.handleGeometryPropertyChanged); this._edgeMaterial.dispose(); this.resetState(); } @@ -255,7 +268,7 @@ export class ThreeShape extends Object3D implements IVisualShape { private cloneSubEdge(index: number, material: LineBasicMaterial) { let allPositions = this._edges!.geometry.getAttribute("position") as Float32BufferAttribute; - let group = this.shape.mesh.edges!.groups[index]; + let group = this.geometry.shape.value!.mesh.edges!.groups[index]; let positions = allPositions.array.slice(group.start * 3, (group.start + group.count) * 3); let buff = new BufferGeometry(); buff.setAttribute("position", new Float32BufferAttribute(positions, 3)); @@ -287,11 +300,11 @@ export class ThreeShape extends Object3D implements IVisualShape { } private cloneSubFace(index: number, material: MeshLambertMaterial) { - let group = this.shape.mesh.faces!.groups[index]; + let group = this.geometry.shape.value?.mesh.faces!.groups[index]; if (!group) return undefined; let allPositions = this._faces!.geometry.getAttribute("position") as Float32BufferAttribute; let allNormals = this._faces!.geometry.getAttribute("normal") as Float32BufferAttribute; - let allIndices = this.shape.mesh.faces!.indices; + let allIndices = this.geometry.shape.value!.mesh.faces!.indices; let indices = allIndices.slice(group.start, group.start + group.count); let indiceStart = Math.min(...indices); let indiceEnd = Math.max(...indices) + 1; diff --git a/packages/chili-three/src/threeHighlighter.ts b/packages/chili-three/src/threeHighlighter.ts index 2927ac79..0d33a65a 100644 --- a/packages/chili-three/src/threeHighlighter.ts +++ b/packages/chili-three/src/threeHighlighter.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IHighlighter, IVisualShape, ShapeType, VisualState } from "chili-core"; +import { IHighlighter, IVisualGeometry, ShapeType, VisualState } from "chili-core"; export class ThreeHighlighter implements IHighlighter { - private readonly _stateMap = new Map>(); + private readonly _stateMap = new Map>(); clear(): void { this._stateMap.forEach((v, k) => { @@ -12,14 +12,14 @@ export class ThreeHighlighter implements IHighlighter { this._stateMap.clear(); } - removeAllStates(shape: IVisualShape, resetState: boolean): void { + removeAllStates(shape: IVisualGeometry, resetState: boolean): void { if (!this._stateMap.has(shape)) return; this._stateMap.delete(shape); if (resetState) shape.resetState(); } updateStateData( - shape: IVisualShape, + shape: IVisualGeometry, mode: "add" | "remove", state: VisualState, type: ShapeType, diff --git a/packages/chili-three/src/threeMeshObject.ts b/packages/chili-three/src/threeMeshObject.ts index 84971785..2ccc889a 100644 --- a/packages/chili-three/src/threeMeshObject.ts +++ b/packages/chili-three/src/threeMeshObject.ts @@ -1,9 +1,8 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IVisualObject } from "chili-core"; +import { IVisualObject, Matrix4 } from "chili-core"; import { Mesh, MeshBasicMaterial } from "three"; import { ThreeHelper } from "./threeHelper"; -import { Matrix4 } from "chili-core"; export class ThreeMeshObject extends Mesh implements IVisualObject { get transform() { diff --git a/packages/chili-three/src/threeView.ts b/packages/chili-three/src/threeView.ts index d41fc24a..4da9ad2a 100644 --- a/packages/chili-three/src/threeView.ts +++ b/packages/chili-three/src/threeView.ts @@ -34,8 +34,8 @@ import { import { SelectionBox } from "three/examples/jsm/interactive/SelectionBox"; import { CameraController } from "./cameraController"; import { Constants } from "./constants"; +import { ThreeGeometry } from "./threeGeometry"; import { ThreeHelper } from "./threeHelper"; -import { ThreeShape } from "./threeShape"; import { ThreeVisualContext } from "./threeVisualContext"; import { ViewGizmo } from "./viewGizmo"; @@ -272,11 +272,11 @@ export class ThreeView extends Observable implements IView { shape: Mesh | LineSegments, shapeFilter?: IShapeFilter, ) { - if (!(shape.parent instanceof ThreeShape) || !shape.parent.visible) return; + if (!(shape.parent instanceof ThreeGeometry) || !shape.parent.visible) return; if (shapeType === ShapeType.Shape && shape instanceof LineSegments) { - if (shapeFilter && !shapeFilter.allow(shape.parent.shape)) return; + if (shapeFilter && !shapeFilter.allow(shape.parent.geometry.shape.value!)) return; detecteds.push({ - shape: shape.parent.shape, + shape: shape.parent.geometry.shape.value!, owner: shape.parent, indexes: [], }); @@ -294,12 +294,15 @@ export class ThreeView extends Observable implements IView { let result: VisualShapeData[] = []; for (const element of intersections) { const parent = element.object.parent; - if (!(parent instanceof ThreeShape) || (shapeFilter && !shapeFilter.allow(parent.shape))) { + if ( + !(parent instanceof ThreeGeometry) || + (shapeFilter && !shapeFilter.allow(parent.geometry.shape.value!)) + ) { continue; } result.push({ owner: parent, - shape: parent.shape, + shape: parent.geometry.shape.value!, indexes: [], }); } @@ -314,7 +317,7 @@ export class ThreeView extends Observable implements IView { let result: VisualShapeData[] = []; for (const intersected of intersections) { const visualShape = intersected.object.parent; - if (!(visualShape instanceof ThreeShape)) continue; + if (!(visualShape instanceof ThreeGeometry)) continue; let { shape, indexes } = this.getShape(shapeType, visualShape, intersected); if (!shape || (shapeFilter && !shapeFilter.allow(shape))) { continue; @@ -330,7 +333,7 @@ export class ThreeView extends Observable implements IView { private getShape( shapeType: ShapeType, - parent: ThreeShape, + parent: ThreeGeometry, element: Intersection, ): { shape: IShape | undefined; @@ -347,8 +350,8 @@ export class ThreeView extends Observable implements IView { return { shape, indexes: [index!] }; } - private getWireAndIndexes(shape: IShape, groups: ShapeMeshGroup[], parent: ThreeShape) { - let wire = shape.findAncestor(ShapeType.Wire, parent.shape).at(0); + private getWireAndIndexes(shape: IShape, groups: ShapeMeshGroup[], parent: ThreeGeometry) { + let wire = shape.findAncestor(ShapeType.Wire, parent.geometry.shape.value!).at(0); let indexes: number[] = []; if (wire) { let edges = wire.findSubShapes(ShapeType.Edge, true); @@ -363,18 +366,18 @@ export class ThreeView extends Observable implements IView { return { shape: wire, indexes }; } - private findShapeAndIndex(parent: ThreeShape, element: Intersection) { + private findShapeAndIndex(parent: ThreeGeometry, element: Intersection) { let shape: IShape | undefined = undefined; let index: number | undefined = undefined; let groups: ShapeMeshGroup[] | undefined = undefined; if (element.faceIndex !== null) { - groups = parent.shape.mesh.faces?.groups; + groups = parent.geometry.shape.value?.mesh.faces?.groups; if (groups) { index = ThreeHelper.findGroupIndex(groups, element.faceIndex! * 3)!; shape = groups[index].shape; } } else if (element.index !== null) { - groups = parent.shape.mesh.edges?.groups; + groups = parent.geometry.shape.value?.mesh.edges?.groups; if (groups) { index = ThreeHelper.findGroupIndex(groups, element.index!)!; shape = groups[index].shape; @@ -395,7 +398,7 @@ export class ThreeView extends Observable implements IView { if (obj !== undefined) shapes.push(obj); }; this.document.visual.context.shapes().forEach((x) => { - if (!(x instanceof ThreeShape) || !x.visible) return; + if (!(x instanceof ThreeGeometry) || !x.visible) return; if ( shapeType === ShapeType.Shape || ShapeType.hasCompound(shapeType) || diff --git a/packages/chili-three/src/threeVisualContext.ts b/packages/chili-three/src/threeVisualContext.ts index 0b398c5b..0cb1b8e6 100644 --- a/packages/chili-three/src/threeVisualContext.ts +++ b/packages/chili-three/src/threeVisualContext.ts @@ -9,8 +9,8 @@ import { INode, IVisual, IVisualContext, + IVisualGeometry, IVisualObject, - IVisualShape, LineType, Material, MathUtils, @@ -34,12 +34,12 @@ import { TextureLoader, MeshLambertMaterial as ThreeMaterial, } from "three"; -import { ThreeShape } from "./threeShape"; +import { ThreeGeometry } from "./threeGeometry"; import { ThreeVisualObject } from "./threeVisualObject"; export class ThreeVisualContext implements IVisualContext { - private readonly _shapeModelMap = new WeakMap(); - private readonly _modelShapeMap = new WeakMap(); + private readonly _shapeModelMap = new WeakMap(); + private readonly _modelShapeMap = new WeakMap(); private readonly materialMap = new Map(); readonly visualShapes: Group; @@ -82,6 +82,14 @@ export class ThreeVisualContext implements IVisualContext { } }; + getMaterial(id: string): ThreeMaterial { + let material = this.materialMap.get(id); + if (!material) { + throw new Error("Material not found"); + } + return material; + } + private onMaterialPropertyChanged = (prop: keyof Material, source: Material) => { let material = this.materialMap.get(source.id); if (!material) return; @@ -133,9 +141,6 @@ export class ThreeVisualContext implements IVisualContext { dispose() { this.visualShapes.traverse((x) => { - if (x instanceof ThreeShape) { - this._shapeModelMap.get(x)?.removePropertyChanged(this.handleModelPropertyChanged); - } if (IDisposable.isDisposable(x)) x.dispose(); }); this.visual.document.materials.forEach((x) => @@ -149,7 +154,7 @@ export class ThreeVisualContext implements IVisualContext { this.scene.remove(this.visualShapes, this.tempShapes); } - getModel(shape: IVisualShape): IModel | undefined { + getModel(shape: IVisualGeometry): IModel | undefined { return this._shapeModelMap.get(shape); } @@ -164,21 +169,21 @@ export class ThreeVisualContext implements IVisualContext { return this.visualShapes.children.length; } - getShape(model: IModel): IVisualShape | undefined { + getShape(model: IModel): IVisualGeometry | undefined { return this._modelShapeMap.get(model); } - shapes(): IVisualShape[] { - let shapes = new Array(); + shapes(): IVisualGeometry[] { + let shapes = new Array(); this.visualShapes.children.forEach((x) => this._getThreeShapes(shapes, x)); return shapes; } - private _getThreeShapes(shapes: Array, shape: Object3D) { + private _getThreeShapes(shapes: Array, shape: Object3D) { let group = shape as Group; if (group.type === "Group") { group.children.forEach((x) => this._getThreeShapes(shapes, x)); - } else if (shape instanceof ThreeShape) shapes.push(shape); + } else if (shape instanceof ThreeGeometry) shapes.push(shape); } displayShapeMesh(...datas: ShapeMeshData[]): number { @@ -238,44 +243,26 @@ export class ThreeVisualContext implements IVisualContext { this.visualShapes.add(childGroup); return; } - model.onPropertyChanged(this.handleModelPropertyChanged); this.displayModel(model); }); } private displayModel(model: IModel) { - let modelShape = model.shape(); + let modelShape = model.geometry.shape.value; if (modelShape === undefined) return; - let material = this.materialMap.get(model.materialId); - if (!material) { - throw new Error("Material not found"); - } - let threeShape = new ThreeShape(modelShape, this.visual.highlighter, material); - threeShape.transform = model.matrix; + let threeShape = new ThreeGeometry(model.geometry, this.visual.highlighter, this); this.visualShapes.add(threeShape); this._shapeModelMap.set(threeShape, model); this._modelShapeMap.set(model, threeShape); } - private handleModelPropertyChanged = (property: keyof IModel, model: IModel) => { - let shape = this._modelShapeMap.get(model) as ThreeShape; - if (shape === undefined) return; - if (property === "matrix") { - shape.transform = model.matrix; - } else if (property === "materialId") { - let material = this.materialMap.get(model.materialId)!; - shape.setFaceMaterial(material); - } - }; - removeModel(models: IModel[]) { models.forEach((m) => { - m.removePropertyChanged(this.handleModelPropertyChanged); let shape = this._modelShapeMap.get(m); this._modelShapeMap.delete(m); if (!shape) return; this._shapeModelMap.delete(shape); - if (shape instanceof ThreeShape) { + if (shape instanceof ThreeGeometry) { this.visualShapes.remove(shape); shape.dispose(); } @@ -288,7 +275,7 @@ export class ThreeVisualContext implements IVisualContext { } const shapes: Object3D[] = []; this.visualShapes.traverse((child) => { - if (!(child instanceof ThreeShape)) return; + if (!(child instanceof ThreeGeometry)) return; if (shapeType === ShapeType.Edge) { let wireframe = child.edges(); if (wireframe) shapes.push(wireframe); diff --git a/packages/chili-three/test/testEdge.ts b/packages/chili-three/test/testEdge.ts index e1beb9a5..cae1dc14 100644 --- a/packages/chili-three/test/testEdge.ts +++ b/packages/chili-three/test/testEdge.ts @@ -1,5 +1,5 @@ import { - Body, + GeometryObject, I18nKeys, ICurve, IDocument, @@ -80,7 +80,7 @@ export class TestEdge implements IEdge { } } -export class TestBody extends Body { +export class TestBody extends GeometryObject { display: I18nKeys = "body.line"; constructor( document: IDocument, diff --git a/packages/chili-three/test/three.test.ts b/packages/chili-three/test/three.test.ts index 78b8d5c8..f81d5e45 100644 --- a/packages/chili-three/test/three.test.ts +++ b/packages/chili-three/test/three.test.ts @@ -36,7 +36,7 @@ describe("three test", () => { expect(shapes[0].shape.shapeType).toBe(ShapeType.Edge); let shape = context.getShape(model); - expect(shapes.at(0)?.shape).toEqual(shape?.shape); + expect(shapes.at(0)?.shape).toEqual(shape?.geometry.shape.value); expect(context.getModel(shape!)).toEqual(model); context.removeModel([model]); diff --git a/packages/chili-ui/src/property/materialProperty.module.css b/packages/chili-ui/src/property/materialProperty.module.css new file mode 100644 index 00000000..6aa4a0d9 --- /dev/null +++ b/packages/chili-ui/src/property/materialProperty.module.css @@ -0,0 +1,30 @@ +.material { + display: flex; + flex-direction: row; + align-items: center; + margin-top: 4px; + + span { + font-size: 14px; + opacity: 0.75; + color: var(--color); + min-width: 72px; + flex: 0 1 auto; + } + + button { + flex: 1 1 auto; + font-size: 1rem; + padding: 4px; + outline: none; + background-color: transparent; + + border: 1px solid var(--border-color); + border-radius: 4px; + margin: 0px; + + &:hover { + background-color: var(--hover-background-color); + } + } +} diff --git a/packages/chili-ui/src/property/materialProperty.ts b/packages/chili-ui/src/property/materialProperty.ts new file mode 100644 index 00000000..516fea29 --- /dev/null +++ b/packages/chili-ui/src/property/materialProperty.ts @@ -0,0 +1,56 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { GeometryObject, IDocument, Material, Property, PubSub, Transaction } from "chili-core"; +import { button, div, localize, span } from "../controls"; +import style from "./materialProperty.module.css"; +import { PropertyBase } from "./propertyBase"; + +export class MaterialProperty extends PropertyBase { + constructor( + readonly document: IDocument, + objects: GeometryObject[], + readonly property: Property, + ) { + super(objects); + this.appendChild( + div( + { + className: style.material, + }, + span({ + textContent: localize(property.display), + }), + button({ + textContent: this.findMaterial(objects[0].materialId).name, + onclick: (e) => { + PubSub.default.pub( + "editMaterial", + document, + this.findMaterial(objects[0].materialId)!, + (material) => { + this.setMaterial(e, material); + }, + ); + }, + }), + ), + ); + } + + private setMaterial(e: MouseEvent, material: Material) { + let button = e.target as HTMLButtonElement; + button.textContent = material.name; + Transaction.excute(this.document, "change material", () => { + this.objects.forEach((x) => { + x[this.property.name] = material.id; + }); + }); + this.document.visual.update(); + } + + private findMaterial(id: string) { + return this.document.materials.find((x) => x.id === id)!; + } +} + +customElements.define("chili-material-property", MaterialProperty); diff --git a/packages/chili-ui/src/property/propertyView.module.css b/packages/chili-ui/src/property/propertyView.module.css index d45c61d9..87cb7af6 100644 --- a/packages/chili-ui/src/property/propertyView.module.css +++ b/packages/chili-ui/src/property/propertyView.module.css @@ -29,34 +29,3 @@ grid-gap: 10px; align-items: center; } - -.material { - display: flex; - flex-direction: row; - align-items: center; - margin-top: 4px; - - span { - font-size: 14px; - opacity: 0.75; - color: var(--color); - min-width: 72px; - flex: 0 1 auto; - } - - button { - flex: 1 1 auto; - font-size: 1rem; - padding: 4px; - outline: none; - background-color: transparent; - - border: 1px solid var(--border-color); - border-radius: 4px; - margin: 0px; - - &:hover { - background-color: var(--hover-background-color); - } - } -} diff --git a/packages/chili-ui/src/property/propertyView.ts b/packages/chili-ui/src/property/propertyView.ts index 2c383f79..9c00f1a2 100644 --- a/packages/chili-ui/src/property/propertyView.ts +++ b/packages/chili-ui/src/property/propertyView.ts @@ -2,18 +2,17 @@ import { GeometryModel, + GeometryObject, I18nKeys, IConverter, IDocument, - IModel, INode, IView, Property, PubSub, } from "chili-core"; import { Expander } from "../components"; - -import { button, div, label, localize, span } from "../controls"; +import { div, label, localize } from "../controls"; import { InputProperty } from "./input"; import { MatrixConverter } from "./matrixConverter"; import style from "./propertyView.module.css"; @@ -46,9 +45,8 @@ export class PropertyView extends HTMLElement { private handleShowProperties = (document: IDocument, nodes: INode[]) => { this.removeProperties(); if (nodes.length === 0) return; - this.addDefault(document, nodes); - this.addTransform(document, nodes); - this.addBody(nodes, document); + this.addModel(document, nodes); + this.addGeometry(nodes, document); }; private removeProperties() { @@ -57,61 +55,43 @@ export class PropertyView extends HTMLElement { } } - private addDefault(document: IDocument, nodes: INode[]) { + private addModel(document: IDocument, nodes: INode[]) { if (nodes.length === 0) return; let properties = div(); let header = new InputProperty(document, nodes, Property.getProperty(nodes[0], "name")!); if (INode.isModelNode(nodes[0])) { appendProperty(properties, document, nodes); - if (nodes.length === 1) { - this.appendMaterialProperty(document, nodes[0], properties); - } } this.panel.append(header, properties); } - private appendMaterialProperty(document: IDocument, model: IModel, properties: HTMLDivElement) { - const findMaterial = (id: string) => document.materials.find((x) => x.id === id)!; - properties.append( - div( - { - className: style.material, - }, - span({ - textContent: localize(Property.getProperty(model, "materialId")!.display), - }), - button({ - textContent: findMaterial(model.materialId).name, - onclick: (e) => { - PubSub.default.pub( - "editMaterial", - document, - findMaterial(model.materialId)!, - (material) => { - let button = e.target as HTMLButtonElement; - button.textContent = material.name; - model.materialId = material.id; - console.log(model.materialId); - - document.visual.update(); - }, - ); - }, - }), - ), - ); + private addGeometry(nodes: INode[], document: IDocument) { + let geometries = nodes.filter((x) => INode.isModelNode(x)).map((x) => (x as GeometryModel).geometry); + if (geometries.length === 0 || !this.isAllElementsOfTypeFirstElement(geometries)) return; + this.addCommon(document, geometries); + this.addParameters(geometries, document); } - private addBody(nodes: INode[], document: IDocument) { - let bodies = nodes.filter((x) => INode.isModelNode(x)).map((x) => (x as GeometryModel).body); - if (bodies.length === 0 || !this.isAllElementsOfTypeFirstElement(bodies)) return; - let body = new Expander(bodies[0].display); - this.panel.append(body); - body.classList.add(style.expander); - Property.getProperties(bodies[0]).forEach((x) => { - appendProperty(body.contenxtPanel, document, bodies, x); + private addCommon(document: IDocument, geometries: GeometryObject[]) { + let common = new Expander("common.general"); + this.panel.append(common); + common.classList.add(style.expander); + Property.getOwnProperties(GeometryObject.prototype).forEach((x) => { + appendProperty(common.contenxtPanel, document, geometries, x); }); + this.addTransform(common, document, geometries); + } + + private addParameters(geometries: GeometryObject[], document: IDocument) { + let parameters = new Expander(geometries[0].display); + this.panel.append(parameters); + parameters.classList.add(style.expander); + Property.getProperties(Object.getPrototypeOf(geometries[0]), GeometryObject.prototype).forEach( + (x) => { + appendProperty(parameters.contenxtPanel, document, geometries, x); + }, + ); } private isAllElementsOfTypeFirstElement(arr: any[]): boolean { @@ -127,13 +107,9 @@ export class PropertyView extends HTMLElement { return true; } - private addTransform(document: IDocument, nodes: INode[]) { - nodes = nodes.filter((x) => INode.isModelNode(x)); - if (nodes.length === 0) return; - let transform = new Expander("properties.group.transform").addClass(style.expander); - this.panel.append(transform); + private addTransform(dom: HTMLElement, document: IDocument, geometries: GeometryObject[]) { const addMatrix = (display: I18nKeys, converter: IConverter) => { - appendProperty(transform, document, nodes, { + appendProperty(dom, document, geometries, { name: "matrix", display: display, converter, @@ -141,9 +117,9 @@ export class PropertyView extends HTMLElement { }; // 这部分代码有问题,待完善 let converters = MatrixConverter.init(); - addMatrix("model.translation", converters.translation); - addMatrix("model.scale", converters.scale); - addMatrix("model.rotation", converters.rotate); + addMatrix("transform.translation", converters.translation); + addMatrix("transform.scale", converters.scale); + addMatrix("transform.rotation", converters.rotate); } } diff --git a/packages/chili-ui/src/property/utils.ts b/packages/chili-ui/src/property/utils.ts index e10f126c..fff9cc7b 100644 --- a/packages/chili-ui/src/property/utils.ts +++ b/packages/chili-ui/src/property/utils.ts @@ -4,6 +4,7 @@ import { IDocument, Property } from "chili-core"; import { InputProperty } from "./input"; import { CheckProperty } from "./check"; import { ColorProperty } from "./colorProperty"; +import { MaterialProperty } from "./materialProperty"; export function appendProperty(container: HTMLElement, document: IDocument, objs: any[], prop?: Property) { if (prop === undefined) return; @@ -12,6 +13,8 @@ export function appendProperty(container: HTMLElement, document: IDocument, objs if (prop.type === "color") { container.append(new ColorProperty(document, objs, prop)); + } else if (prop.type === "materialId") { + container.append(new MaterialProperty(document, objs, prop)); } else if (type === "object" || type === "string" || type === "number") { container.append(new InputProperty(document, objs, prop)); } else if (type === "boolean") { diff --git a/packages/chili/src/bodys/arcBody.ts b/packages/chili/src/bodys/arcBody.ts index 0b98e363..3afb4ddd 100644 --- a/packages/chili/src/bodys/arcBody.ts +++ b/packages/chili/src/bodys/arcBody.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; @Serializer.register("ArcBody", ["document", "normal", "center", "start", "angle"]) -export class ArcBody extends Body { +export class ArcBody extends GeometryObject { readonly display: I18nKeys = "body.arc"; private _center: XYZ; diff --git a/packages/chili/src/bodys/boolean.ts b/packages/chili/src/bodys/boolean.ts index b1edbf16..3afa8f22 100644 --- a/packages/chili/src/bodys/boolean.ts +++ b/packages/chili/src/bodys/boolean.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; @Serializer.register("BooleanBody", ["document", "booleanShape"]) -export class BooleanBody extends Body { +export class BooleanBody extends GeometryObject { override display: I18nKeys = "body.bolean"; private _booleanShape: IShape; diff --git a/packages/chili/src/bodys/box.ts b/packages/chili/src/bodys/box.ts index 4fabd7b6..8aac1cbc 100644 --- a/packages/chili/src/bodys/box.ts +++ b/packages/chili/src/bodys/box.ts @@ -1,9 +1,18 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Plane, Property, Result, Serializer } from "chili-core"; +import { + GeometryObject, + I18nKeys, + IDocument, + IShape, + Plane, + Property, + Result, + Serializer, +} from "chili-core"; @Serializer.register("BoxBody", ["document", "plane", "dx", "dy", "dz"]) -export class BoxBody extends Body { +export class BoxBody extends GeometryObject { readonly display: I18nKeys = "body.box"; private _dx: number; diff --git a/packages/chili/src/bodys/circle.ts b/packages/chili/src/bodys/circle.ts index a52f46a1..610aabd6 100644 --- a/packages/chili/src/bodys/circle.ts +++ b/packages/chili/src/bodys/circle.ts @@ -1,9 +1,18 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { FaceableBody, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; +import { + FaceableGeometry, + I18nKeys, + IDocument, + IShape, + Property, + Result, + Serializer, + XYZ, +} from "chili-core"; @Serializer.register("CircleBody", ["document", "normal", "center", "radius"]) -export class CircleBody extends FaceableBody { +export class CircleBody extends FaceableGeometry { readonly display: I18nKeys = "body.circle"; private _center: XYZ; diff --git a/packages/chili/src/bodys/face.ts b/packages/chili/src/bodys/face.ts index ad53b2e8..3e253af4 100644 --- a/packages/chili/src/bodys/face.ts +++ b/packages/chili/src/bodys/face.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IEdge, IShape, IWire, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IEdge, IShape, IWire, Result, Serializer } from "chili-core"; @Serializer.register("FaceBody", ["document", "shapes"]) -export class FaceBody extends Body { +export class FaceBody extends GeometryObject { override display: I18nKeys = "body.face"; private _shapes: IEdge[] | IWire; diff --git a/packages/chili/src/bodys/fuse.ts b/packages/chili/src/bodys/fuse.ts index 5a98baa3..2a530354 100644 --- a/packages/chili/src/bodys/fuse.ts +++ b/packages/chili/src/bodys/fuse.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; @Serializer.register("FuseBody", ["document", "bottom", "top"]) -export class FuseBody extends Body { +export class FuseBody extends GeometryObject { override display: I18nKeys = "body.fuse"; private _bottom: IShape; diff --git a/packages/chili/src/bodys/importer.ts b/packages/chili/src/bodys/importer.ts index f17a9e1d..5699aab1 100644 --- a/packages/chili/src/bodys/importer.ts +++ b/packages/chili/src/bodys/importer.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Result, Serializer } from "chili-core"; @Serializer.register("ImportedBody", ["document", "importedShape"]) -export class ImportedBody extends Body { +export class ImportedBody extends GeometryObject { override display: I18nKeys = "body.imported"; private _importedShape: IShape; diff --git a/packages/chili/src/bodys/line.ts b/packages/chili/src/bodys/line.ts index 7bd0cce6..48fd0e5a 100644 --- a/packages/chili/src/bodys/line.ts +++ b/packages/chili/src/bodys/line.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; @Serializer.register("LineBody", ["document", "start", "end"]) -export class LineBody extends Body { +export class LineBody extends GeometryObject { readonly display: I18nKeys = "body.line"; private _start: XYZ; diff --git a/packages/chili/src/bodys/polygon.ts b/packages/chili/src/bodys/polygon.ts index fe989df6..e581553d 100644 --- a/packages/chili/src/bodys/polygon.ts +++ b/packages/chili/src/bodys/polygon.ts @@ -1,9 +1,18 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { FaceableBody, I18nKeys, IDocument, IShape, Property, Result, Serializer, XYZ } from "chili-core"; +import { + FaceableGeometry, + I18nKeys, + IDocument, + IShape, + Property, + Result, + Serializer, + XYZ, +} from "chili-core"; @Serializer.register("PolygonBody", ["document", "points"]) -export class PolygonBody extends FaceableBody { +export class PolygonBody extends FaceableGeometry { readonly display: I18nKeys = "body.polygon"; private _points: XYZ[]; diff --git a/packages/chili/src/bodys/prism.ts b/packages/chili/src/bodys/prism.ts index aea656a5..92d85518 100644 --- a/packages/chili/src/bodys/prism.ts +++ b/packages/chili/src/bodys/prism.ts @@ -1,9 +1,18 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IFace, IShape, Property, Result, Serializer } from "chili-core"; +import { + GeometryObject, + I18nKeys, + IDocument, + IFace, + IShape, + Property, + Result, + Serializer, +} from "chili-core"; @Serializer.register("PrismBody", ["document", "face", "length"]) -export class PrismBody extends Body { +export class PrismBody extends GeometryObject { override display: I18nKeys = "body.prism"; private _face: IFace; diff --git a/packages/chili/src/bodys/rect.ts b/packages/chili/src/bodys/rect.ts index 95921fa8..7d1559f8 100644 --- a/packages/chili/src/bodys/rect.ts +++ b/packages/chili/src/bodys/rect.ts @@ -1,7 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { - FaceableBody, + FaceableGeometry, I18nKeys, IDocument, IShape, @@ -13,7 +13,7 @@ import { } from "chili-core"; @Serializer.register("RectBody", ["document", "plane", "dx", "dy"]) -export class RectBody extends FaceableBody { +export class RectBody extends FaceableGeometry { readonly display: I18nKeys = "body.rect"; private _dx: number; diff --git a/packages/chili/src/bodys/revolve.ts b/packages/chili/src/bodys/revolve.ts index 313a34a1..36c8468f 100644 --- a/packages/chili/src/bodys/revolve.ts +++ b/packages/chili/src/bodys/revolve.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IShape, Ray, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IShape, Ray, Result, Serializer } from "chili-core"; @Serializer.register("RevolveBody", ["document", "profile", "axis", "angle"]) -export class RevolveBody extends Body { +export class RevolveBody extends GeometryObject { override display: I18nKeys = "body.revol"; private _profile: IShape; diff --git a/packages/chili/src/bodys/sweep.ts b/packages/chili/src/bodys/sweep.ts index f49a8422..f12bdbb9 100644 --- a/packages/chili/src/bodys/sweep.ts +++ b/packages/chili/src/bodys/sweep.ts @@ -1,9 +1,19 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IEdge, IShape, IWire, Result, Serializer, ShapeType } from "chili-core"; +import { + GeometryObject, + I18nKeys, + IDocument, + IEdge, + IShape, + IWire, + Result, + Serializer, + ShapeType, +} from "chili-core"; @Serializer.register("SweepBody", ["document", "profile", "path"]) -export class SweepBody extends Body { +export class SweepBody extends GeometryObject { override display: I18nKeys = "body.sweep"; private _profile: IShape; diff --git a/packages/chili/src/bodys/wire.ts b/packages/chili/src/bodys/wire.ts index 789b2066..5ac3acfc 100644 --- a/packages/chili/src/bodys/wire.ts +++ b/packages/chili/src/bodys/wire.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { Body, I18nKeys, IDocument, IEdge, IShape, Result, Serializer } from "chili-core"; +import { GeometryObject, I18nKeys, IDocument, IEdge, IShape, Result, Serializer } from "chili-core"; @Serializer.register("WireBody", ["document", "edges"]) -export class WireBody extends Body { +export class WireBody extends GeometryObject { override display: I18nKeys = "body.wire"; private _edges: IEdge[]; diff --git a/packages/chili/src/commands/boolean.ts b/packages/chili/src/commands/boolean.ts index 99d80ae8..2b75b39e 100644 --- a/packages/chili/src/commands/boolean.ts +++ b/packages/chili/src/commands/boolean.ts @@ -9,8 +9,8 @@ let count = 1; export abstract class BooleanOperate extends CreateCommand { protected override create(): GeometryModel { - let shape1 = (this.stepDatas[0].models?.at(0) as GeometryModel)?.shape()!; - let shape2 = (this.stepDatas[1].models?.at(0) as GeometryModel)?.shape()!; + let shape1 = (this.stepDatas[0].models?.at(0) as GeometryModel)?.geometry.shape.value!; + let shape2 = (this.stepDatas[1].models?.at(0) as GeometryModel)?.geometry.shape.value!; let booleanType = this.getBooleanOperateType(); let booleanShape: Result; if (booleanType === "common") { @@ -35,7 +35,7 @@ export abstract class BooleanOperate extends CreateCommand { new SelectModelStep("prompt.select.shape", false), new SelectModelStep("prompt.select.shape", false, { allow: (shape) => { - return !this.stepDatas[0].models?.map((x) => x.shape()).includes(shape); + return !this.stepDatas[0].models?.map((x) => x.geometry.shape.value).includes(shape); }, }), ]; diff --git a/packages/chili/src/commands/create/converter.ts b/packages/chili/src/commands/create/converter.ts index 14e1e10b..989bb1a8 100644 --- a/packages/chili/src/commands/create/converter.ts +++ b/packages/chili/src/commands/create/converter.ts @@ -63,7 +63,7 @@ abstract class ConvertCommand extends CancelableCommand { .map((x) => x as GeometryModel) .filter((x) => { if (x === undefined) return false; - let shape = x.shape(); + let shape = x.geometry.shape.value; if (shape === undefined) return false; if (filter !== undefined && !filter.allow(shape)) return false; return true; @@ -78,7 +78,7 @@ abstract class ConvertCommand extends CancelableCommand { }) export class ConvertToWire extends ConvertCommand { protected override create(document: IDocument, models: IModel[]): Result { - let edges = models.map((x) => x.shape()) as IEdge[]; + let edges = models.map((x) => x.geometry.shape.value) as IEdge[]; let wireBody = new WireBody(document, edges); if (!wireBody.shape.isOk) { return Result.err(wireBody.shape.error); @@ -95,7 +95,7 @@ export class ConvertToWire extends ConvertCommand { }) export class ConvertToFace extends ConvertCommand { protected override create(document: IDocument, models: IModel[]): Result { - let edges = models.map((x) => x.shape()) as IEdge[]; + let edges = models.map((x) => x.geometry.shape.value) as IEdge[]; let wireBody = new FaceBody(document, edges); if (!wireBody.shape.isOk) { return Result.err(wireBody.shape.error); diff --git a/packages/chili/src/commands/importExport.ts b/packages/chili/src/commands/importExport.ts index 89371748..9dc17b60 100644 --- a/packages/chili/src/commands/importExport.ts +++ b/packages/chili/src/commands/importExport.ts @@ -91,7 +91,7 @@ abstract class Export implements ICommand { PubSub.default.pub( "showPermanent", async () => { - let shapes = models!.map((x) => x.shape()!); + let shapes = models!.map((x) => x.geometry.shape.value!); let shapeString = await this.convertAsync(application, type, ...shapes); if (!shapeString.isOk) { PubSub.default.pub("showToast", "toast.converter.error"); diff --git a/packages/chili/src/commands/modify/array.ts b/packages/chili/src/commands/modify/array.ts index 0cdcb45d..1bcfcfea 100644 --- a/packages/chili/src/commands/modify/array.ts +++ b/packages/chili/src/commands/modify/array.ts @@ -71,8 +71,8 @@ export class Array extends MultistepCommand { } this.positions = []; this.models?.forEach((model) => { - let ps = model.shape()?.mesh.edges?.positions; - if (ps) this.positions = this.positions!.concat(model.matrix.ofPoints(ps)); + let ps = model.geometry.shape.value?.mesh.edges?.positions; + if (ps) this.positions = this.positions!.concat(model.geometry.matrix.ofPoints(ps)); }); return true; } @@ -82,7 +82,7 @@ export class Array extends MultistepCommand { let vec = this.stepDatas[1].point!.sub(this.stepDatas[0].point!); let transform = Matrix4.createTranslation(vec.x, vec.y, vec.z); this.models?.forEach((x) => { - x.matrix = x.matrix.multiply(transform); + x.geometry.matrix = x.geometry.matrix.multiply(transform); }); this.document.visual.update(); }); diff --git a/packages/chili/src/commands/modify/transformedCommand.ts b/packages/chili/src/commands/modify/transformedCommand.ts index 05586251..20b8f604 100644 --- a/packages/chili/src/commands/modify/transformedCommand.ts +++ b/packages/chili/src/commands/modify/transformedCommand.ts @@ -65,8 +65,8 @@ export abstract class TransformedCommand extends MultistepCommand { } this.positions = []; this.models.forEach((model) => { - let ps = model.shape()?.mesh.edges?.positions; - if (ps) this.positions = this.positions!.concat(model.matrix.ofPoints(ps)); + let ps = model.geometry.shape.value?.mesh.edges?.positions; + if (ps) this.positions = this.positions!.concat(model.geometry.matrix.ofPoints(ps)); }); return true; } @@ -79,7 +79,7 @@ export abstract class TransformedCommand extends MultistepCommand { } let transform = this.transfrom(this.stepDatas.at(-1)!.point!); models?.forEach((x) => { - x.matrix = x.matrix.multiply(transform); + x.geometry.matrix = x.geometry.matrix.multiply(transform); }); this.document.visual.update(); }); diff --git a/packages/chili/src/document.ts b/packages/chili/src/document.ts index ac718c95..fbe9ffed 100644 --- a/packages/chili/src/document.ts +++ b/packages/chili/src/document.ts @@ -30,7 +30,7 @@ import { import { Material } from "chili-core/src/material"; import { Selection } from "./selection"; -const FILE_VERSIOM = "0.1.1"; +const FILE_VERSIOM = "0.1.2"; export class Document extends Observable implements IDocument { readonly visual: IVisual; diff --git a/packages/chili/src/selection.ts b/packages/chili/src/selection.ts index e5df6fe0..47e3f447 100644 --- a/packages/chili/src/selection.ts +++ b/packages/chili/src/selection.ts @@ -102,7 +102,7 @@ export class Selection implements ISelection, IDisposable { private nodeFilter = (x: INode) => { if (INode.isModelNode(x)) { - let shape = x.shape(); + let shape = x.geometry.shape.value; if (!shape || !this.filter) return true; return this.filter.allow(shape); }