diff --git a/package-lock.json b/package-lock.json index 02012a5e..9ba34a1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "chili3d", - "version": "0.3.0", + "version": "0.4-beta", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "chili3d", - "version": "0.3.0", + "version": "0.4-beta", "workspaces": [ "packages/*" ], @@ -9497,7 +9497,7 @@ } }, "packages/chili": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*", "chili-geo": "*", @@ -9505,7 +9505,7 @@ } }, "packages/chili-builder": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili": "*", "chili-three": "*", @@ -9514,11 +9514,11 @@ } }, "packages/chili-core": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": {} }, "packages/chili-geo": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*" } @@ -9537,13 +9537,13 @@ "devDependencies": {} }, "packages/chili-storage": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*" } }, "packages/chili-three": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "@types/three": "0.168.0", "chili-core": "*", @@ -9553,7 +9553,7 @@ } }, "packages/chili-ui": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*", "chili-geo": "*", @@ -9561,19 +9561,19 @@ } }, "packages/chili-vis": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*" } }, "packages/chili-wasm": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-core": "*" } }, "packages/chili-web": { - "version": "0.3.0", + "version": "0.4-beta", "devDependencies": { "chili-builder": "*" } diff --git a/packages/chili-core/src/math/boundingBox.ts b/packages/chili-core/src/math/boundingBox.ts index e3e91f92..559aed07 100644 --- a/packages/chili-core/src/math/boundingBox.ts +++ b/packages/chili-core/src/math/boundingBox.ts @@ -11,6 +11,10 @@ export class BoundingBox { this.max = max; } + static isValid(box: BoundingBox) { + return box.min.x <= box.max.x && box.min.y <= box.max.y && box.min.z <= box.max.z; + } + static expandByPoint(box: BoundingBox, point: PointLike) { box.min.x = Math.min(box.min.x, point.x); box.min.y = Math.min(box.min.y, point.y); diff --git a/packages/chili-core/src/math/index.ts b/packages/chili-core/src/math/index.ts index b7e4a677..a3c9f8ac 100644 --- a/packages/chili-core/src/math/index.ts +++ b/packages/chili-core/src/math/index.ts @@ -1,10 +1,11 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. +export * from "./boundingBox"; export * from "./mathUtils"; export * from "./matrix4"; export * from "./plane"; +export * from "./planeAngle"; export * from "./quaternion"; export * from "./ray"; export * from "./xy"; export * from "./xyz"; -export * from "./planeAngle"; diff --git a/packages/chili-core/src/math/matrix4.ts b/packages/chili-core/src/math/matrix4.ts index e809649a..06d6e4fc 100644 --- a/packages/chili-core/src/math/matrix4.ts +++ b/packages/chili-core/src/math/matrix4.ts @@ -387,7 +387,7 @@ export class Matrix4 { return result; } - ofPoints(points: number[]): number[] { + ofPoints(points: ArrayLike): number[] { let result: number[] = []; for (let i = 0; i < points.length / 3; i++) { let x = diff --git a/packages/chili-core/src/model/node.ts b/packages/chili-core/src/model/node.ts index a72fb603..2a0b9e9b 100644 --- a/packages/chili-core/src/model/node.ts +++ b/packages/chili-core/src/model/node.ts @@ -9,14 +9,12 @@ import { Id, PubSub, Result, - debounce, } from "../foundation"; import { I18n, I18nKeys } from "../i18n"; -import { Matrix4 } from "../math"; -import { BoundingBox } from "../math/boundingBox"; +import { BoundingBox, Matrix4 } from "../math"; import { Property } from "../property"; import { Serialized, Serializer } from "../serialize"; -import { IShape, IShapeMeshData } from "../shape"; +import { IShape, IShapeMeshData, Mesh } from "../shape"; export interface INode extends IPropertyChanged, IDisposable { readonly id: string; @@ -222,34 +220,17 @@ export namespace INode { } } -export interface IMeshObject extends IPropertyChanged { - materialId: string; - matrix: Matrix4; - boundingBox(): BoundingBox; - get mesh(): IShapeMeshData; -} - -export abstract class GeometryNode extends Node implements IMeshObject { +export abstract class VisualNode extends Node { @Serializer.serialze() - @Property.define("common.material", { type: "materialId" }) - get materialId(): string { - return this.getPrivateValue("materialId"); - } - set materialId(value: string) { - this.setProperty("materialId", value); + get transform(): Matrix4 { + return this.getPrivateValue("transform", Matrix4.identity()); } - - @Serializer.serialze() - get matrix(): Matrix4 { - return this.getPrivateValue("matrix", Matrix4.identity()); - } - set matrix(value: Matrix4) { + set transform(value: Matrix4) { this.setProperty( - "matrix", + "transform", value, (_p, oldMatrix) => { - this.onMatrixChanged(value, oldMatrix); - this._boundingBox = undefined; + this.onTransformChanged(value, oldMatrix); }, { equals: (left, right) => left.equals(right), @@ -257,29 +238,54 @@ export abstract class GeometryNode extends Node implements IMeshObject { ); } - constructor(document: IDocument, name: string, materialId?: string, id: string = Id.generate()) { - super(document, name, id); - this.setPrivateValue("materialId", materialId ?? document.materials.at(0)?.id ?? ""); + protected onVisibleChanged(): void { + this.document.visual.context.setVisible(this, this.visible && this.parentVisible); } - protected _boundingBox: BoundingBox | undefined; - boundingBox(): BoundingBox { - if (this._boundingBox === undefined) { - let points = this.mesh.faces?.positions ?? this.mesh.edges?.positions ?? []; - points = this.matrix.ofPoints(points); - this._boundingBox = BoundingBox.fromNumbers(points); - } - return this._boundingBox; + protected onParentVisibleChanged(): void { + this.document.visual.context.setVisible(this, this.visible && this.parentVisible); } - protected onMatrixChanged(newMatrix: Matrix4, oldMatrix: Matrix4): void {} + abstract boundingBox(): BoundingBox; - protected onVisibleChanged(): void { - this.document.visual.context.setVisible(this, this.visible && this.parentVisible); + protected onTransformChanged(newMatrix: Matrix4, oldMatrix: Matrix4): void {} +} + +@Serializer.register(["document", "mesh", "name", "id"]) +export class MeshNode extends VisualNode { + protected _mesh: Mesh; + @Serializer.serialze() + get mesh(): Mesh { + return this._mesh; + } + set mesh(value: Mesh) { + this.setProperty("mesh", value); } - protected onParentVisibleChanged(): void { - this.document.visual.context.setVisible(this, this.visible && this.parentVisible); + constructor(document: IDocument, mesh: Mesh, name: string, id: string = Id.generate()) { + super(document, name, id); + this._mesh = mesh; + } + + override boundingBox(): BoundingBox { + let points = this.transform.ofPoints(this.mesh.position); + return BoundingBox.fromNumbers(points); + } +} + +export abstract class GeometryNode extends VisualNode { + @Serializer.serialze() + @Property.define("common.material", { type: "materialId" }) + get materialId(): string { + return this.getPrivateValue("materialId"); + } + set materialId(value: string) { + this.setProperty("materialId", value); + } + + constructor(document: IDocument, name: string, materialId?: string, id: string = Id.generate()) { + super(document, name, id); + this.setPrivateValue("materialId", materialId ?? document.materials.at(0)?.id ?? ""); } protected _mesh: IShapeMeshData | undefined; @@ -290,6 +296,16 @@ export abstract class GeometryNode extends Node implements IMeshObject { return this._mesh; } + protected _boundingBox: BoundingBox | undefined; + override boundingBox(): BoundingBox { + if (this._boundingBox === undefined) { + let points = this.mesh.faces?.positions ?? this.mesh.edges?.positions ?? []; + points = this.transform.ofPoints(points); + return BoundingBox.fromNumbers(points); + } + return this._boundingBox; + } + protected abstract createMesh(): IShapeMeshData; } @@ -300,7 +316,7 @@ export abstract class ShapeNode extends GeometryNode { } protected setShape(shape: Result) { - if (this._shape.isOk && this._shape.isOk && this._shape.value.isEqual(shape.value)) { + if (this._shape.isOk && this._shape.value.isEqual(shape.value)) { return; } @@ -313,11 +329,11 @@ export abstract class ShapeNode extends GeometryNode { this._shape = shape; this._mesh = undefined; this._boundingBox = undefined; - this._shape.value.matrix = this.matrix; + this._shape.value.matrix = this.transform; this.emitPropertyChanged("shape", oldShape); } - protected override onMatrixChanged(newMatrix: Matrix4): void { + protected override onTransformChanged(newMatrix: Matrix4): void { if (this.shape.isOk) this.shape.value.matrix = newMatrix; } diff --git a/packages/chili-core/src/selection.ts b/packages/chili-core/src/selection.ts index 718d01eb..9f4c6c43 100644 --- a/packages/chili-core/src/selection.ts +++ b/packages/chili-core/src/selection.ts @@ -2,14 +2,14 @@ import { AsyncController, IDisposable } from "./foundation"; import { I18nKeys } from "./i18n"; -import { GeometryNode, INode } from "./model"; -import { IShapeFilter } from "./selectionFilter"; +import { INode, VisualNode } from "./model"; +import { INodeFilter, IShapeFilter } from "./selectionFilter"; import { ShapeType } from "./shape"; import { CursorType, IEventHandler, VisualShapeData } from "./visual"; export interface ISelection extends IDisposable { pickShape(prompt: I18nKeys, controller: AsyncController, multiMode: boolean): Promise; - pickModel(prompt: I18nKeys, controller: AsyncController, multiMode: boolean): Promise; + pickNode(prompt: I18nKeys, controller: AsyncController, multiMode: boolean): Promise; pickAsync( handler: IEventHandler, prompt: I18nKeys, @@ -18,8 +18,8 @@ export interface ISelection extends IDisposable { cursor: CursorType, ): Promise; shapeType: ShapeType; - nodeType: "model" | "node"; - filter?: IShapeFilter; + shapeFilter?: IShapeFilter; + nodeFilter?: INodeFilter; setSelection(nodes: INode[], toggle: boolean): number; clearSelection(): void; getSelectedNodes(): INode[]; diff --git a/packages/chili-core/src/selectionFilter.ts b/packages/chili-core/src/selectionFilter.ts index 4ffb806d..ac9890b8 100644 --- a/packages/chili-core/src/selectionFilter.ts +++ b/packages/chili-core/src/selectionFilter.ts @@ -1,7 +1,12 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. +import { INode } from "./model"; import { IShape } from "./shape"; export interface IShapeFilter { allow(shape: IShape): boolean; } + +export interface INodeFilter { + allow(node: INode): boolean; +} diff --git a/packages/chili-core/src/shape/meshData.ts b/packages/chili-core/src/shape/meshData.ts index 1504ec1e..0541d772 100644 --- a/packages/chili-core/src/shape/meshData.ts +++ b/packages/chili-core/src/shape/meshData.ts @@ -2,9 +2,34 @@ import { VisualConfig } from "../config"; import { XYZ } from "../math"; +import { Serializer } from "../serialize"; import { LineType } from "./lineType"; import { IShape } from "./shape"; +@Serializer.register([]) +export class Mesh { + @Serializer.serialze() + meshType: "line" | "surface" = "line"; + + @Serializer.serialze() + position: number[] = []; + + @Serializer.serialze() + normal: number[] | undefined = undefined; + + @Serializer.serialze() + index: number[] | undefined = undefined; + + @Serializer.serialze() + color: number | number[] = 0xfff; + + @Serializer.serialze() + uv: number[] | undefined = undefined; + + @Serializer.serialze() + groups: { start: number; count: number; materialId: number }[] = []; +} + export interface IShapeMeshData { get shape(): IShape; get edges(): EdgeMeshData | undefined; diff --git a/packages/chili-core/src/visual/detectedData.ts b/packages/chili-core/src/visual/detectedData.ts index ebddfdd7..93d65726 100644 --- a/packages/chili-core/src/visual/detectedData.ts +++ b/packages/chili-core/src/visual/detectedData.ts @@ -2,7 +2,7 @@ import { XYZ } from "../math"; import { IShape } from "../shape"; -import { IVisualGeometry } from "./visualShape"; +import { IVisualGeometry } from "./visualObject"; export interface VisualShapeData { shape: IShape; diff --git a/packages/chili-core/src/visual/highlighter.ts b/packages/chili-core/src/visual/highlighter.ts index 4e536bc2..7ed781fb 100644 --- a/packages/chili-core/src/visual/highlighter.ts +++ b/packages/chili-core/src/visual/highlighter.ts @@ -1,14 +1,15 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { ShapeMeshData, ShapeType } from "../shape"; -import { IVisualGeometry, VisualState } from "./visualShape"; +import { IVisualObject } from "./visualObject"; +import { VisualState } from "./visualShape"; export interface IHighlighter { - getState(shape: IVisualGeometry, type: ShapeType, index?: number): VisualState | undefined; + getState(shape: IVisualObject, type: ShapeType, index?: number): VisualState | undefined; clear(): void; - resetState(shape: IVisualGeometry): void; - addState(shape: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]): void; - removeState(shape: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]): void; + resetState(shape: IVisualObject): void; + addState(shape: IVisualObject, state: VisualState, type: ShapeType, ...index: number[]): void; + removeState(shape: IVisualObject, state: VisualState, type: ShapeType, ...index: number[]): void; highlightMesh(...datas: ShapeMeshData[]): number; removeHighlightMesh(id: number): void; } diff --git a/packages/chili-core/src/visual/view.ts b/packages/chili-core/src/visual/view.ts index 75b947ec..f63c63ad 100644 --- a/packages/chili-core/src/visual/view.ts +++ b/packages/chili-core/src/visual/view.ts @@ -3,10 +3,11 @@ import { IDocument } from "../document"; import { IDisposable, IPropertyChanged } from "../foundation"; import { Plane, Ray, XY, XYZ } from "../math"; -import { IShapeFilter } from "../selectionFilter"; +import { INodeFilter, IShapeFilter } from "../selectionFilter"; import { ShapeType } from "../shape"; import { ICameraController } from "./cameraController"; import { VisualShapeData } from "./detectedData"; +import { IVisualObject } from "./visualObject"; export interface IView extends IPropertyChanged, IDisposable { readonly document: IDocument; @@ -24,8 +25,16 @@ export interface IView extends IPropertyChanged, IDisposable { resize(width: number, heigth: number): void; setDom(element: HTMLElement): void; close(): void; - detected(shapeType: ShapeType, x: number, y: number, shapeFilter?: IShapeFilter): VisualShapeData[]; - rectDetected( + detectVisual(x: number, y: number, nodeFilter?: INodeFilter): IVisualObject[]; + detectVisualRect( + x1: number, + y1: number, + x2: number, + y2: number, + nodeFilter?: INodeFilter, + ): IVisualObject[]; + detectShapes(shapeType: ShapeType, x: number, y: number, shapeFilter?: IShapeFilter): VisualShapeData[]; + detectShapesRect( shapeType: ShapeType, x1: number, y1: number, diff --git a/packages/chili-core/src/visual/visual.ts b/packages/chili-core/src/visual/visual.ts index 0d5a6ff5..844919b3 100644 --- a/packages/chili-core/src/visual/visual.ts +++ b/packages/chili-core/src/visual/visual.ts @@ -14,7 +14,7 @@ export interface IVisual extends IDisposable { readonly context: IVisualContext; readonly viewHandler: IEventHandler; readonly highlighter: IHighlighter; - readonly textGenerator: ITextGenerator; + // readonly textGenerator: ITextGenerator; update(): void; eventHandler: IEventHandler; resetEventHandler(): void; diff --git a/packages/chili-core/src/visual/visualContext.ts b/packages/chili-core/src/visual/visualContext.ts index dec8538e..b05a4f9e 100644 --- a/packages/chili-core/src/visual/visualContext.ts +++ b/packages/chili-core/src/visual/visualContext.ts @@ -1,31 +1,24 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDisposable, INodeChangedObserver } from "../foundation"; -import { XYZ } from "../math"; +import { BoundingBox } from "../math"; import { INode } from "../model"; import { IShapeFilter } from "../selectionFilter"; import { ShapeMeshData } from "../shape"; import { IVisualObject } from "./visualObject"; -import { IVisualGeometry } from "./visualShape"; export interface IVisualContext extends IDisposable, INodeChangedObserver { get shapeCount(): number; addVisualObject(object: IVisualObject): void; - boundingBoxIntersectFilter( - boundingBox: { - min: XYZ; - max: XYZ; - }, - filter?: IShapeFilter, - ): IVisualGeometry[]; + boundingBoxIntersectFilter(boundingBox: BoundingBox, filter?: IShapeFilter): IVisualObject[]; removeVisualObject(object: IVisualObject): void; - addModel(models: INode[]): void; - removeModel(models: INode[]): void; - getShape(model: INode): IVisualGeometry | undefined; - getModel(shape: IVisualGeometry): INode | undefined; - redrawModel(models: INode[]): void; - setVisible(model: INode, visible: boolean): void; - shapes(): IVisualGeometry[]; + addNode(nodes: INode[]): void; + removeNode(nodes: INode[]): void; + getVisual(node: INode): IVisualObject | undefined; + getNode(visual: IVisualObject): INode | undefined; + redrawNode(nodes: INode[]): void; + setVisible(node: INode, visible: boolean): void; + visuals(): IVisualObject[]; displayMesh(...datas: ShapeMeshData[]): number; removeMesh(id: number): void; } diff --git a/packages/chili-core/src/visual/visualObject.ts b/packages/chili-core/src/visual/visualObject.ts index a6484b1c..2a5cc4a3 100644 --- a/packages/chili-core/src/visual/visualObject.ts +++ b/packages/chili-core/src/visual/visualObject.ts @@ -1,12 +1,19 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IDisposable, Matrix4, XYZ } from "chili-core"; +import { BoundingBox, GeometryNode, IDisposable, INode, Matrix4, VisualNode } from "chili-core"; export interface IVisualObject extends IDisposable { visible: boolean; transform: Matrix4; - boundingBox(): { - min: XYZ; - max: XYZ; - }; + boundingBox(): BoundingBox; +} + +export interface IVisualGeometry extends IVisualObject { + get geometryNode(): GeometryNode; +} + +export namespace IVisualObject { + export function isGeometry(obj: IVisualObject): obj is IVisualGeometry { + return (obj as IVisualGeometry).geometryNode !== undefined; + } } diff --git a/packages/chili-core/src/visual/visualShape.ts b/packages/chili-core/src/visual/visualShape.ts index 22a5e85c..3d645e40 100644 --- a/packages/chili-core/src/visual/visualShape.ts +++ b/packages/chili-core/src/visual/visualShape.ts @@ -28,7 +28,3 @@ export interface VisualGroup { count: number; materialIndex?: number; } - -export interface IVisualGeometry extends IVisualObject { - get geometryNode(): GeometryNode; -} diff --git a/packages/chili-three/src/cameraController.ts b/packages/chili-three/src/cameraController.ts index 4ea30d89..7fee3ea7 100644 --- a/packages/chili-three/src/cameraController.ts +++ b/packages/chili-three/src/cameraController.ts @@ -87,7 +87,7 @@ export class CameraController implements ICameraController { } startRotate(x: number, y: number): void { - let shape = this.view.detected(ShapeType.Shape, x, y).at(0)?.owner; + let shape = this.view.detectShapes(ShapeType.Shape, x, y).at(0)?.owner; if (!(shape instanceof ThreeGeometry)) { this._rotateCenter = undefined; return; @@ -144,7 +144,7 @@ export class CameraController implements ICameraController { let box = new Box3(); for (let shape of shapes) { - let threeGeometry = context.getShape(shape) as ThreeGeometry; + let threeGeometry = context.getVisual(shape) as ThreeGeometry; let boundingBox = new Box3().setFromObject(threeGeometry); if (boundingBox) { box.union(boundingBox); diff --git a/packages/chili-three/src/threeGeometry.ts b/packages/chili-three/src/threeGeometry.ts index 82bb4de5..1c6d6db4 100644 --- a/packages/chili-three/src/threeGeometry.ts +++ b/packages/chili-three/src/threeGeometry.ts @@ -4,13 +4,11 @@ import { EdgeMeshData, FaceMeshData, GeometryNode, - IMeshObject, IVisualGeometry, - Matrix4, ShapeNode, VisualConfig, } from "chili-core"; -import { DoubleSide, Material, Mesh, MeshLambertMaterial, Object3D } from "three"; +import { DoubleSide, Material, Mesh, MeshLambertMaterial } from "three"; import { MeshUtils } from "chili-geo"; import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; @@ -19,10 +17,11 @@ import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeome import { ThreeGeometryFactory } from "./threeGeometryFactory"; import { ThreeHelper } from "./threeHelper"; import { ThreeVisualContext } from "./threeVisualContext"; +import { ThreeVisualObject } from "./threeVisualObject"; -export class ThreeGeometry extends Object3D implements IVisualGeometry { +export class ThreeGeometry extends ThreeVisualObject implements IVisualGeometry { private _faceMaterial: Material; - private _edgeMaterial = new LineMaterial({ + private readonly _edgeMaterial = new LineMaterial({ linewidth: 1, color: VisualConfig.defaultEdgeColor, side: DoubleSide, @@ -45,22 +44,12 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { } } - get transform() { - return ThreeHelper.toMatrix(this.matrix); - } - - set transform(value: Matrix4) { - this.matrix.fromArray(value.toArray()); - } - constructor( readonly geometryNode: GeometryNode, readonly context: ThreeVisualContext, ) { - super(); - this.transform = geometryNode.matrix; + super(geometryNode); this._faceMaterial = context.getMaterial(geometryNode.materialId); - this.matrixAutoUpdate = false; this.generateShape(); geometryNode.onPropertyChanged(this.handleGeometryPropertyChanged); } @@ -69,7 +58,7 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { return this._faces?.geometry.boundingBox ?? this._edges?.geometry.boundingBox ?? undefined; } - boundingBox() { + override boundingBox() { let box = this._faces?.geometry.boundingBox ?? this._edges?.geometry.boundingBox; let min = ThreeHelper.toXYZ(box!.min); let max = ThreeHelper.toXYZ(box!.max); @@ -79,14 +68,12 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { }; } - private handleGeometryPropertyChanged = (property: keyof IMeshObject) => { - if (property === "matrix") { - this.transform = this.geometryNode.matrix; - } else if (property === "materialId") { + private readonly handleGeometryPropertyChanged = (property: keyof GeometryNode) => { + if (property === "materialId") { let material = this.context.getMaterial(this.geometryNode.materialId); this.changeFaceMaterial(material); } else if ((property as keyof ShapeNode) === "shape") { - this.removeSubShapes(); + this.removeMeshes(); this.generateShape(); } }; @@ -97,13 +84,14 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { if (mesh?.edges?.positions.length) this.initEdges(mesh.edges); } - dispose() { - this.removeSubShapes(); + override dispose() { + super.dispose(); + this.removeMeshes(); this.geometryNode.removePropertyChanged(this.handleGeometryPropertyChanged); this._edgeMaterial.dispose(); } - private removeSubShapes() { + private removeMeshes() { if (this._edges) { this.remove(this._edges); this._edges.geometry.dispose(); diff --git a/packages/chili-three/src/threeHighlighter.ts b/packages/chili-three/src/threeHighlighter.ts index e8d1a022..90f10393 100644 --- a/packages/chili-three/src/threeHighlighter.ts +++ b/packages/chili-three/src/threeHighlighter.ts @@ -1,13 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { - IDisposable, - IHighlighter, - IVisualGeometry, - ShapeMeshData, - ShapeType, - VisualState, -} from "chili-core"; +import { IDisposable, IHighlighter, ShapeMeshData, ShapeType, VisualState } from "chili-core"; import { MeshUtils } from "chili-geo"; import { Group, Mesh, Points } from "three"; import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; @@ -16,13 +9,14 @@ import { hilightEdgeMaterial, selectedEdgeMaterial } from "./common"; import { ThreeGeometry } from "./threeGeometry"; import { ThreeGeometryFactory } from "./threeGeometryFactory"; import { ThreeVisualContext } from "./threeVisualContext"; +import { ThreeMeshObject, ThreeVisualObject } from "./threeVisualObject"; export class GeometryState { - private readonly _states: Map = new Map(); + private readonly _states: Map = new Map(); constructor( readonly highlighter: ThreeHighlighter, - readonly geometry: ThreeGeometry, + readonly visual: ThreeVisualObject, ) {} getState(type: ShapeType, index?: number) { @@ -42,6 +36,32 @@ export class GeometryState { this.updateState("remove", state, type, index); } + private updateState(method: "add" | "remove", state: VisualState, type: ShapeType, index: number[]) { + if (ShapeType.isWhole(type)) { + this.setWholeState(method, state, type); + } else if (index.length > 0) { + this.setSubGeometryState(method, state, type, index); + } + } + + private setWholeState(method: "add" | "remove", state: VisualState, type: ShapeType) { + const key = this.state_key(type); + let [oldState, newState] = this.updateStates(key, method, state); + if (this.visual instanceof ThreeGeometry) { + if (newState === VisualState.normal) { + this.visual.removeTemperaryMaterial(); + } else if (VisualState.hasState(newState, VisualState.highlighter)) { + this.visual.setEdgesMateiralTemperary(hilightEdgeMaterial); + } else if (VisualState.hasState(newState, VisualState.selected)) { + this.visual.setEdgesMateiralTemperary(selectedEdgeMaterial); + } + } else if (this.visual instanceof ThreeMeshObject) { + this.visual.setHighlighted(newState !== VisualState.normal); + } + + this._states.set(key, [newState, undefined]); + } + private updateStates( key: string, method: "add" | "remove", @@ -59,28 +79,6 @@ export class GeometryState { return [oldState, newState]; } - private updateState(method: "add" | "remove", state: VisualState, type: ShapeType, index: number[]) { - if (ShapeType.isWhole(type)) { - this.setWholeState(method, state, type); - } else if (index.length > 0) { - this.setSubGeometryState(method, state, type, index); - } - } - - private setWholeState(method: "add" | "remove", state: VisualState, type: ShapeType) { - const key = this.state_key(type); - let [oldState, newState] = this.updateStates(key, method, state); - if (newState === VisualState.normal) { - this.geometry.removeTemperaryMaterial(); - } else if (VisualState.hasState(newState, VisualState.highlighter)) { - this.geometry.setEdgesMateiralTemperary(hilightEdgeMaterial); - } else if (VisualState.hasState(newState, VisualState.selected)) { - this.geometry.setEdgesMateiralTemperary(selectedEdgeMaterial); - } - - this._states.set(key, [newState, this.geometry as any]); - } - resetState() { this.highlighter.container.children.forEach((x) => { (x as any).geometry?.dispose(); @@ -102,14 +100,7 @@ export class GeometryState { if (oldState !== undefined && newState === VisualState.normal) { shouldRemoved.push(key); } else { - let geometry = this.getOrCloneGeometry(type, key, i); - if (geometry) { - let material = VisualState.hasState(newState, VisualState.highlighter) - ? hilightEdgeMaterial - : selectedEdgeMaterial; - geometry.material = material; - this._states.set(key, [newState, geometry]); - } + this.addSubEdgeState(type, key, i, newState); } }); @@ -123,16 +114,29 @@ export class GeometryState { }); } + private addSubEdgeState(type: ShapeType, key: string, i: number, newState: VisualState) { + let geometry = this.getOrCloneGeometry(type, key, i); + if (geometry && "material" in geometry) { + let material = VisualState.hasState(newState, VisualState.highlighter) + ? hilightEdgeMaterial + : selectedEdgeMaterial; + geometry.material = material; + this._states.set(key, [newState, geometry]); + } + } + private getOrCloneGeometry(type: ShapeType, key: string, index: number) { + if (!(this.visual instanceof ThreeGeometry)) return undefined; + let geometry = this._states.get(key)?.[1]; if (geometry !== undefined) return geometry; let points: number[] | undefined = undefined; if (ShapeType.hasFace(type) || ShapeType.hasShell(type)) { - points = MeshUtils.subFaceOutlines(this.geometry.geometryNode.mesh.faces!, index); + points = MeshUtils.subFaceOutlines(this.visual.geometryNode.mesh.faces!, index); } if (points === undefined && (ShapeType.hasEdge(type) || ShapeType.hasWire(type))) { - points = MeshUtils.subEdge(this.geometry.geometryNode.mesh.edges!, index); + points = MeshUtils.subEdge(this.visual.geometryNode.mesh.edges!, index); } if (!points) { @@ -144,13 +148,13 @@ export class GeometryState { lineGeometry.setPositions(points); let segment = new LineSegments2(lineGeometry); this.highlighter.container.add(segment); - segment.applyMatrix4(this.geometry.matrixWorld); + segment.applyMatrix4(this.visual.matrixWorld); return segment; } } export class ThreeHighlighter implements IHighlighter { - private readonly _stateMap = new Map(); + private readonly _stateMap = new Map(); readonly container: Group; constructor(readonly content: ThreeVisualContext) { @@ -166,34 +170,34 @@ export class ThreeHighlighter implements IHighlighter { this._stateMap.clear(); } - resetState(geometry: IVisualGeometry): void { + resetState(geometry: ThreeVisualObject): void { if (!this._stateMap.has(geometry)) return; let geometryState = this._stateMap.get(geometry); geometryState!.resetState(); this._stateMap.delete(geometry); } - getState(shape: IVisualGeometry, type: ShapeType, index?: number): VisualState | undefined { + getState(shape: ThreeVisualObject, type: ShapeType, index?: number): VisualState | undefined { if (this._stateMap.has(shape)) { return this._stateMap.get(shape)!.getState(type, index); } return undefined; } - addState(geometry: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]) { + addState(geometry: ThreeVisualObject, state: VisualState, type: ShapeType, ...index: number[]) { let geometryState = this.getOrInitState(geometry); geometryState.addState(state, type, index); } - removeState(geometry: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]) { + removeState(geometry: ThreeVisualObject, state: VisualState, type: ShapeType, ...index: number[]) { let geometryState = this.getOrInitState(geometry); geometryState.removeState(state, type, index); } - private getOrInitState(geometry: IVisualGeometry) { + private getOrInitState(geometry: ThreeVisualObject) { let geometryState = this._stateMap.get(geometry); if (!geometryState) { - geometryState = new GeometryState(this, geometry as ThreeGeometry); + geometryState = new GeometryState(this, geometry); this._stateMap.set(geometry, geometryState); } return geometryState; diff --git a/packages/chili-three/src/threeMeshObject.ts b/packages/chili-three/src/threeMeshObject.ts deleted file mode 100644 index 02d8b755..00000000 --- a/packages/chili-three/src/threeMeshObject.ts +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. - -import { IVisualObject, Matrix4, XYZ } from "chili-core"; -import { Box3, Mesh, MeshBasicMaterial } from "three"; -import { ThreeHelper } from "./threeHelper"; - -export class ThreeMeshObject extends Mesh implements IVisualObject { - get transform() { - return ThreeHelper.toMatrix(this.matrix); - } - - set transform(value: Matrix4) { - this.matrix.fromArray(value.toArray()); - } - - get color() { - return ThreeHelper.toColor((this.material as MeshBasicMaterial).color); // TODO: assert - } - - get opacity() { - return (this.material as MeshBasicMaterial).opacity; // TODO: assert - } - - boundingBox(): { min: XYZ; max: XYZ } { - const box = new Box3(); - box.setFromObject(this); - return { min: ThreeHelper.toXYZ(box.min), max: ThreeHelper.toXYZ(box.max) }; - } - - dispose(): void { - this.geometry.dispose(); - if (Array.isArray(this.material)) { - this.material.forEach((m) => m.dispose()); - } else { - this.material.dispose(); - } - } -} diff --git a/packages/chili-three/src/threeTextGenerator.ts b/packages/chili-three/src/threeTextGenerator.ts deleted file mode 100644 index df71b554..00000000 --- a/packages/chili-three/src/threeTextGenerator.ts +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. - -import { ITextGenerator } from "chili-core"; -import { DoubleSide, MeshBasicMaterial, ShapeGeometry } from "three"; -import { Font, FontLoader } from "three/examples/jsm/loaders/FontLoader.js"; -import { ThreeMeshObject } from "./threeMeshObject"; - -export class ThreeTextGenerator implements ITextGenerator { - private readonly _fonts: Map = new Map(); - - async generate(text: string, size: number, color: number, fontName = "fzhei") { - let font = await this._getFont(fontName); - let shapes = font.generateShapes(text, size); - const geometry = new ShapeGeometry(shapes); - let material = new MeshBasicMaterial({ - color: color, - side: DoubleSide, - }); - return new ThreeMeshObject(geometry, material); - } - - private async _getFont(fontName: string) { - let font = this._fonts.get(fontName); - if (!font) { - const loader = new FontLoader(); - font = await loader.loadAsync(`fonts/${fontName}.json`); - this._fonts.set(fontName, font); - } - return font; - } -} diff --git a/packages/chili-three/src/threeView.ts b/packages/chili-three/src/threeView.ts index 69c5c7c5..0957911a 100644 --- a/packages/chili-three/src/threeView.ts +++ b/packages/chili-three/src/threeView.ts @@ -2,9 +2,11 @@ import { IDocument, + INodeFilter, IShape, IShapeFilter, IView, + IVisualObject, Observable, Plane, PubSub, @@ -12,6 +14,7 @@ import { ShapeMeshGroup, ShapeNode, ShapeType, + VisualNode, VisualShapeData, XY, XYZ, @@ -38,23 +41,23 @@ import { ThreeGeometry } from "./threeGeometry"; import { ThreeHelper } from "./threeHelper"; import { ThreeHighlighter } from "./threeHighlighter"; import { ThreeVisualContext } from "./threeVisualContext"; +import { ThreeMeshObject, ThreeVisualObject } from "./threeVisualObject"; import { ViewGizmo } from "./viewGizmo"; export class ThreeView extends Observable implements IView { private _dom?: HTMLElement; - private _resizeObserver: ResizeObserver; + private readonly _resizeObserver: ResizeObserver; - private _scene: Scene; - private _renderer: WebGLRenderer; - private _workplane: Plane; + private readonly _scene: Scene; + private readonly _renderer: WebGLRenderer; + private readonly _workplane: Plane; private _needsUpdate: boolean = false; private readonly _gizmo: ViewGizmo; readonly cameraController: CameraController; readonly dynamicLight = new DirectionalLight(0xffffff, 2); - private _name: string; get name(): string { - return this._name; + return this.getPrivateValue("name"); } set name(value: string) { this.setProperty("name", value); @@ -77,7 +80,7 @@ export class ThreeView extends Observable implements IView { readonly content: ThreeVisualContext, ) { super(); - this._name = name; + this.setPrivateValue("name", name); this._scene = content.scene; this._workplane = workplane; let resizerObserverCallback = debounce(this._resizerObserverCallback, 100); @@ -109,7 +112,7 @@ export class ThreeView extends Observable implements IView { PubSub.default.pub("viewClosed", this); } - private _resizerObserverCallback = (entries: ResizeObserverEntry[]) => { + private readonly _resizerObserverCallback = (entries: ResizeObserverEntry[]) => { for (const entry of entries) { if (entry.target === this._dom) { this.resize(entry.contentRect.width, entry.contentRect.height); @@ -249,19 +252,75 @@ export class ThreeView extends Observable implements IView { return new Vector3(x, y, z).unproject(this.camera); } - rectDetected( - shapeType: ShapeType, + detectVisual(x: number, y: number, nodeFilter?: INodeFilter): IVisualObject[] { + let visual: IVisualObject[] = []; + let detecteds = this.findIntersectedNodes(x, y); + for (const detected of detecteds) { + let threeObject = detected.object.parent as ThreeVisualObject; + if (!threeObject) continue; + + let node = this.getNodeFromObject(threeObject); + if (node === undefined) continue; + if (nodeFilter !== undefined && !nodeFilter.allow(node)) { + continue; + } + visual.push(threeObject); + } + return visual; + } + + detectVisualRect( mx1: number, my1: number, mx2: number, my2: number, - shapeFilter?: IShapeFilter, - ) { + nodeFilter?: INodeFilter, + ): IVisualObject[] { + const selectionBox = this.initSelectionBox(mx1, my1, mx2, my2); + let visual = new Set(); + for (const obj of selectionBox.select()) { + let threeObject = obj.parent as ThreeVisualObject; + if (!threeObject || !threeObject.visible) continue; + + let node = this.getNodeFromObject(threeObject); + if (node === undefined) continue; + if (nodeFilter !== undefined && !nodeFilter.allow(node)) { + continue; + } + visual.add(threeObject); + } + return Array.from(visual); + } + + private getNodeFromObject(threeObject: Object3D) { + let node: VisualNode | undefined; + if (threeObject instanceof ThreeMeshObject) { + node = threeObject.meshNode; + } else if (threeObject instanceof ThreeGeometry) { + node = threeObject.geometryNode; + } + + return node; + } + + private initSelectionBox(mx1: number, my1: number, mx2: number, my2: number) { const selectionBox = new SelectionBox(this.camera, this._scene); const start = this.screenToCameraRect(mx1, my1); const end = this.screenToCameraRect(mx2, my2); selectionBox.startPoint.set(start.x, start.y, 0.5); selectionBox.endPoint.set(end.x, end.y, 0.5); + return selectionBox; + } + + detectShapesRect( + shapeType: ShapeType, + mx1: number, + my1: number, + mx2: number, + my2: number, + shapeFilter?: IShapeFilter, + ) { + const selectionBox = this.initSelectionBox(mx1, my1, mx2, my2); let detecteds: VisualShapeData[] = []; let containsCache = new Set(); for (const obj of selectionBox.select()) { @@ -306,8 +365,13 @@ export class ThreeView extends Observable implements IView { return (obj.parent.geometryNode as ShapeNode).shape.unchecked(); } - detected(shapeType: ShapeType, mx: number, my: number, shapeFilter?: IShapeFilter): VisualShapeData[] { - let intersections = this.findIntersections(shapeType, mx, my); + detectShapes( + shapeType: ShapeType, + mx: number, + my: number, + shapeFilter?: IShapeFilter, + ): VisualShapeData[] { + let intersections = this.findIntersectedShapes(shapeType, mx, my); return ShapeType.isWhole(shapeType) ? this.detectThreeShapes(intersections, shapeFilter) : this.detectSubShapes(shapeType, intersections, shapeFilter); @@ -430,18 +494,39 @@ export class ThreeView extends Observable implements IView { return { shape, index, groups }; } - private findIntersections(shapeType: ShapeType, mx: number, my: number) { + private findIntersectedNodes(mx: number, my: number) { + let visuals: Object3D[] = []; + const addObject = (obj: Object3D | undefined) => { + if (obj !== undefined) visuals.push(obj); + }; + this.document.visual.context.visuals().forEach((x) => { + if (!x.visible) return; + + if (x instanceof ThreeGeometry) { + addObject(x.edges()); + addObject(x.faces()); + } + + if (x instanceof ThreeMeshObject) { + addObject(x.mesh); + } + }); + + return this.initRaycaster(mx, my).intersectObjects(visuals, false); + } + + private findIntersectedShapes(shapeType: ShapeType, mx: number, my: number) { let raycaster = this.initRaycaster(mx, my); - let shapes = this.initIntersectableObjects(shapeType); + let shapes = this.initIntersectableShapes(shapeType); return raycaster.intersectObjects(shapes, false); } - private initIntersectableObjects(shapeType: ShapeType) { + private initIntersectableShapes(shapeType: ShapeType) { let shapes = new Array(); const addObject = (obj: Object3D | undefined) => { if (obj !== undefined) shapes.push(obj); }; - this.document.visual.context.shapes().forEach((x) => { + this.document.visual.context.visuals().forEach((x) => { if (!(x instanceof ThreeGeometry) || !x.visible) return; if ( shapeType === ShapeType.Shape || diff --git a/packages/chili-three/src/threeVisual.ts b/packages/chili-three/src/threeVisual.ts index 791cdf19..5b36472f 100644 --- a/packages/chili-three/src/threeVisual.ts +++ b/packages/chili-three/src/threeVisual.ts @@ -1,10 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDisposable, IDocument, IEventHandler, ITextGenerator, IVisual, Logger, Plane } from "chili-core"; -import { ModelSelectionHandler } from "chili-vis"; +import { NodeSelectionHandler } from "chili-vis"; import { AmbientLight, AxesHelper, Object3D, Scene } from "three"; import { ThreeHighlighter } from "./threeHighlighter"; -import { ThreeTextGenerator } from "./threeTextGenerator"; import { ThreeView } from "./threeView"; import { ThreeViewHandler } from "./threeViewEventHandler"; import { ThreeVisualContext } from "./threeVisualContext"; @@ -17,7 +16,7 @@ export class ThreeVisual implements IVisual { readonly scene: Scene; readonly viewHandler: IEventHandler; readonly highlighter: ThreeHighlighter; - readonly textGenerator: ITextGenerator; + // readonly textGenerator: ITextGenerator; private _eventHandler: IEventHandler; @@ -33,11 +32,11 @@ export class ThreeVisual implements IVisual { constructor(readonly document: IDocument) { this.scene = this.initScene(); - this.defaultEventHandler = new ModelSelectionHandler(document, true); + this.defaultEventHandler = new NodeSelectionHandler(document, true); this.context = new ThreeVisualContext(this, this.scene); this.viewHandler = new ThreeViewHandler(); this.highlighter = new ThreeHighlighter(this.context); - this.textGenerator = new ThreeTextGenerator(); + // this.textGenerator = new ThreeTextGenerator(); this._eventHandler = this.defaultEventHandler; } diff --git a/packages/chili-three/src/threeVisualContext.ts b/packages/chili-three/src/threeVisualContext.ts index 097dc09c..db66b9ec 100644 --- a/packages/chili-three/src/threeVisualContext.ts +++ b/packages/chili-three/src/threeVisualContext.ts @@ -8,10 +8,10 @@ import { IShapeFilter, IVisual, IVisualContext, - IVisualGeometry, IVisualObject, Material, MathUtils, + MeshNode, NodeAction, NodeRecord, ShapeMeshData, @@ -31,15 +31,17 @@ import { Scene, TextureLoader, MeshLambertMaterial as ThreeMaterial, + Vector3, } from "three"; import { ThreeGeometry } from "./threeGeometry"; import { ThreeGeometryFactory } from "./threeGeometryFactory"; import { ThreeHelper } from "./threeHelper"; +import { ThreeMeshObject } from "./threeVisualObject"; export class ThreeVisualContext implements IVisualContext { - private readonly _shapeModelMap = new WeakMap(); - private readonly _modelShapeMap = new WeakMap(); - private readonly materialMap = new Map(); + private readonly _visualNodeMap = new WeakMap(); + private readonly _NodeVisualMap = new WeakMap(); + readonly materialMap = new Map(); readonly visualShapes: Group; readonly tempShapes: Group; @@ -55,7 +57,7 @@ export class ThreeVisualContext implements IVisualContext { visual.document.materials.onCollectionChanged(this.onMaterialsChanged); } - private onMaterialsChanged = (args: CollectionChangedArgs) => { + private readonly onMaterialsChanged = (args: CollectionChangedArgs) => { if (args.action === CollectionAction.add) { args.items.forEach((item: Material) => { let material = new ThreeMaterial({ @@ -101,7 +103,7 @@ export class ThreeVisualContext implements IVisualContext { return material; } - private onMaterialPropertyChanged = (prop: keyof Material, source: Material) => { + private readonly onMaterialPropertyChanged = (prop: keyof Material, source: Material) => { let material = this.materialMap.get(source.id); if (!material) return; if (prop === "color") { @@ -133,8 +135,8 @@ export class ThreeVisualContext implements IVisualContext { INode.nodeOrChildrenAppendToNodes(rms, x.node); } }); - this.addModel(adds.filter((x) => !INode.isLinkedListNode(x))); - this.removeModel(rms.filter((x) => !INode.isLinkedListNode(x))); + this.addNode(adds.filter((x) => !INode.isLinkedListNode(x))); + this.removeNode(rms.filter((x) => !INode.isLinkedListNode(x))); }; addVisualObject(object: IVisualObject): void { @@ -165,13 +167,13 @@ export class ThreeVisualContext implements IVisualContext { this.scene.remove(this.visualShapes, this.tempShapes); } - getModel(shape: IVisualGeometry): INode | undefined { - return this._shapeModelMap.get(shape); + getNode(visual: IVisualObject): INode | undefined { + return this._visualNodeMap.get(visual); } - redrawModel(models: INode[]) { - this.removeModel(models); - this.addModel(models); + redrawNode(models: INode[]) { + this.removeNode(models); + this.addNode(models); // TODO: set state } @@ -180,13 +182,13 @@ export class ThreeVisualContext implements IVisualContext { return this.visualShapes.children.length; } - getShape(model: INode): IVisualGeometry | undefined { - return this._modelShapeMap.get(model); + getVisual(nodel: INode): IVisualObject | undefined { + return this._NodeVisualMap.get(nodel); } - shapes(): IVisualGeometry[] { - let shapes = new Array(); - this.visualShapes.children.forEach((x) => this._getThreeShapes(shapes, x)); + visuals(): IVisualObject[] { + let shapes = new Array(); + this.visualShapes.children.forEach((x) => this._getVisualObject(shapes, x)); return shapes; } @@ -196,29 +198,36 @@ export class ThreeVisualContext implements IVisualContext { max: XYZ; }, filter?: IShapeFilter, - ): IVisualGeometry[] { + ): IVisualObject[] { let box = new Box3().setFromPoints([ ThreeHelper.fromXYZ(boundingBox.min), ThreeHelper.fromXYZ(boundingBox.max), ]); - return this.shapes().filter((x) => { + return this.visuals().filter((x) => { if (filter && x instanceof ShapeNode && x.shape.isOk && !filter.allow(x.shape.value)) { return false; } - let testBox = (x as ThreeGeometry).box(); - if (testBox === undefined) { + let boundingBox = x.boundingBox(); + if (boundingBox === undefined) { return false; } + + let testBox = new Box3( + new Vector3(boundingBox.min.x, boundingBox.min.y, boundingBox.min.z), + new Vector3(boundingBox.max.x, boundingBox.max.y, boundingBox.max.z), + ); return box.intersectsBox(testBox); }); } - private _getThreeShapes(shapes: Array, shape: Object3D) { - let group = shape as Group; + private _getVisualObject(visuals: Array, obj: Object3D) { + let group = obj as Group; if (group.type === "Group") { - group.children.forEach((x) => this._getThreeShapes(shapes, x)); - } else if (shape instanceof ThreeGeometry) shapes.push(shape); + group.children.forEach((x) => this._getVisualObject(visuals, x)); + } else if (obj instanceof ThreeGeometry || obj instanceof ThreeMeshObject) { + visuals.push(obj); + } } displayMesh(...datas: ShapeMeshData[]): number { @@ -252,37 +261,43 @@ export class ThreeVisualContext implements IVisualContext { this.tempShapes.remove(shape); } - setVisible(model: INode, visible: boolean): void { - let shape = this.getShape(model); + setVisible(node: INode, visible: boolean): void { + let shape = this.getVisual(node); if (shape === undefined || shape.visible === visible) return; shape.visible = visible; } - addModel(models: INode[]) { - models.forEach((model) => { - if (this._modelShapeMap.has(model)) return; - this.displayModel(model); + addNode(nodes: INode[]) { + nodes.forEach((node) => { + if (this._NodeVisualMap.has(node)) return; + this.displayNode(node); }); } - private displayModel(model: INode) { - if (model instanceof ShapeNode) { - let threeShape = new ThreeGeometry(model as any, this); - this.visualShapes.add(threeShape); - this._shapeModelMap.set(threeShape, model); - this._modelShapeMap.set(model, threeShape); + private displayNode(node: INode) { + let visualObject: (IVisualObject & Object3D) | undefined = undefined; + if (node instanceof MeshNode) { + visualObject = new ThreeMeshObject(this, node); + } else if (node instanceof ShapeNode) { + visualObject = new ThreeGeometry(node as any, this); + } + + if (visualObject) { + this.visualShapes.add(visualObject); + this._visualNodeMap.set(visualObject, node); + this._NodeVisualMap.set(node, visualObject); } } - removeModel(models: INode[]) { + removeNode(models: INode[]) { models.forEach((m) => { - let shape = this._modelShapeMap.get(m); - this._modelShapeMap.delete(m); - if (!shape) return; - this._shapeModelMap.delete(shape); - if (shape instanceof ThreeGeometry) { - this.visualShapes.remove(shape); - shape.dispose(); + let visual = this._NodeVisualMap.get(m); + this._NodeVisualMap.delete(m); + if (!visual) return; + this._visualNodeMap.delete(visual); + if (visual instanceof ThreeGeometry || visual instanceof ThreeMeshObject) { + this.visualShapes.remove(visual); + visual.dispose(); } }); } @@ -294,6 +309,7 @@ export class ThreeVisualContext implements IVisualContext { const shapes: Object3D[] = []; this.visualShapes.traverse((child) => { if (!(child instanceof ThreeGeometry)) return; + if (shapeType === ShapeType.Edge) { let wireframe = child.edges(); if (wireframe) shapes.push(wireframe); diff --git a/packages/chili-three/src/threeVisualObject.ts b/packages/chili-three/src/threeVisualObject.ts index 3d7378be..58cfd040 100644 --- a/packages/chili-three/src/threeVisualObject.ts +++ b/packages/chili-three/src/threeVisualObject.ts @@ -1,38 +1,124 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IVisualObject, Matrix4, XYZ } from "chili-core"; -import { Box3, Group, Object3D } from "three"; +import { BoundingBox, IVisualObject, Matrix4, MeshNode, VisualNode } from "chili-core"; +import { Box3, BufferGeometry, DoubleSide, Float32BufferAttribute, Material, Mesh, Object3D } from "three"; +import { LineMaterial } from "three/examples/jsm/lines/LineMaterial"; +import { LineSegments2 } from "three/examples/jsm/lines/LineSegments2"; +import { LineSegmentsGeometry } from "three/examples/jsm/lines/LineSegmentsGeometry"; import { ThreeHelper } from "./threeHelper"; +import { ThreeVisualContext } from "./threeVisualContext"; -export class ThreeVisualObject extends Group implements IVisualObject { +export class ThreeVisualObject extends Object3D implements IVisualObject { get transform() { return ThreeHelper.toMatrix(this.matrix); } - set transform(value: Matrix4) { this.matrix.fromArray(value.toArray()); } - constructor(readonly proxy: Object3D) { + constructor(private visualNode: VisualNode) { super(); - this.add(proxy); this.matrixAutoUpdate = false; + this.transform = visualNode.transform; + visualNode.onPropertyChanged(this.handlePropertyChanged); } - boundingBox(): { min: XYZ; max: XYZ } { + private readonly handlePropertyChanged = (property: keyof MeshNode) => { + if (property === "transform") { + this.transform = this.visualNode.transform; + } + }; + + boundingBox(): BoundingBox { const box = new Box3(); - box.setFromObject(this.proxy); + box.setFromObject(this); return { min: ThreeHelper.toXYZ(box.min), max: ThreeHelper.toXYZ(box.max) }; } - dispose(): void { - const disposeObject3D = (disposable: any) => { - disposable.geometry?.dispose(); - disposable.material?.dispose(); - }; - this.proxy.traverse((child) => { - disposeObject3D(child); + dispose() { + this.visualNode.removePropertyChanged(this.handlePropertyChanged); + } +} + +export class ThreeMeshObject extends ThreeVisualObject { + private _mesh: LineSegments2 | Mesh; + get mesh() { + return this._mesh; + } + private readonly material: Material; + + constructor( + readonly context: ThreeVisualContext, + readonly meshNode: MeshNode, + ) { + super(meshNode); + this._mesh = this.createMesh(); + this.material = this._mesh.material as Material; + this.add(this._mesh); + meshNode.onPropertyChanged(this.handleGeometryPropertyChanged); + } + + setHighlighted(highlighted: boolean) { + this.material.transparent = highlighted; + this.material.opacity = highlighted ? 0.5 : 1; + } + + private createMesh() { + if (this.meshNode.mesh.meshType === "line") { + return this.newLine(); + } else if (this.meshNode.mesh.meshType === "surface") { + return this.newMesh(); + } + + throw new Error("Unknown mesh type"); + } + + private readonly handleGeometryPropertyChanged = (property: keyof MeshNode) => { + if (property === "mesh") { + this.disposeMesh(); + this._mesh = this.createMesh(); + this.add(this._mesh); + } + }; + + private newMesh() { + let buff = new BufferGeometry(); + buff.setAttribute("position", new Float32BufferAttribute(this.meshNode.mesh.position, 3)); + if (this.meshNode.mesh.normal) { + buff.setAttribute("normal", new Float32BufferAttribute(this.meshNode.mesh.normal, 3)); + } + if (this.meshNode.mesh.uv) { + buff.setAttribute("uv", new Float32BufferAttribute(this.meshNode.mesh.uv, 2)); + } + if (this.meshNode.mesh.index) { + buff.setIndex(this.meshNode.mesh.index); + } + buff.computeBoundingBox(); + return new Mesh(buff, this.context.materialMap.values().next().value); + } + + private newLine() { + let material = new LineMaterial({ + linewidth: 1, + color: this.meshNode.mesh.color as number, + side: DoubleSide, }); - disposeObject3D(this.proxy); + let buff = new LineSegmentsGeometry(); + buff.setPositions(this.meshNode.mesh.position); + buff.computeBoundingBox(); + return new LineSegments2(buff, material); + } + + private disposeMesh() { + if (this._mesh instanceof LineSegments2) { + this._mesh.material.dispose(); + } + this._mesh.geometry?.dispose(); + } + + override dispose(): void { + super.dispose(); + this.meshNode.removePropertyChanged(this.handleGeometryPropertyChanged); + this.disposeMesh(); } } diff --git a/packages/chili-three/test/three.test.ts b/packages/chili-three/test/three.test.ts index 3a400728..1ddbe69c 100644 --- a/packages/chili-three/test/three.test.ts +++ b/packages/chili-three/test/three.test.ts @@ -1,7 +1,7 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { expect, jest, test } from "@jest/globals"; -import { Material, ShapeNode, ShapeType, XY, XYZ } from "chili-core"; +import { IVisualGeometry, Material, ShapeNode, ShapeType, XY, XYZ } from "chili-core"; import { TestDocument } from "./testDocument"; import { TestNode } from "./testEdge"; import { TestView } from "./testView"; @@ -31,18 +31,18 @@ describe("three test", () => { test("test context", () => { let context = doc.visual.context; let model = new TestNode(doc, XYZ.zero, new XYZ(100, 0, 0)); - context.addModel([model]); - expect(context.getShape(model)).not.toBeNull(); + context.addNode([model]); + expect(context.getVisual(model)).not.toBeNull(); let mouse = view.worldToScreen(new XYZ(50, 0, 0)); - let shapes = view.detected(ShapeType.Shape, mouse.x, mouse.y); + let shapes = view.detectShapes(ShapeType.Shape, mouse.x, mouse.y); expect(shapes.length).toEqual(1); expect(shapes[0].shape.shapeType).toBe(ShapeType.Edge); - let shape = context.getShape(model); + let shape = context.getVisual(model) as IVisualGeometry; expect(shapes.at(0)?.shape).toEqual((shape?.geometryNode as ShapeNode).shape.value); - expect(context.getModel(shape!)).toEqual(model); + expect(context.getNode(shape)).toEqual(model); - context.removeModel([model]); - expect(view.detected(ShapeType.Shape, mouse.x, mouse.y).length).toEqual(0); + context.removeNode([model]); + expect(view.detectShapes(ShapeType.Shape, mouse.x, mouse.y).length).toEqual(0); }); }); diff --git a/packages/chili-ui/src/project/tree/tree.ts b/packages/chili-ui/src/project/tree/tree.ts index ee365f69..64fddd7a 100644 --- a/packages/chili-ui/src/project/tree/tree.ts +++ b/packages/chili-ui/src/project/tree/tree.ts @@ -1,7 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { - GeometryNode, IDocument, INode, INodeChangedObserver, @@ -10,8 +9,9 @@ import { PubSub, ShapeType, Transaction, + VisualNode, } from "chili-core"; -import { SelectionHandler } from "chili-vis"; +import { NodeSelectionHandler, ShapeSelectionHandler } from "chili-vis"; import style from "./tree.module.css"; import { TreeItem } from "./treeItem"; import { TreeGroup } from "./treeItemGroup"; @@ -122,7 +122,7 @@ export class Tree extends HTMLElement implements INodeChangedObserver { private createHTMLElement(document: IDocument, node: INode): TreeItem { let result: TreeItem; if (INode.isLinkedListNode(node)) result = new TreeGroup(document, node); - else if (node instanceof GeometryNode) result = new TreeModel(document, node); + else if (node instanceof VisualNode) result = new TreeModel(document, node); else throw new Error("unknown node"); return result; } @@ -183,10 +183,15 @@ export class Tree extends HTMLElement implements INodeChangedObserver { }; private canSelect() { - return ( - this.document.visual.eventHandler instanceof SelectionHandler && - this.document.visual.eventHandler.shapeType === ShapeType.Shape - ); + if (this.document.visual.eventHandler instanceof NodeSelectionHandler) { + return true; + } + + if (this.document.visual.eventHandler instanceof ShapeSelectionHandler) { + return this.document.visual.eventHandler.shapeType === ShapeType.Shape; + } + + return false; } private setLastClickItem(item: INode | undefined) { diff --git a/packages/chili-ui/src/property/material/materialEditor.ts b/packages/chili-ui/src/property/material/materialEditor.ts index f1dfc4f3..24585f37 100644 --- a/packages/chili-ui/src/property/material/materialEditor.ts +++ b/packages/chili-ui/src/property/material/materialEditor.ts @@ -22,7 +22,7 @@ class UrlStringConverter implements IConverter { } export class MaterialEditor extends HTMLElement { - private editingControl: HTMLElement; + private readonly editingControl: HTMLElement; private readonly colorConverter = new ColorConverter(); constructor(readonly dataContent: MaterialDataContent) { @@ -116,11 +116,11 @@ export class MaterialEditor extends HTMLElement { PubSub.default.remove("showProperties", this._handleShowProperty); } - private _handleShowProperty = () => { + private readonly _handleShowProperty = () => { this.remove(); }; - private _onEditingMaterialChanged = (property: keyof MaterialDataContent) => { + private readonly _onEditingMaterialChanged = (property: keyof MaterialDataContent) => { if (property !== "editingMaterial") return; this.editingControl.firstChild?.remove(); this.initEditingControl(this.dataContent.editingMaterial); @@ -129,7 +129,7 @@ export class MaterialEditor extends HTMLElement { private initEditingControl(material: Material) { const selectTexture = async () => { let file = await readFileAsync(".png, .jpg, .jpeg", false, "readAsDataURL"); - material.texture = file.value[0].data as string; + material.texture = file.value[0].data; }; let container = div({ className: style.properties, diff --git a/packages/chili-ui/src/property/materialProperty.ts b/packages/chili-ui/src/property/materialProperty.ts index 1f61d057..09f13da7 100644 --- a/packages/chili-ui/src/property/materialProperty.ts +++ b/packages/chili-ui/src/property/materialProperty.ts @@ -26,7 +26,7 @@ export class MaterialProperty extends PropertyBase { PubSub.default.pub( "editMaterial", document, - this.findMaterial(objects[0].materialId)!, + this.findMaterial(objects[0].materialId), (material) => { this.setMaterial(e, material); }, diff --git a/packages/chili-ui/src/property/propertyView.ts b/packages/chili-ui/src/property/propertyView.ts index 7a23f5da..89846c84 100644 --- a/packages/chili-ui/src/property/propertyView.ts +++ b/packages/chili-ui/src/property/propertyView.ts @@ -12,6 +12,7 @@ import { ParameterShapeNode, Property, PubSub, + VisualNode, } from "chili-core"; import { Expander, div, label, localize } from "../components"; import { MatrixConverter } from "./matrixConverter"; @@ -73,19 +74,19 @@ export class PropertyView extends HTMLElement { } private addGeometry(nodes: INode[], document: IDocument) { - let geometries = nodes.filter((x) => x instanceof GeometryNode); + let geometries = nodes.filter((x) => x instanceof VisualNode); if (geometries.length === 0 || !this.isAllElementsOfTypeFirstElement(geometries)) return; this.addTransform(document, geometries); this.addParameters(geometries, document); } - private addTransform(document: IDocument, geometries: GeometryNode[]) { + private addTransform(document: IDocument, geometries: VisualNode[]) { let matrix = new Expander("common.matrix"); this.panel.append(matrix); const addMatrix = (display: I18nKeys, converter: IConverter) => { appendProperty(matrix, document, geometries, { - name: "matrix", + name: "transform", display: display, converter, }); @@ -97,7 +98,7 @@ export class PropertyView extends HTMLElement { addMatrix("transform.rotation", converters.rotate); } - private addParameters(geometries: GeometryNode[], document: IDocument) { + private addParameters(geometries: VisualNode[], document: IDocument) { let entities = geometries.filter((x) => x instanceof ParameterShapeNode); if (entities.length === 0 || !this.isAllElementsOfTypeFirstElement(entities)) return; let parameters = new Expander(entities[0].display()); diff --git a/packages/chili-ui/src/property/utils.ts b/packages/chili-ui/src/property/utils.ts index fff9cc7b..b8ca3598 100644 --- a/packages/chili-ui/src/property/utils.ts +++ b/packages/chili-ui/src/property/utils.ts @@ -1,20 +1,31 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IDocument, Property } from "chili-core"; -import { InputProperty } from "./input"; +import { GeometryNode, IDocument, Property } from "chili-core"; import { CheckProperty } from "./check"; import { ColorProperty } from "./colorProperty"; +import { InputProperty } from "./input"; import { MaterialProperty } from "./materialProperty"; export function appendProperty(container: HTMLElement, document: IDocument, objs: any[], prop?: Property) { - if (prop === undefined) return; + if (prop === undefined || objs.length === 0) return; + if (!(prop.name in objs[0])) { + alert(`Property ${prop.name} not found in ${Object.getPrototypeOf(objs[0]).constructor.name}`); + return; + } + const propValue = (objs[0] as unknown as any)[prop.name]; const type = typeof propValue; if (prop.type === "color") { container.append(new ColorProperty(document, objs, prop)); } else if (prop.type === "materialId") { - container.append(new MaterialProperty(document, objs, prop)); + container.append( + new MaterialProperty( + document, + objs.filter((x) => x instanceof GeometryNode), + 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-vis/src/index.ts b/packages/chili-vis/src/index.ts index d8a3e9d5..3dd63a16 100644 --- a/packages/chili-vis/src/index.ts +++ b/packages/chili-vis/src/index.ts @@ -1,5 +1,5 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -export * from "./modelSelectionEventHandler"; +export * from "./nodeSelectionEventHandler"; export * from "./selectionEventHandler"; export * from "./shapeSelectionEventHandler"; diff --git a/packages/chili-vis/src/modelSelectionEventHandler.ts b/packages/chili-vis/src/modelSelectionEventHandler.ts deleted file mode 100644 index 4627c5a4..00000000 --- a/packages/chili-vis/src/modelSelectionEventHandler.ts +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. - -import { - AsyncController, - GeometryNode, - IDocument, - IShapeFilter, - IView, - ShapeType, - VisualShapeData, -} from "chili-core"; -import { SelectionHandler } from "./selectionEventHandler"; - -export class ModelSelectionHandler extends SelectionHandler { - models(): GeometryNode[] { - return this.document.selection.getSelectedNodes().filter((x) => x instanceof GeometryNode); - } - - constructor( - document: IDocument, - multiMode: boolean, - controller?: AsyncController, - filter?: IShapeFilter, - ) { - super(document, ShapeType.Shape, multiMode, controller, filter); - } - - protected override select(view: IView, shapes: VisualShapeData[], event: PointerEvent): number { - if (shapes.length === 0) { - this.clearSelected(this.document); - return 0; - } - let models = shapes - .map((x) => view.document.visual.context.getModel(x.owner)) - .filter((x) => x !== undefined); - this.document.selection.setSelection(models, event.shiftKey); - return models.length; - } - - override clearSelected(document: IDocument): void { - document.selection.clearSelection(); - } -} diff --git a/packages/chili-vis/src/nodeSelectionEventHandler.ts b/packages/chili-vis/src/nodeSelectionEventHandler.ts new file mode 100644 index 00000000..7e2b96e1 --- /dev/null +++ b/packages/chili-vis/src/nodeSelectionEventHandler.ts @@ -0,0 +1,131 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { + AsyncController, + GeometryNode, + IDocument, + INodeFilter, + IView, + IVisualObject, + ShapeType, + VisualNode, + VisualState, +} from "chili-core"; +import { SelectionHandler } from "./selectionEventHandler"; + +export class NodeSelectionHandler extends SelectionHandler { + private _highlights: IVisualObject[] | undefined; + private _detectAtMouse: IVisualObject[] | undefined; + private _lockDetected: IVisualObject | undefined; // 用于切换捕获的对象 + + nodes(): VisualNode[] { + return this.document.selection.getSelectedNodes() as VisualNode[]; + } + + constructor( + document: IDocument, + multiMode: boolean, + controller?: AsyncController, + readonly filter?: INodeFilter, + ) { + super(document, multiMode, controller); + } + + protected override select(view: IView, event: PointerEvent): number { + if (!this._highlights || this._highlights.length === 0) { + this.clearSelected(this.document); + return 0; + } + let models = this._highlights + .map((x) => view.document.visual.context.getNode(x)) + .filter((x) => x !== undefined); + this.document.selection.setSelection(models, event.shiftKey); + return models.length; + } + + override pointerMove(view: IView, event: PointerEvent): void { + if (event.buttons === 4) { + return; + } + this._detectAtMouse = undefined; + if (this.rect) { + this.updateRect(this.rect, event); + } + let detecteds = this.getDetecteds(view, event); + this.setHighlight(view, detecteds); + } + + getDetecteds(view: IView, event: PointerEvent) { + let detecteds: IVisualObject[] = []; + if (this.rect && this.mouse.x !== event.offsetX && this.mouse.y !== event.offsetY) { + detecteds = view.detectVisualRect( + this.mouse.x, + this.mouse.y, + event.offsetX, + event.offsetY, + this.filter, + ); + } else { + this._detectAtMouse = view.detectVisual(event.offsetX, event.offsetY, this.filter); + let detected = this.getDetecting(); + if (detected) detecteds.push(detected); + } + return detecteds; + } + + private getDetecting() { + if (this._detectAtMouse) { + let index = 0; + if (this._lockDetected) { + let loked = this.getDetcedtingIndex(); + if (loked >= 0) index = loked; + } + return this._detectAtMouse[index]; + } + return undefined; + } + + private getDetcedtingIndex() { + if (!this._detectAtMouse) return -1; + for (let i = 0; i < this._detectAtMouse.length; i++) { + if (this._lockDetected === this._detectAtMouse[i]) { + return i; + } + } + return -1; + } + + private setHighlight(view: IView, detecteds: IVisualObject[]) { + this.cleanHighlights(); + detecteds.forEach((x) => { + view.document.visual.highlighter.addState(x, VisualState.highlighter, ShapeType.Shape); + }); + this._highlights = detecteds; + view.update(); + } + + protected override cleanHighlights(): void { + this._highlights?.forEach((x) => { + this.document.visual.highlighter.removeState(x, VisualState.highlighter, ShapeType.Shape); + }); + this._highlights = undefined; + } + + protected override highlightNext(view: IView): void { + if (this._detectAtMouse && this._detectAtMouse.length > 1) { + let index = 1; + if (this._lockDetected) { + let detecting = this.getDetcedtingIndex(); + if (detecting !== -1) + index = detecting === this._detectAtMouse.length - 1 ? 0 : detecting + 1; + } + this._lockDetected = this._detectAtMouse[index]; + let detected = this.getDetecting(); + if (detected) this.setHighlight(view, [detected]); + } + } + + override clearSelected(document: IDocument): void { + document.selection.clearSelection(); + } +} diff --git a/packages/chili-vis/src/selectionEventHandler.ts b/packages/chili-vis/src/selectionEventHandler.ts index 810eaec8..399af43e 100644 --- a/packages/chili-vis/src/selectionEventHandler.ts +++ b/packages/chili-vis/src/selectionEventHandler.ts @@ -31,18 +31,13 @@ interface SelectionRect { } export abstract class SelectionHandler implements IEventHandler { - private rect?: SelectionRect; - private mouse = { isDown: false, x: 0, y: 0 }; - protected _highlights: VisualShapeData[] | undefined; - private _detectAtMouse: VisualShapeData[] | undefined; - private _lockDetected: IShape | undefined; // 用于切换捕获的对象 + protected rect?: SelectionRect; + protected mouse = { isDown: false, x: 0, y: 0 }; constructor( readonly document: IDocument, - readonly shapeType: ShapeType, readonly multiMode: boolean, readonly controller?: AsyncController, - readonly filter?: IShapeFilter, ) { controller?.onCancelled((s) => { this.clearSelected(document); @@ -52,64 +47,15 @@ export abstract class SelectionHandler implements IEventHandler { dispose() {} - pointerMove(view: IView, event: PointerEvent): void { - if (event.buttons === 4) { - return; - } - this._detectAtMouse = undefined; - if (this.rect) { - this.updateRect(this.rect, event); - } - let detecteds = this.getDetecteds(view, event); - this.setHighlight(view, detecteds); - } + abstract pointerMove(view: IView, event: PointerEvent): void; - private getDetecteds(view: IView, event: PointerEvent) { - let detecteds: VisualShapeData[] = []; - if (this.rect && this.mouse.x !== event.offsetX && this.mouse.y !== event.offsetY) { - detecteds = detecteds.concat( - view.rectDetected( - this.shapeType, - this.mouse.x, - this.mouse.y, - event.offsetX, - event.offsetY, - this.filter, - ), - ); - } else { - this._detectAtMouse = view.detected(this.shapeType, event.offsetX, event.offsetY, this.filter); - let detected = this.getDetecting(); - if (detected) detecteds.push(detected); - } - return detecteds; - } + protected abstract cleanHighlights(): void; - private getDetecting() { - if (this._detectAtMouse) { - let index = 0; - if (this._lockDetected) { - let loked = this.getDetcedtingIndex(); - if (loked >= 0) index = loked; - } - return this._detectAtMouse[index]; - } - return undefined; - } + protected abstract clearSelected(document: IDocument): void; - protected setHighlight(view: IView, detecteds: VisualShapeData[]) { - this.cleanHighlights(); - detecteds.forEach((x) => { - view.document.visual.highlighter.addState( - x.owner, - VisualState.highlighter, - this.shapeType, - ...x.indexes, - ); - }); - this._highlights = detecteds; - view.update(); - } + protected abstract select(view: IView, event: PointerEvent): number; + + protected abstract highlightNext(view: IView): void; pointerDown(view: IView, event: PointerEvent): void { event.preventDefault(); @@ -134,7 +80,7 @@ export abstract class SelectionHandler implements IEventHandler { }; } - private updateRect(rect: SelectionRect, event: PointerEvent) { + protected updateRect(rect: SelectionRect, event: PointerEvent) { rect.element.style.display = "block"; const x1 = Math.min(rect.clientX, event.clientX); const y1 = Math.min(rect.clientY, event.clientY); @@ -157,17 +103,13 @@ export abstract class SelectionHandler implements IEventHandler { if (this.mouse.isDown && event.button === 0) { this.mouse.isDown = false; this.removeRect(view); - let count = this.select(view, this._highlights ?? [], event); + let count = this.select(view, event); this.cleanHighlights(); view.update(); if (count > 0 && !this.multiMode) this.controller?.success(); } } - protected abstract clearSelected(document: IDocument): void; - - protected abstract select(view: IView, shapes: VisualShapeData[], event: PointerEvent): number; - private removeRect(view: IView) { if (this.rect) { this.rect.element.remove(); @@ -175,18 +117,6 @@ export abstract class SelectionHandler implements IEventHandler { } } - protected cleanHighlights() { - this._highlights?.forEach((x) => { - x.owner.geometryNode.document.visual.highlighter.removeState( - x.owner, - VisualState.highlighter, - this.shapeType, - ...x.indexes, - ); - }); - this._highlights = undefined; - } - keyDown(view: IView, event: KeyboardEvent): void { if (event.key === "Escape") { if (this.controller) { @@ -203,8 +133,86 @@ export abstract class SelectionHandler implements IEventHandler { this.highlightNext(view); } } +} + +export abstract class ShapeSelectionHandler extends SelectionHandler { + protected _highlights: VisualShapeData[] | undefined; + private _detectAtMouse: VisualShapeData[] | undefined; + private _lockDetected: IShape | undefined; // 用于切换捕获的对象 + + constructor( + document: IDocument, + readonly shapeType: ShapeType, + multiMode: boolean, + controller?: AsyncController, + readonly filter?: IShapeFilter, + ) { + super(document, multiMode, controller); + } + + pointerMove(view: IView, event: PointerEvent): void { + if (event.buttons === 4) { + return; + } + this._detectAtMouse = undefined; + if (this.rect) { + this.updateRect(this.rect, event); + } + let detecteds = this.getDetecteds(view, event); + this.setHighlight(view, detecteds); + } + + private getDetecteds(view: IView, event: PointerEvent) { + let detecteds: VisualShapeData[] = []; + if (this.rect && this.mouse.x !== event.offsetX && this.mouse.y !== event.offsetY) { + detecteds = view.detectShapesRect( + this.shapeType, + this.mouse.x, + this.mouse.y, + event.offsetX, + event.offsetY, + this.filter, + ); + } else { + this._detectAtMouse = view.detectShapes( + this.shapeType, + event.offsetX, + event.offsetY, + this.filter, + ); + let detected = this.getDetecting(); + if (detected) detecteds.push(detected); + } + return detecteds; + } - private highlightNext(view: IView) { + protected setHighlight(view: IView, detecteds: VisualShapeData[]) { + this.cleanHighlights(); + detecteds.forEach((x) => { + view.document.visual.highlighter.addState( + x.owner, + VisualState.highlighter, + this.shapeType, + ...x.indexes, + ); + }); + this._highlights = detecteds; + view.update(); + } + + protected cleanHighlights() { + this._highlights?.forEach((x) => { + x.owner.geometryNode.document.visual.highlighter.removeState( + x.owner, + VisualState.highlighter, + this.shapeType, + ...x.indexes, + ); + }); + this._highlights = undefined; + } + + protected highlightNext(view: IView) { if (this._detectAtMouse && this._detectAtMouse.length > 1) { let index = 1; if (this._lockDetected) { @@ -218,6 +226,18 @@ export abstract class SelectionHandler implements IEventHandler { } } + private getDetecting() { + if (this._detectAtMouse) { + let index = 0; + if (this._lockDetected) { + let loked = this.getDetcedtingIndex(); + if (loked >= 0) index = loked; + } + return this._detectAtMouse[index]; + } + return undefined; + } + private getDetcedtingIndex() { if (!this._detectAtMouse) return -1; for (let i = 0; i < this._detectAtMouse.length; i++) { diff --git a/packages/chili-vis/src/shapeSelectionEventHandler.ts b/packages/chili-vis/src/shapeSelectionEventHandler.ts index 12208aeb..ad3c44db 100644 --- a/packages/chili-vis/src/shapeSelectionEventHandler.ts +++ b/packages/chili-vis/src/shapeSelectionEventHandler.ts @@ -1,9 +1,9 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDocument, IView, VisualShapeData, VisualState } from "chili-core"; -import { SelectionHandler } from "./selectionEventHandler"; +import { ShapeSelectionHandler } from "./selectionEventHandler"; -export class ShapeSelectionHandler extends SelectionHandler { +export class SubshapeSelectionHandler extends ShapeSelectionHandler { private _shapes: Set = new Set(); shapes(): VisualShapeData[] { @@ -23,9 +23,9 @@ export class ShapeSelectionHandler extends SelectionHandler { this._shapes.clear(); } - protected override select(view: IView, shapes: VisualShapeData[], event: PointerEvent): number { + protected override select(view: IView, event: PointerEvent): number { if (event.shiftKey) { - shapes.forEach((x) => { + this._highlights?.forEach((x) => { if (this._shapes.has(x)) { this.removeSelected(x); } else { @@ -34,7 +34,7 @@ export class ShapeSelectionHandler extends SelectionHandler { }); } else { this.clearSelected(view.document.visual.document); - shapes.forEach((x) => { + this._highlights?.forEach((x) => { this.addSelected(x); }); } diff --git a/packages/chili/src/commands/boolean.ts b/packages/chili/src/commands/boolean.ts index db35076a..07e00f66 100644 --- a/packages/chili/src/commands/boolean.ts +++ b/packages/chili/src/commands/boolean.ts @@ -2,13 +2,13 @@ import { GeometryNode, IShape, Result, ShapeNode, command } from "chili-core"; import { BooleanNode } from "../bodys/boolean"; -import { IStep, SelectModelStep } from "../step"; +import { IStep, SelectNodeStep } from "../step"; import { CreateCommand } from "./createCommand"; export abstract class BooleanOperate extends CreateCommand { protected override geometryNode(): GeometryNode { - let shape1 = (this.stepDatas[0].models?.at(0) as ShapeNode)?.shape.value; - let shape2 = (this.stepDatas[1].models?.at(0) as ShapeNode)?.shape.value; + let shape1 = (this.stepDatas[0].nodes?.at(0) as ShapeNode)?.shape.value; + let shape2 = (this.stepDatas[1].nodes?.at(0) as ShapeNode)?.shape.value; let booleanType = this.getBooleanOperateType(); let booleanShape: Result; if (booleanType === "common") { @@ -21,7 +21,7 @@ export abstract class BooleanOperate extends CreateCommand { let node = new BooleanNode(this.document, booleanShape.value); this.stepDatas.forEach((x) => { - x.models?.at(0)?.parent?.remove(x.models[0]); + x.nodes?.at(0)?.parent?.remove(x.nodes[0]); }); return node; } @@ -30,10 +30,10 @@ export abstract class BooleanOperate extends CreateCommand { protected override getSteps(): IStep[] { return [ - new SelectModelStep("prompt.select.shape", false), - new SelectModelStep("prompt.select.shape", false, { + new SelectNodeStep("prompt.select.shape", false), + new SelectNodeStep("prompt.select.shape", false, { allow: (shape) => { - return !this.stepDatas[0].models + return !this.stepDatas[0].nodes ?.map((x) => (x as ShapeNode).shape.value) .includes(shape); }, diff --git a/packages/chili/src/commands/create/converter.ts b/packages/chili/src/commands/create/converter.ts index ba829aad..14e9bbe1 100644 --- a/packages/chili/src/commands/create/converter.ts +++ b/packages/chili/src/commands/create/converter.ts @@ -17,7 +17,7 @@ import { } from "chili-core"; import { FaceNode } from "../../bodys/face"; import { WireNode } from "../../bodys/wire"; -import { SelectModelStep } from "../../step"; +import { SelectNodeStep } from "../../step"; abstract class ConvertCommand extends CancelableCommand { async executeAsync(): Promise { @@ -49,11 +49,11 @@ abstract class ConvertCommand extends CancelableCommand { let models = this._getSelectedModels(document, filter); document.selection.clearSelection(); if (models.length > 0) return models; - let step = new SelectModelStep("prompt.select.models", true, filter); + let step = new SelectNodeStep("prompt.select.models", true, filter); this.controller = new AsyncController(); let data = await step.execute(document, this.controller); document.selection.clearSelection(); - return data?.models; + return data?.nodes; } private _getSelectedModels(document: IDocument, filter?: IShapeFilter) { diff --git a/packages/chili/src/commands/importExport.ts b/packages/chili/src/commands/importExport.ts index b6b32084..1f900cd3 100644 --- a/packages/chili/src/commands/importExport.ts +++ b/packages/chili/src/commands/importExport.ts @@ -9,16 +9,20 @@ import { ICommand, IDocument, IShape, + Mesh, + MeshNode, NodeLinkedList, PubSub, Result, ShapeNode, Transaction, + VisualNode, command, download, readFilesAsync, } from "chili-core"; -import { SelectModelStep } from "../step"; +import { STLLoader } from "three/examples/jsm/loaders/STLLoader"; +import { SelectNodeStep } from "../step"; let count = 1; @@ -127,16 +131,16 @@ abstract class Export implements ICommand { private async selectModelsAsync(application: IApplication) { let models = application.activeView?.document.selection .getSelectedNodes() - .filter((x) => x instanceof GeometryNode); + .filter((x) => x instanceof VisualNode); if (models?.length === 0) { let controller = new AsyncController(); - let step = new SelectModelStep("prompt.select.models", true); + let step = new SelectNodeStep("prompt.select.models", true); let data = await step.execute(application.activeView?.document!, controller); - if (!data?.models) { + if (!data?.nodes) { PubSub.default.pub("showToast", "prompt.select.noModelSelected"); return; } - models = data.models; + models = data.nodes; } return models; } diff --git a/packages/chili/src/commands/modify/array.ts b/packages/chili/src/commands/modify/array.ts index 8c929d95..61c00e6d 100644 --- a/packages/chili/src/commands/modify/array.ts +++ b/packages/chili/src/commands/modify/array.ts @@ -7,6 +7,7 @@ import { Matrix4, Transaction, VisualConfig, + VisualNode, XYZ, command, } from "chili-core"; @@ -20,7 +21,7 @@ import { MultistepCommand } from "../multistepCommand"; icon: "icon-array", }) export class Array extends MultistepCommand { - private models?: GeometryNode[]; + private models?: VisualNode[]; private positions?: number[]; getSteps(): IStep[] { @@ -63,14 +64,11 @@ export class Array extends MultistepCommand { this.models = this.document.selection.getSelectedNodes().filter((x) => x instanceof GeometryNode); if (this.models.length === 0) { this.controller = new AsyncController(); - this.models = await this.document.selection.pickModel("axis.x", this.controller, true); + this.models = await this.document.selection.pickNode("axis.x", this.controller, true); if (this.models.length === 0) return false; } this.positions = []; - this.models?.forEach((model) => { - let ps = model.mesh.edges?.positions; - if (ps) this.positions = this.positions!.concat(model.matrix.ofPoints(ps)); - }); + this.models?.forEach((model) => {}); return true; } @@ -79,7 +77,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.transform = x.transform.multiply(transform); }); this.document.visual.update(); }); diff --git a/packages/chili/src/commands/modify/break.ts b/packages/chili/src/commands/modify/break.ts index e724f1f8..0142e245 100644 --- a/packages/chili/src/commands/modify/break.ts +++ b/packages/chili/src/commands/modify/break.ts @@ -22,7 +22,7 @@ export class Break extends MultistepCommand { } let curve2 = curve.trim(parameter, curve.lastParameter()); curve.setTrim(curve.firstParameter(), parameter); - let model = this.document.visual.context.getModel(this.stepDatas[0].shapes[0].owner)!; + let model = this.document.visual.context.getNode(this.stepDatas[0].shapes[0].owner)!; model.parent?.remove(model); (this.stepDatas[0].shapes[0].shape as IEdge).update(curve); diff --git a/packages/chili/src/commands/modify/split.ts b/packages/chili/src/commands/modify/split.ts index 73211c3f..0ae27eff 100644 --- a/packages/chili/src/commands/modify/split.ts +++ b/packages/chili/src/commands/modify/split.ts @@ -28,13 +28,13 @@ export class Split extends MultistepCommand { protected override executeMainTask() { Transaction.excute(this.document, `excute ${Object.getPrototypeOf(this).data.name}`, () => { - let old = this.document.visual.context.getModel(this.stepDatas[0].shapes[0].owner)!; + let old = this.document.visual.context.getNode(this.stepDatas[0].shapes[0].owner)!; let shape = this.splitedShape(); if (old instanceof EditableShapeNode) { old.shape = Result.ok(shape); } else if (old instanceof GeometryNode) { let model = new EditableShapeNode(this.document, old.name, shape); - model.matrix = old.matrix; + model.transform = old.transform; this.removeModels( this.stepDatas[0].shapes[0].owner, ...this.stepDatas[1].shapes.map((x) => x.owner), @@ -47,7 +47,7 @@ export class Split extends MultistepCommand { private removeModels(...shapes: IVisualGeometry[]) { shapes.forEach((x) => { - let model = this.document.visual.context.getModel(x); + let model = this.document.visual.context.getNode(x); model?.parent?.remove(model); }); } diff --git a/packages/chili/src/commands/modify/transformedCommand.ts b/packages/chili/src/commands/modify/transformedCommand.ts index a2b90b47..19a1e57e 100644 --- a/packages/chili/src/commands/modify/transformedCommand.ts +++ b/packages/chili/src/commands/modify/transformedCommand.ts @@ -6,16 +6,18 @@ import { GeometryNode, LineType, Matrix4, + MeshNode, Property, PubSub, Transaction, VisualConfig, + VisualNode, XYZ, } from "chili-core"; import { MultistepCommand } from "../multistepCommand"; export abstract class TransformedCommand extends MultistepCommand { - protected models?: GeometryNode[]; + protected models?: VisualNode[]; protected positions?: number[]; @Property.define("common.clone") @@ -40,10 +42,10 @@ export abstract class TransformedCommand extends MultistepCommand { }; private async ensureSelectedModels() { - this.models = this.document.selection.getSelectedNodes().filter((x) => x instanceof GeometryNode); + this.models = this.document.selection.getSelectedNodes().filter((x) => x instanceof VisualNode); if (this.models.length > 0) return true; this.controller = new AsyncController(); - this.models = await this.document.selection.pickModel("prompt.select.models", this.controller, true); + this.models = await this.document.selection.pickNode("prompt.select.models", this.controller, true); if (this.models.length > 0) return true; if (this.controller.result?.status === "success") { PubSub.default.pub("showToast", "toast.select.noSelected"); @@ -56,8 +58,13 @@ export abstract class TransformedCommand extends MultistepCommand { this.positions = []; this.models!.forEach((model) => { - let ps = model.mesh.edges?.positions; - if (ps) this.positions = this.positions!.concat(model.matrix.ofPoints(ps)); + if (model instanceof MeshNode) { + let ps = model.mesh.position; + if (ps) this.positions = this.positions!.concat(model.transform.ofPoints(ps)); + } else if (model instanceof GeometryNode) { + let ps = model.mesh.edges?.positions; + if (ps) this.positions = this.positions!.concat(model.transform.ofPoints(ps)); + } }); return true; } @@ -74,7 +81,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.transform = x.transform.multiply(transform); }); this.document.visual.update(); }); diff --git a/packages/chili/src/commands/modify/trim.ts b/packages/chili/src/commands/modify/trim.ts index 116128f1..683ad6b7 100644 --- a/packages/chili/src/commands/modify/trim.ts +++ b/packages/chili/src/commands/modify/trim.ts @@ -13,6 +13,7 @@ import { IShapeFilter, ITrimmedCurve, IView, + IVisualGeometry, ShapeNode, ShapeType, Transaction, @@ -21,7 +22,7 @@ import { command, } from "chili-core"; import { GeoUtils } from "chili-geo"; -import { SelectionHandler } from "chili-vis"; +import { ShapeSelectionHandler } from "chili-vis"; @command({ name: "modify.trim", @@ -66,7 +67,7 @@ export class Trim extends CancelableCommand { } private trimEdge(selected: TrimEdge) { - let model = this.document.visual.context.getModel(selected.edge.owner); + let model = this.document.visual.context.getNode(selected.edge.owner); let materialId = (model as GeometryNode)?.materialId; selected.segments.retainSegments.forEach((segment) => { let newEdge = selected.curve.trim(segment.start, segment.end).makeEdge(); @@ -98,7 +99,7 @@ interface TrimEdge { }; } -export class PickTrimEdgeEventHandler extends SelectionHandler { +export class PickTrimEdgeEventHandler extends ShapeSelectionHandler { selected: TrimEdge | undefined; private highlightedEdge: number | undefined; private highlight: undefined | TrimEdge; @@ -140,7 +141,7 @@ export class PickTrimEdgeEventHandler extends SelectionHandler { this.selected = undefined; } - protected override select(view: IView, shapes: VisualShapeData[], event: PointerEvent): number { + protected override select(view: IView, event: PointerEvent): number { this.selected = this.highlight; return this.selected ? 1 : 0; } @@ -150,12 +151,8 @@ function findEdges(detecteds: VisualShapeData[], view: IView) { let boundingBox = detecteds[0].owner.boundingBox(); let otherEdges = view.document.visual.context .boundingBoxIntersectFilter(boundingBox, new EdgeFilter()) - .filter( - (d) => - d.geometryNode instanceof ShapeNode && - d.geometryNode.shape.value.id !== detecteds[0].shape.id, - ) - .map((x) => (x.geometryNode as ShapeNode).shape.value as IEdge); + .map((x) => ((x as IVisualGeometry)?.geometryNode as ShapeNode)?.shape.value as IEdge) + .filter((x) => x !== undefined && x.id !== detecteds[0].shape.id); return otherEdges; } diff --git a/packages/chili/src/document.ts b/packages/chili/src/document.ts index f99f6e78..e2ecb51c 100644 --- a/packages/chili/src/document.ts +++ b/packages/chili/src/document.ts @@ -28,7 +28,7 @@ import { } from "chili-core"; import { Selection } from "./selection"; -const FILE_VERSIOM = "0.4.0"; +const FILE_VERSIOM = "0.4.1"; export class Document extends Observable implements IDocument { readonly visual: IVisual; @@ -38,6 +38,8 @@ export class Document extends Observable implements IDocument { private readonly _nodeChangedObservers = new Set(); + static readonly version = FILE_VERSIOM; + get name(): string { return this.getPrivateValue("name"); } diff --git a/packages/chili/src/editEventHandler.ts b/packages/chili/src/editEventHandler.ts index cee9eea7..f09bd77a 100644 --- a/packages/chili/src/editEventHandler.ts +++ b/packages/chili/src/editEventHandler.ts @@ -1,15 +1,15 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { IDocument, INode, PubSub } from "chili-core"; -import { ModelSelectionHandler } from "chili-vis"; +import { NodeSelectionHandler } from "chili-vis"; -export class EditEventHandler extends ModelSelectionHandler { +export class EditEventHandler extends NodeSelectionHandler { constructor( document: IDocument, - readonly nodes: INode[], + readonly selectedNodes: INode[], ) { super(document, true); - PubSub.default.pub("showProperties", document, nodes); + PubSub.default.pub("showProperties", document, selectedNodes); } override dispose() { diff --git a/packages/chili/src/selection.ts b/packages/chili/src/selection.ts index 314c52ac..b26a17d1 100644 --- a/packages/chili/src/selection.ts +++ b/packages/chili/src/selection.ts @@ -3,53 +3,51 @@ import { AsyncController, CursorType, - GeometryNode, I18nKeys, IDisposable, IDocument, IEventHandler, INode, + INodeFilter, ISelection, IShapeFilter, Logger, PubSub, ShapeNode, ShapeType, + VisualNode, VisualState, } from "chili-core"; -import { ModelSelectionHandler, ShapeSelectionHandler } from "chili-vis"; +import { NodeSelectionHandler, SubshapeSelectionHandler } from "chili-vis"; export class Selection implements ISelection, IDisposable { private _selectedNodes: INode[] = []; private _unselectedNodes: INode[] = []; shapeType: ShapeType = ShapeType.Shape; - nodeType: "model" | "node" = "node"; - filter?: IShapeFilter; + shapeFilter?: IShapeFilter; + nodeFilter?: INodeFilter; constructor(readonly document: IDocument) {} async pickShape(prompt: I18nKeys, controller: AsyncController, multiMode: boolean) { - let handler = new ShapeSelectionHandler( + let handler = new SubshapeSelectionHandler( this.document, this.shapeType, multiMode, controller, - this.filter, + this.shapeFilter, ); await this.pickAsync(handler, prompt, controller, multiMode); return handler.shapes(); } - async pickModel(prompt: I18nKeys, controller: AsyncController, multiMode: boolean) { - let oldNodeType = this.nodeType; + async pickNode(prompt: I18nKeys, controller: AsyncController, multiMode: boolean) { try { - this.nodeType = "model"; - let handler = new ModelSelectionHandler(this.document, multiMode, controller, this.filter); + let handler = new NodeSelectionHandler(this.document, multiMode, controller, this.nodeFilter); await this.pickAsync(handler, prompt, controller, multiMode); - return handler.models(); + return handler.nodes(); } finally { - this.nodeType = oldNodeType; } } @@ -88,7 +86,7 @@ export class Selection implements ISelection, IDisposable { } setSelection(nodes: INode[], toggle: boolean) { - nodes = this.nodeType === "node" ? nodes : nodes.filter(this.nodeFilter); + nodes = nodes.filter(this.shapeNodeFilter); if (toggle) { this.toggleSelectPublish(nodes, true); } else { @@ -98,14 +96,18 @@ export class Selection implements ISelection, IDisposable { return this._selectedNodes.length; } - private readonly nodeFilter = (x: INode) => { + private readonly shapeNodeFilter = (x: INode) => { if (x instanceof ShapeNode) { let shape = x.shape.value; - if (!shape || !this.filter) return true; - return this.filter.allow(shape); + if (!shape || !this.shapeFilter) return true; + return this.shapeFilter.allow(shape); } - return false; + if (this.nodeFilter) { + return this.nodeFilter.allow(x); + } + + return true; }; deselect(nodes: INode[]) { @@ -130,8 +132,8 @@ export class Selection implements ISelection, IDisposable { private addSelectPublish(nodes: INode[], publish: boolean) { nodes.forEach((m) => { - if (m instanceof GeometryNode) { - let visual = this.document.visual.context.getShape(m); + if (m instanceof VisualNode) { + let visual = this.document.visual.context.getVisual(m); if (visual) this.document.visual.highlighter.addState(visual, VisualState.selected, ShapeType.Shape); } @@ -142,8 +144,8 @@ export class Selection implements ISelection, IDisposable { private removeSelectedPublish(nodes: INode[], publish: boolean) { for (const node of nodes) { - if (node instanceof GeometryNode) { - let visual = this.document.visual.context.getShape(node); + if (node instanceof VisualNode) { + let visual = this.document.visual.context.getVisual(node); if (visual) this.document.visual.highlighter.removeState( visual, diff --git a/packages/chili/src/snap/snap.ts b/packages/chili/src/snap/snap.ts index 56eef881..a00aad60 100644 --- a/packages/chili/src/snap/snap.ts +++ b/packages/chili/src/snap/snap.ts @@ -1,14 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { - GeometryNode, - IDocument, - IShapeFilter, - IView, - ShapeMeshData, - VisualShapeData, - XYZ, -} from "chili-core"; +import { IDocument, IShapeFilter, IView, ShapeMeshData, VisualNode, VisualShapeData, XYZ } from "chili-core"; export type SnapValidator = (point: XYZ) => boolean; export type SnapPreviewer = (point: XYZ | undefined) => ShapeMeshData[]; @@ -32,7 +24,7 @@ export interface SnapedData { distance?: number; refPoint?: XYZ; shapes: VisualShapeData[]; - models?: GeometryNode[]; + nodes?: VisualNode[]; } export interface MouseAndDetected { diff --git a/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts b/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts index f6d8e989..ffe75296 100644 --- a/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts +++ b/packages/chili/src/snap/snapEventHandler/snapEventHandler.ts @@ -148,7 +148,7 @@ export abstract class SnapEventHandler implements } private findDetecteds(shapeType: ShapeType, view: IView, event: MouseEvent): MouseAndDetected { - let shapes = view.detected(shapeType, event.offsetX, event.offsetY, this.data.filter); + let shapes = view.detectShapes(shapeType, event.offsetX, event.offsetY, this.data.filter); return { shapes, view, diff --git a/packages/chili/src/step/selectStep.ts b/packages/chili/src/step/selectStep.ts index 073105ed..c6c2f85d 100644 --- a/packages/chili/src/step/selectStep.ts +++ b/packages/chili/src/step/selectStep.ts @@ -14,14 +14,14 @@ export abstract class SelectStep implements IStep { async execute(document: IDocument, controller: AsyncController): Promise { let oldShapeType = document.selection.shapeType; - let oldFilter = document.selection.filter; + let oldFilter = document.selection.shapeFilter; try { document.selection.shapeType = this.snapeType; - document.selection.filter = this.filter; + document.selection.shapeFilter = this.filter; return await this.select(document, controller); } finally { document.selection.shapeType = oldShapeType; - document.selection.filter = oldFilter; + document.selection.shapeFilter = oldFilter; } } @@ -42,7 +42,7 @@ export class SelectShapeStep extends SelectStep { } } -export class SelectModelStep extends SelectStep { +export class SelectNodeStep extends SelectStep { constructor(prompt: I18nKeys, multiple: boolean, filter?: IShapeFilter) { super(ShapeType.Shape, prompt, multiple, filter); } @@ -51,12 +51,12 @@ export class SelectModelStep extends SelectStep { document: IDocument, controller: AsyncController, ): Promise { - let models = await document.selection.pickModel(this.prompt, controller, this.multiple); - if (models.length === 0) return undefined; + let nodes = await document.selection.pickNode(this.prompt, controller, this.multiple); + if (nodes.length === 0) return undefined; return { view: document.application.activeView!, shapes: [], - models, + nodes: nodes, }; } }