From 18104dbe36ccbf6b7492b0c26411e48cff4ba45a Mon Sep 17 00:00:00 2001 From: xiange Date: Tue, 4 Jun 2024 20:10:54 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=A6=84=20refactor:=20refactor=20IHighligh?= =?UTF-8?q?ter=20and=20ThreeGeometry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/chili-core/src/visual/highlighter.ts | 12 +- packages/chili-core/src/visual/visualShape.ts | 4 - packages/chili-core/test/visual.test.ts | 11 + packages/chili-geo/src/index.ts | 1 + packages/chili-geo/src/mesh.ts | 34 +++ packages/chili-three/src/threeGeometry.ts | 218 ++-------------- packages/chili-three/src/threeHighlighter.ts | 245 ++++++++++++++++-- packages/chili-three/src/threeView.ts | 3 +- .../chili-three/src/threeVisualContext.ts | 2 +- .../chili-vis/src/selectionEventHandler.ts | 20 +- .../src/shapeSelectionEventHandler.ts | 21 +- packages/chili/src/commands/createCommand.ts | 1 - packages/chili/src/commands/modify/split.ts | 3 +- .../chili/src/commands/multistepCommand.ts | 19 +- packages/chili/src/selection.ts | 22 +- packages/chili/src/snap/objectSnap.ts | 8 +- 16 files changed, 351 insertions(+), 273 deletions(-) create mode 100644 packages/chili-geo/src/mesh.ts diff --git a/packages/chili-core/src/visual/highlighter.ts b/packages/chili-core/src/visual/highlighter.ts index e9d2668a..eb10fb3a 100644 --- a/packages/chili-core/src/visual/highlighter.ts +++ b/packages/chili-core/src/visual/highlighter.ts @@ -4,13 +4,9 @@ import { ShapeType } from "../shape"; import { IVisualGeometry, VisualState } from "./visualShape"; export interface IHighlighter { + getState(shape: IVisualGeometry, type: ShapeType, index?: number): VisualState | undefined; clear(): void; - removeAllStates(shape: IVisualGeometry, resetState: boolean): void; - updateStateData( - shape: IVisualGeometry, - mode: "add" | "remove", - state: VisualState, - type: ShapeType, - index?: number, - ): VisualState; + resetState(shape: IVisualGeometry): void; + addState(shape: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]): void; + removeState(shape: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]): void; } diff --git a/packages/chili-core/src/visual/visualShape.ts b/packages/chili-core/src/visual/visualShape.ts index 1bed6866..51c98e83 100644 --- a/packages/chili-core/src/visual/visualShape.ts +++ b/packages/chili-core/src/visual/visualShape.ts @@ -1,7 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. import { GeometryEntity } from "../model"; -import { ShapeType } from "../shape"; import { IVisualObject } from "./visualObject"; export enum VisualState { @@ -32,7 +31,4 @@ export interface VisualGroup { export interface IVisualGeometry extends IVisualObject { get geometryEngity(): GeometryEntity; - addState(state: VisualState, type: ShapeType, ...indexes: number[]): void; - removeState(state: VisualState, type: ShapeType, ...indexes: number[]): void; - resetState(): void; } diff --git a/packages/chili-core/test/visual.test.ts b/packages/chili-core/test/visual.test.ts index 4f828b8a..7671b6b0 100644 --- a/packages/chili-core/test/visual.test.ts +++ b/packages/chili-core/test/visual.test.ts @@ -26,5 +26,16 @@ describe("visual test", () => { expect(state).toBe(0); expect(VisualState.hasState(state, VisualState.highlight)).toBeFalsy(); expect(VisualState.hasState(state, VisualState.selected)).toBeFalsy(); + + state = VisualState.highlight; + state = VisualState.addState(state, VisualState.selected); + expect(state).toBe(3); + expect(VisualState.hasState(state, VisualState.highlight)).toBeTruthy(); + expect(VisualState.hasState(state, VisualState.selected)).toBeTruthy(); + + state = VisualState.removeState(state, VisualState.highlight); + expect(state).toBe(2); + expect(VisualState.hasState(state, VisualState.highlight)).toBeFalsy(); + expect(VisualState.hasState(state, VisualState.selected)).toBeTruthy(); }); }); diff --git a/packages/chili-geo/src/index.ts b/packages/chili-geo/src/index.ts index 4feb8329..61951ac4 100644 --- a/packages/chili-geo/src/index.ts +++ b/packages/chili-geo/src/index.ts @@ -1,3 +1,4 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. +export * from "./mesh"; export * from "./utils"; diff --git a/packages/chili-geo/src/mesh.ts b/packages/chili-geo/src/mesh.ts new file mode 100644 index 00000000..b34253b6 --- /dev/null +++ b/packages/chili-geo/src/mesh.ts @@ -0,0 +1,34 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +import { EdgeMeshData, FaceMeshData, MathUtils } from "chili-core"; + +export class MeshUtils { + static subFace(mesh: FaceMeshData, index: number) { + let group = mesh?.groups[index]; + if (!group) return undefined; + + let indices = mesh.indices.slice(group.start, group.start + group.count); + let minMax = MathUtils.minMax(indices)!; + let [indiceStart, indiceEnd] = [minMax.min, minMax.max + 1]; + + let positions = mesh.positions.slice(indiceStart * 3, indiceEnd * 3); + let normals = mesh.normals.slice(indiceStart * 3, indiceEnd * 3); + indices = indices.map((i) => i - indiceStart); + + return { + positions, + normals, + indices, + }; + } + + static subEdge(mesh: EdgeMeshData, index: number) { + let group = mesh?.groups[index]; + if (!group) return undefined; + + let positions = mesh.positions.slice(group.start * 3, (group.start + group.count) * 3); + return { + positions, + }; + } +} diff --git a/packages/chili-three/src/threeGeometry.ts b/packages/chili-three/src/threeGeometry.ts index 3468ad66..70534c5f 100644 --- a/packages/chili-three/src/threeGeometry.ts +++ b/packages/chili-three/src/threeGeometry.ts @@ -4,19 +4,12 @@ import { EdgeMeshData, FaceMeshData, GeometryEntity, - IHighlighter, IVisualGeometry, - MathUtils, Matrix4, ShapeMeshData, - ShapeType, - VisualConfig, - VisualState, } from "chili-core"; import { - AlwaysDepth, BufferGeometry, - DoubleSide, Float32BufferAttribute, LineBasicMaterial, LineSegments, @@ -27,52 +20,11 @@ import { Color as ThreeColor, } from "three"; +import { MeshUtils } from "chili-geo"; import { ThreeHelper } from "./threeHelper"; import { ThreeVisualContext } from "./threeVisualContext"; -const hilightEdgeMaterial = new LineBasicMaterial({ - color: ThreeHelper.fromColor(VisualConfig.highlightEdgeColor), - linewidth: 2, - polygonOffset: true, - polygonOffsetFactor: -1, - polygonOffsetUnits: -1, - depthFunc: AlwaysDepth, -}); - -const selectedEdgeMaterial = new LineBasicMaterial({ - color: ThreeHelper.fromColor(VisualConfig.selectedEdgeColor), - linewidth: 2, - polygonOffset: true, - polygonOffsetFactor: -1, - polygonOffsetUnits: -1, - depthFunc: AlwaysDepth, -}); - -const highlightFaceMaterial = new MeshLambertMaterial({ - color: ThreeHelper.fromColor(VisualConfig.highlightFaceColor), - side: DoubleSide, - transparent: true, - opacity: 0.85, - depthFunc: AlwaysDepth, - polygonOffset: true, - polygonOffsetFactor: 1, - polygonOffsetUnits: 1, -}); - -const selectedFaceMaterial = new MeshLambertMaterial({ - color: ThreeHelper.fromColor(VisualConfig.selectedFaceColor), - side: DoubleSide, - transparent: true, - opacity: 0.32, - polygonOffset: true, - polygonOffsetFactor: 1, - polygonOffsetUnits: 1, -}); - export class ThreeGeometry extends Object3D implements IVisualGeometry { - private readonly _highlightedFaces: Map = new Map(); - private readonly _highlightedEdges: Map = new Map(); - private _faceMaterial: Material; private _edgeMaterial = new LineBasicMaterial(); private _edges?: LineSegments; @@ -83,7 +35,7 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { return this._edgeMaterial; } - setFaceMaterial(material: Material) { + changeFaceMaterial(material: Material) { if (this._faces) { this._faceMaterial = material; this._faces.material = material; @@ -100,7 +52,6 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { constructor( readonly geometryEngity: GeometryEntity, - readonly highlighter: IHighlighter, readonly context: ThreeVisualContext, ) { super(); @@ -119,8 +70,8 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { if (property === "matrix") { this.transform = this.geometryEngity.matrix; } else if (property === "materialId") { - let material = this.context.getMaterial(this.geometryEngity.materialId)!; - this.setFaceMaterial(material); + let material = this.context.getMaterial(this.geometryEngity.materialId); + this.changeFaceMaterial(material); } else if (property === "shape") { this.removeSubShapes(); this.generateShape(); @@ -137,7 +88,6 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { this.removeSubShapes(); this.geometryEngity.removePropertyChanged(this.handleGeometryPropertyChanged); this._edgeMaterial.dispose(); - this.resetState(); } private removeSubShapes() { @@ -187,159 +137,42 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { } } - addState(state: VisualState, type: ShapeType, ...indexes: number[]) { - this.removeOrAddState("add", state, type, ...indexes); - } - - removeState(state: VisualState, type: ShapeType, ...indexes: number[]) { - this.removeOrAddState("remove", state, type, ...indexes); - } - - private removeOrAddState( - action: "remove" | "add", - state: VisualState, - type: ShapeType, - ...indexes: number[] - ) { - if (type === ShapeType.Shape) { - let newState = this.highlighter.updateStateData(this, action, state, type); - this.setStateMaterial(newState); - return; - } - indexes.forEach((index) => { - let newState = this.highlighter.updateStateData(this, action, state, type, index); - this.setSubShapeState(type, newState, index); - }); - } - - private setSubShapeState(type: ShapeType, newState: VisualState, index: number) { - if (ShapeType.hasFace(type)) { - if (this._faces) this.setSubFaceState(newState, index); - } - if (ShapeType.hasEdge(type) || ShapeType.hasWire(type)) { - if (this._edges) this.setSubEdgeState(newState, index); + setFacesMateiralTemperary(material: MeshLambertMaterial) { + if (this._faces) { + this._faces.material = material; } - // TODO: other type } - private setStateMaterial(newState: VisualState) { - if (this._faces) { - let faceMaterial = this._faceMaterial; - if (VisualState.hasState(newState, VisualState.selected)) { - faceMaterial = selectedFaceMaterial; - } else if (VisualState.hasState(newState, VisualState.highlight)) { - faceMaterial = highlightFaceMaterial; - } - this._faces.material = faceMaterial; - } + setEdgesMateiralTemperary(material: LineBasicMaterial) { if (this._edges) { - let edgeMaterial: Material = this._edgeMaterial; - if (VisualState.hasState(newState, VisualState.selected)) { - edgeMaterial = selectedEdgeMaterial; - } else if (VisualState.hasState(newState, VisualState.highlight)) { - edgeMaterial = hilightEdgeMaterial; - } - this._edges.material = edgeMaterial; + this._edges.material = material; } } - resetState(): void { - this.highlighter.removeAllStates(this, false); + removeTemperaryMaterial(): void { if (this._edges) this._edges.material = this._edgeMaterial; if (this._faces) this._faces.material = this._faceMaterial; - this._highlightedEdges.forEach((_, index) => this.removeEdge(index)); - this._highlightedFaces.forEach((_, index) => this.removeFace(index)); - this._highlightedEdges.clear(); - this._highlightedFaces.clear(); - } - - private removeEdge(index: number) { - let edge = this._highlightedEdges.get(index); - if (edge) { - this.remove(edge); - edge.geometry.dispose(); - this._highlightedEdges.delete(index); - } - } - - private removeFace(index: number) { - let face = this._highlightedFaces.get(index); - if (face) { - this.remove(face); - face.geometry.dispose(); - this._highlightedFaces.delete(index); - } } - private setSubEdgeState(state: VisualState, index: number) { - if (!this._edges) return; - - if (state === VisualState.normal) { - this.removeEdge(index); - return; - } - - let material = getEdgeStateMaterial(state); - if (this._highlightedEdges.has(index)) { - this._highlightedEdges.get(index)!.material = material; - return; - } + cloneSubEdge(index: number, material: LineBasicMaterial) { + let mesh = MeshUtils.subEdge(this.geometryEngity.shape.value!.mesh.edges!, index); + if (!mesh) return undefined; - let edge = this.cloneSubEdge(index, material); - edge.renderOrder = 99; - this.add(edge); - this._highlightedEdges.set(index, edge); - } - - private cloneSubEdge(index: number, material: LineBasicMaterial) { - let allPositions = this._edges!.geometry.getAttribute("position") as Float32BufferAttribute; - let group = this.geometryEngity.shape.value!.mesh.edges!.groups[index]; - let positions = allPositions.array.slice(group.start * 3, (group.start + group.count) * 3); let buff = new BufferGeometry(); - buff.setAttribute("position", new Float32BufferAttribute(positions, 3)); - return new LineSegments(buff, material); - } - - private setSubFaceState(state: VisualState, index: number) { - if (!this._faces) return; + buff.setAttribute("position", new Float32BufferAttribute(mesh.positions, 3)); - if (state === VisualState.normal) { - this.removeFace(index); - return; - } - - let material = getFaceStateMaterial(state); - if (this._highlightedFaces.has(index)) { - this._highlightedFaces.get(index)!.material = material; - return; - } - - let face = this.cloneSubFace(index, material); - if (face) { - face.renderOrder = 99; - this.add(face); - this._highlightedFaces.set(index, face); - } + return new LineSegments(buff, material); } - private cloneSubFace(index: number, material: MeshLambertMaterial) { - let group = this.geometryEngity.shape.value?.mesh.faces!.groups[index]; - if (!group) return undefined; - - let allPositions = this._faces!.geometry.getAttribute("position") as Float32BufferAttribute; - let allNormals = this._faces!.geometry.getAttribute("normal") as Float32BufferAttribute; - let allIndices = this.geometryEngity.shape.value!.mesh.faces!.indices; - let indices = allIndices.slice(group.start, group.start + group.count); - let minMax = MathUtils.minMax(indices); - let indiceStart = minMax!.min; - let indiceEnd = minMax!.max + 1; - let positions = allPositions.array.slice(indiceStart * 3, indiceEnd * 3); - let normals = allNormals.array.slice(indiceStart * 3, indiceEnd * 3); + cloneSubFace(index: number, material: MeshLambertMaterial) { + let mesh = MeshUtils.subFace(this.geometryEngity.shape.value!.mesh.faces!, index); + if (!mesh) return undefined; let buff = new BufferGeometry(); - buff.setAttribute("position", new Float32BufferAttribute(positions, 3)); - buff.setAttribute("normal", new Float32BufferAttribute(normals, 3)); - buff.setIndex(indices.map((i) => i - indiceStart)); + buff.setAttribute("position", new Float32BufferAttribute(mesh.positions, 3)); + buff.setAttribute("normal", new Float32BufferAttribute(mesh.normals, 3)); + buff.setIndex(mesh.indices); + return new Mesh(buff, material); } @@ -351,10 +184,3 @@ export class ThreeGeometry extends Object3D implements IVisualGeometry { return this._edges; } } -function getEdgeStateMaterial(state: VisualState) { - return VisualState.hasState(state, VisualState.selected) ? selectedEdgeMaterial : hilightEdgeMaterial; -} - -function getFaceStateMaterial(state: VisualState) { - return VisualState.hasState(state, VisualState.selected) ? selectedFaceMaterial : highlightFaceMaterial; -} diff --git a/packages/chili-three/src/threeHighlighter.ts b/packages/chili-three/src/threeHighlighter.ts index 0d33a65a..41596985 100644 --- a/packages/chili-three/src/threeHighlighter.ts +++ b/packages/chili-three/src/threeHighlighter.ts @@ -1,44 +1,239 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IHighlighter, IVisualGeometry, ShapeType, VisualState } from "chili-core"; +import { IHighlighter, IVisualGeometry, ShapeType, VisualConfig, VisualState } from "chili-core"; +import { DoubleSide, LineBasicMaterial, LineSegments, Mesh, MeshLambertMaterial } from "three"; +import { ThreeGeometry } from "./threeGeometry"; +import { ThreeHelper } from "./threeHelper"; + +const hilightEdgeMaterial = new LineBasicMaterial({ + color: ThreeHelper.fromColor(VisualConfig.highlightEdgeColor), + linewidth: 2, +}); + +const selectedEdgeMaterial = new LineBasicMaterial({ + color: ThreeHelper.fromColor(VisualConfig.selectedEdgeColor), + linewidth: 2, +}); + +const highlightFaceMaterial = new MeshLambertMaterial({ + color: ThreeHelper.fromColor(VisualConfig.highlightFaceColor), + side: DoubleSide, + transparent: true, + opacity: 0.85, +}); + +const selectedFaceMaterial = new MeshLambertMaterial({ + color: ThreeHelper.fromColor(VisualConfig.selectedFaceColor), + side: DoubleSide, + transparent: true, + opacity: 0.32, +}); + +export class GeometryState { + private readonly _highlightedFaces: Map = new Map(); + private readonly _highlightedEdges: Map = new Map(); + private readonly _states: Map = new Map(); + + constructor(readonly geometry: ThreeGeometry) {} + + getState(type: ShapeType, index?: number) { + const key = `${type}_${index}`; + return this._states.get(key); + } + + addState(state: VisualState, type: ShapeType, index?: number) { + return this.updateState("add", state, type, index); + } + + removeState(state: VisualState, type: ShapeType, index?: number) { + return this.updateState("remove", state, type, index); + } + + private updateState(method: "add" | "remove", state: VisualState, type: ShapeType, index?: number) { + const key = `${type}_${index}`; + + let newState = this._states.get(key); + if (newState === undefined) { + if (method === "remove") return VisualState.normal; + newState = state; + } else { + let func = method === "add" ? VisualState.addState : VisualState.removeState; + newState = func(newState, state); + } + + this._states.set(key, newState); + this.displayState(type, newState, index); + return newState; + } + + private displayState(type: ShapeType, newState: VisualState, index: number | undefined) { + if (type === ShapeType.Shape) { + this.setGeometryState(newState); + } else if (index !== undefined) { + this.setSubGeometryState(type, newState, index); + } + } + + private setGeometryState(newState: VisualState) { + if (newState === VisualState.normal) { + this.geometry.removeTemperaryMaterial(); + } else { + let { faceMaterial, edgeMaterial } = this.getShapeMaterial(newState); + this.geometry.setFacesMateiralTemperary(faceMaterial); + this.geometry.setEdgesMateiralTemperary(edgeMaterial); + } + } + + resetState() { + this._highlightedEdges.forEach((_, index) => this.removeEdge(index)); + this._highlightedFaces.forEach((_, index) => this.removeFace(index)); + this._highlightedEdges.clear(); + this._highlightedFaces.clear(); + this.geometry.removeTemperaryMaterial(); + } + + private removeEdge(index: number) { + let edge = this._highlightedEdges.get(index); + if (edge) { + this.geometry.remove(edge); + edge.geometry.dispose(); + this._highlightedEdges.delete(index); + } + } + + private removeFace(index: number) { + let face = this._highlightedFaces.get(index); + if (face) { + this.geometry.remove(face); + face.geometry.dispose(); + this._highlightedFaces.delete(index); + } + } + + private setSubGeometryState(type: ShapeType, newState: VisualState, index: number) { + if (ShapeType.hasFace(type)) { + this.setSubFaceState(newState, index); + } + if (ShapeType.hasEdge(type) || ShapeType.hasWire(type)) { + this.setSubEdgeState(newState, index); + } + } + + private getShapeMaterial(newState: VisualState) { + let faceMaterial = highlightFaceMaterial; + let edgeMaterial = hilightEdgeMaterial; + if (VisualState.hasState(newState, VisualState.selected)) { + faceMaterial = selectedFaceMaterial; + edgeMaterial = selectedEdgeMaterial; + } + return { faceMaterial, edgeMaterial }; + } + + private setSubEdgeState(state: VisualState, index: number) { + if (state === VisualState.normal) { + this.removeEdge(index); + return; + } + + let material = this.getEdgeStateMaterial(state); + if (this._highlightedEdges.has(index)) { + this._highlightedEdges.get(index)!.material = material; + return; + } + + let edge = this.geometry.cloneSubEdge(index, material); + if (edge) { + edge.renderOrder = 99; + this.geometry.add(edge); + this._highlightedEdges.set(index, edge); + } + } + + private setSubFaceState(state: VisualState, index: number) { + if (state === VisualState.normal) { + this.removeFace(index); + return; + } + + let material = this.getFaceStateMaterial(state); + if (this._highlightedFaces.has(index)) { + this._highlightedFaces.get(index)!.material = material; + return; + } + + let face = this.geometry.cloneSubFace(index, material); + if (face) { + face.renderOrder = 99; + this.geometry.add(face); + this._highlightedFaces.set(index, face); + } + } + + getEdgeStateMaterial(state: VisualState) { + return VisualState.hasState(state, VisualState.selected) + ? selectedEdgeMaterial + : hilightEdgeMaterial; + } + + getFaceStateMaterial(state: VisualState) { + return VisualState.hasState(state, VisualState.selected) + ? selectedFaceMaterial + : highlightFaceMaterial; + } +} export class ThreeHighlighter implements IHighlighter { - private readonly _stateMap = new Map>(); + private readonly _stateMap = new Map(); clear(): void { this._stateMap.forEach((v, k) => { - this.removeAllStates(k, true); + this.resetState(k); }); this._stateMap.clear(); } - removeAllStates(shape: IVisualGeometry, resetState: boolean): void { - if (!this._stateMap.has(shape)) return; - this._stateMap.delete(shape); - if (resetState) shape.resetState(); + resetState(geometry: IVisualGeometry): 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 { + if (this._stateMap.has(shape)) { + return this._stateMap.get(shape)!.getState(type, index); + } + return undefined; } - updateStateData( - shape: IVisualGeometry, - mode: "add" | "remove", - state: VisualState, - type: ShapeType, - index?: number, - ) { - let map = this._stateMap.get(shape); - if (!map) { - map = new Map(); - this._stateMap.set(shape, map); + addState(geometry: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]) { + let geometryState = this.getOrInitState(geometry); + if (type === ShapeType.Shape) { + geometryState.addState(state, type); + } else { + index.forEach((i) => { + geometryState.addState(state, type, i); + }); } + } - const key = `${type}_${index}`; - let newState = map.get(key) ?? VisualState.normal; - if (mode === "add") { - newState = VisualState.addState(newState, state); + removeState(geometry: IVisualGeometry, state: VisualState, type: ShapeType, ...index: number[]) { + let geometryState = this.getOrInitState(geometry); + if (type === ShapeType.Shape) { + geometryState.removeState(state, type); } else { - newState = VisualState.removeState(newState, state); + index.forEach((i) => { + geometryState.removeState(state, type, i); + }); } - map.set(key, newState); - return newState; + } + + private getOrInitState(geometry: IVisualGeometry) { + let geometryState = this._stateMap.get(geometry); + if (!geometryState) { + geometryState = new GeometryState(geometry as ThreeGeometry); + this._stateMap.set(geometry, geometryState); + } + return geometryState; } } diff --git a/packages/chili-three/src/threeView.ts b/packages/chili-three/src/threeView.ts index 3dc22987..00ee8ac2 100644 --- a/packages/chili-three/src/threeView.ts +++ b/packages/chili-three/src/threeView.ts @@ -278,7 +278,8 @@ export class ThreeView extends Observable implements IView { shapeFilter?: IShapeFilter, ) { if (!(shape.parent instanceof ThreeGeometry) || !shape.parent.visible) return; - if (shapeType === ShapeType.Shape && shape instanceof LineSegments) { + + if (shape instanceof LineSegments) { if (shapeFilter && !shapeFilter.allow(shape.parent.geometryEngity.shape.value!)) return; detecteds.push({ shape: shape.parent.geometryEngity.shape.value!, diff --git a/packages/chili-three/src/threeVisualContext.ts b/packages/chili-three/src/threeVisualContext.ts index e166ba79..fa866c95 100644 --- a/packages/chili-three/src/threeVisualContext.ts +++ b/packages/chili-three/src/threeVisualContext.ts @@ -300,7 +300,7 @@ export class ThreeVisualContext implements IVisualContext { private displayModel(model: IModel) { let modelShape = model.geometry.shape.value; if (modelShape === undefined) return; - let threeShape = new ThreeGeometry(model.geometry, this.visual.highlighter, this); + let threeShape = new ThreeGeometry(model.geometry, this); this.visualShapes.add(threeShape); this._shapeModelMap.set(threeShape, model); this._modelShapeMap.set(model, threeShape); diff --git a/packages/chili-vis/src/selectionEventHandler.ts b/packages/chili-vis/src/selectionEventHandler.ts index dda6d9c2..bfe7a4d6 100644 --- a/packages/chili-vis/src/selectionEventHandler.ts +++ b/packages/chili-vis/src/selectionEventHandler.ts @@ -50,10 +50,7 @@ export abstract class SelectionHandler implements IEventHandler { }); } - dispose() { - this._highlights = undefined; - this._detectAtMouse = undefined; - } + dispose() {} pointerMove(view: IView, event: PointerEvent): void { this._detectAtMouse = undefined; @@ -100,8 +97,12 @@ export abstract class SelectionHandler implements IEventHandler { private setHighlight(view: IView, detecteds: VisualShapeData[]) { this.cleanHighlights(); detecteds.forEach((x) => { - let shapeType = this.shapeType === ShapeType.Shape ? this.shapeType : x.shape.shapeType; - x.owner.addState(VisualState.highlight, shapeType, ...x.indexes); + view.document.visual.highlighter.addState( + x.owner, + VisualState.highlight, + this.shapeType, + ...x.indexes, + ); }); this._highlights = detecteds; view.update(); @@ -173,7 +174,12 @@ export abstract class SelectionHandler implements IEventHandler { protected cleanHighlights() { this._highlights?.forEach((x) => { - x.owner.removeState(VisualState.highlight, this.shapeType, ...x.indexes); + x.owner.geometryEngity.document.visual.highlighter.removeState( + x.owner, + VisualState.highlight, + this.shapeType, + ...x.indexes, + ); }); this._highlights = undefined; } diff --git a/packages/chili-vis/src/shapeSelectionEventHandler.ts b/packages/chili-vis/src/shapeSelectionEventHandler.ts index 3c700157..2f79fccd 100644 --- a/packages/chili-vis/src/shapeSelectionEventHandler.ts +++ b/packages/chili-vis/src/shapeSelectionEventHandler.ts @@ -1,6 +1,6 @@ // Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. -import { IDocument, IView, VisualShapeData, VisualState } from "chili-core"; +import { IDocument, IView, ShapeType, VisualShapeData, VisualState } from "chili-core"; import { SelectionHandler } from "./selectionEventHandler"; export class ShapeSelectionHandler extends SelectionHandler { @@ -10,14 +10,15 @@ export class ShapeSelectionHandler extends SelectionHandler { return [...this._shapes]; } - override dispose(): void { - super.dispose(); - this._shapes.clear(); - } - override clearSelected(document: IDocument): void { + let highlighter = document.visual.highlighter; for (const shape of this._shapes.values()) { - shape.owner.removeState(VisualState.selected, shape.shape.shapeType, ...shape.indexes); + highlighter.removeState( + shape.owner, + VisualState.selected, + shape.shape.shapeType, + ...shape.indexes, + ); } this._shapes.clear(); } @@ -42,11 +43,13 @@ export class ShapeSelectionHandler extends SelectionHandler { private removeSelected(shape: VisualShapeData) { this._shapes.delete(shape); - shape.owner.removeState(VisualState.selected, shape.shape.shapeType, ...shape.indexes); + let highlighter = shape.owner.geometryEngity.document.visual.highlighter; + highlighter.removeState(shape.owner, VisualState.selected, shape.shape.shapeType, ...shape.indexes); } private addSelected(shape: VisualShapeData) { - shape.owner.addState(VisualState.selected, shape.shape.shapeType, ...shape.indexes); + let highlighter = shape.owner.geometryEngity.document.visual.highlighter; + highlighter.addState(shape.owner, VisualState.selected, this.shapeType, ...shape.indexes); this._shapes.add(shape); } } diff --git a/packages/chili/src/commands/createCommand.ts b/packages/chili/src/commands/createCommand.ts index ff11b20e..be2071f1 100644 --- a/packages/chili/src/commands/createCommand.ts +++ b/packages/chili/src/commands/createCommand.ts @@ -21,7 +21,6 @@ export abstract class CreateCommand extends MultistepCommand { let name = this.getModelName(geometry) + count++; let model = new GeometryModel(this.document, name, geometry); this.document.addNode(model); - this.document.visual.highlighter.clear(); this.document.visual.update(); }); } diff --git a/packages/chili/src/commands/modify/split.ts b/packages/chili/src/commands/modify/split.ts index 277943f4..05660315 100644 --- a/packages/chili/src/commands/modify/split.ts +++ b/packages/chili/src/commands/modify/split.ts @@ -42,7 +42,6 @@ export class Split extends CreateCommand { ); this.document.addNode(model); } - this.document.visual.highlighter.clear(); this.document.visual.update(); }); } @@ -57,7 +56,7 @@ export class Split extends CreateCommand { protected override getSteps(): IStep[] { return [ new SelectShapeStep(ShapeType.Shape, "prompt.select.shape", false), - new SelectShapeStep(ShapeType.Edge | ShapeType.Wire, "prompt.select.shape", true), + new SelectShapeStep(ShapeType.Wire, "prompt.select.shape", true), ]; } } diff --git a/packages/chili/src/commands/multistepCommand.ts b/packages/chili/src/commands/multistepCommand.ts index 938290dc..fc7e343f 100644 --- a/packages/chili/src/commands/multistepCommand.ts +++ b/packages/chili/src/commands/multistepCommand.ts @@ -48,15 +48,20 @@ export abstract class MultistepCommand extends CancelableCommand { protected async executeSteps(): Promise { let steps = this.getSteps(); - while (this.stepDatas.length < steps.length) { - this.controller = new AsyncController(); - let data = await steps[this.stepDatas.length].execute(this.document, this.controller); - if (data === undefined || this.controller.result?.status !== "success") { - return false; + try { + while (this.stepDatas.length < steps.length) { + this.controller = new AsyncController(); + let data = await steps[this.stepDatas.length].execute(this.document, this.controller); + if (data === undefined || this.controller.result?.status !== "success") { + return false; + } + this.stepDatas.push(data); } - this.stepDatas.push(data); + return true; + } finally { + this.document.selection.clearSelection(); + this.document.visual.highlighter.clear(); } - return true; } protected resetSteps() { diff --git a/packages/chili/src/selection.ts b/packages/chili/src/selection.ts index bf8d3a50..c4c732de 100644 --- a/packages/chili/src/selection.ts +++ b/packages/chili/src/selection.ts @@ -36,9 +36,7 @@ export class Selection implements ISelection, IDisposable { this.filter, ); await this.pickAsync(handler, prompt, controller, multiMode); - let shapes = handler.shapes(); - handler.dispose(); - return shapes; + return handler.shapes(); } async pickModel(prompt: I18nKeys, controller: AsyncController, multiMode: boolean) { @@ -47,9 +45,7 @@ export class Selection implements ISelection, IDisposable { this.nodeType = "model"; let handler = new ModelSelectionHandler(this.document, multiMode, controller, this.filter); await this.pickAsync(handler, prompt, controller, multiMode); - let models = handler.models(); - handler.dispose(); - return models; + return handler.models(); } finally { this.nodeType = oldNodeType; } @@ -132,7 +128,9 @@ export class Selection implements ISelection, IDisposable { private addSelectPublish(nodes: INode[], publish: boolean) { nodes.forEach((m) => { if (INode.isModelNode(m)) { - this.document.visual.context.getShape(m)?.addState(VisualState.selected, ShapeType.Shape); + let visual = this.document.visual.context.getShape(m); + if (visual) + this.document.visual.highlighter.addState(visual, VisualState.selected, ShapeType.Shape); } }); this._selectedNodes.push(...nodes); @@ -142,9 +140,13 @@ export class Selection implements ISelection, IDisposable { private removeSelectedPublish(nodes: INode[], publish: boolean) { for (const node of nodes) { if (INode.isModelNode(node)) { - this.document.visual.context - .getShape(node) - ?.removeState(VisualState.selected, ShapeType.Shape); + let visual = this.document.visual.context.getShape(node); + if (visual) + this.document.visual.highlighter.removeState( + visual, + VisualState.selected, + ShapeType.Shape, + ); } } this._selectedNodes = this._selectedNodes.filter((m) => !nodes.includes(m)); diff --git a/packages/chili/src/snap/objectSnap.ts b/packages/chili/src/snap/objectSnap.ts index 05536a6d..f5c929ef 100644 --- a/packages/chili/src/snap/objectSnap.ts +++ b/packages/chili/src/snap/objectSnap.ts @@ -183,13 +183,17 @@ export class ObjectSnap implements ISnapper { } private hilighted(view: IView, shapes: VisualShapeData[]) { - shapes.forEach((x) => x.owner.addState(VisualState.highlight, x.shape.shapeType, ...x.indexes)); + shapes.forEach((x) => { + let highlighter = x.owner.geometryEngity.document.visual.highlighter; + highlighter.addState(x.owner, VisualState.highlight, x.shape.shapeType, ...x.indexes); + }); this._hilightedShapes.push(...shapes); } private unHilighted() { this._hilightedShapes.forEach((x) => { - x.owner.removeState(VisualState.highlight, x.shape.shapeType, ...x.indexes); + let highlighter = x.owner.geometryEngity.document.visual.highlighter; + highlighter.removeState(x.owner, VisualState.highlight, x.shape.shapeType, ...x.indexes); }); this._hilightedShapes.length = 0; }