diff --git a/package.json b/package.json index 6652147..5bc6629 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "yvan-rcs-web", + "name": "yvan-lcc", "version": "0.1.0", "private": true, "type": "module", diff --git a/src/core/Constract.ts b/src/core/Constract.ts index a26486d..37c09ca 100644 --- a/src/core/Constract.ts +++ b/src/core/Constract.ts @@ -25,6 +25,8 @@ const Constract = Object.freeze({ HEIGHT_GSTORE: 0.03, HEIGHT_MEASURE: 0.02, HEIGHT_RACK: 0, - HEIGHT_WAY: 0.01 + HEIGHT_WAY: 0.01, + HEIGHT_WAY_LABEL: 0.03, + HEIGHT_WAY_LINE: 0.02 }) export default Constract diff --git a/src/core/ModelUtils.ts b/src/core/ModelUtils.ts index 23bfa18..ebfce4f 100644 --- a/src/core/ModelUtils.ts +++ b/src/core/ModelUtils.ts @@ -230,6 +230,87 @@ export function findStateItemsByDistance(viewport: Viewport, point: Vector2, dis }) } +export function moveSelectedItem(direct: '↑' | '↓' | '←' | '→') { + // 获取当前是否按住了 Shift + const viewport: Viewport = window['viewport'] + if (!viewport) { + system.msg('没有找到当前视图') + return + } + + let delta = 0.25 + if (CurrentMouseInfo.isShiftKey || CurrentMouseInfo.isMetaKey) { + // 按住 Shift 键时,移动距离只有0.1 + delta = 0.1 + } + + const entityId = viewport.state.selectedEntityId + if (!entityId) { + const multiSelectedEntityIds = viewport.state.multiSelectedEntityIds + if (!multiSelectedEntityIds && multiSelectedEntityIds.length === 0) { + system.msg('请选中要调整坐标的实体', 'error') + return + } + + // 群体移动 + const stateManager = viewport.stateManager + stateManager.beginStateUpdate() + for (const item of stateManager.vdata.items) { + if (multiSelectedEntityIds.includes(item.id)) { + // 根据方向移动 + switch (direct) { + case '↑': + console.log('向上移动', item.tf[0][2],'-=', delta) + item.tf[0][2] -= delta // 向上移动 + break + case '↓': + item.tf[0][2] += delta // 向下移动 + break + case '←': + item.tf[0][0] -= delta // 向左移动 + break + case '→': + item.tf[0][0] += delta // 向右移动 + break + } + } + } + stateManager.endStateUpdate() + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: viewport.state.multiSelectedObjects + }) + EventBus.dispatch('selectedObjectPropertyChanged', {}) + return + } + + const stateManager = viewport.stateManager + viewport.stateManager.beginStateUpdate() + for (const item of stateManager.vdata.items) { + if (item.id === entityId) { + // 根据方向移动 + switch (direct) { + case '↑': + item.tf[0][2] -= delta // 向上移动 + break + case '↓': + item.tf[0][2] += delta // 向下移动 + break + case '←': + item.tf[0][0] -= delta // 向左移动 + break + case '→': + item.tf[0][0] += delta // 向右移动 + break + } + } + } + viewport.stateManager.endStateUpdate() + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: viewport.state.multiSelectedObjects + }) + EventBus.dispatch('selectedObjectPropertyChanged', {}) +} + export function quickCopyByMouse() { // 获取鼠标位置,查看鼠标是否在某个 viewport 的画布上,并取得该 viewport if (!CurrentMouseInfo?.viewport || @@ -484,7 +565,7 @@ function loadObject3DFromJson(items: ItemJson[]): THREE.Object3D[] { export function decimalSumBy(collection: ArrayLike | null | undefined, iteratee?: ((value: T) => number)): number { let sum = new Decimal(0) - _.forEach(collection, (t)=>{ + _.forEach(collection, (t) => { if (typeof iteratee === 'function') { sum = sum.add(new Decimal(iteratee(t))) } else { diff --git a/src/core/controls/MouseMoveInspect.ts b/src/core/controls/MouseMoveInspect.ts index 0eb53bc..f2d4f3b 100644 --- a/src/core/controls/MouseMoveInspect.ts +++ b/src/core/controls/MouseMoveInspect.ts @@ -35,12 +35,16 @@ export default class MouseMoveInspect implements IControls { lvFn = undefined } - mouseLv() { + mouseLv(event: MouseEvent) { this.viewport.state.mouse.x = NaN this.viewport.state.mouse.z = NaN window['CurrentMouseInfo'] = { x: NaN, - z: NaN + z: NaN, + isShiftKey: false, + isCtrlKey: false, + isAltKey: false, + isMetaKey: false } } @@ -66,6 +70,10 @@ export default class MouseMoveInspect implements IControls { viewport: this.viewport, x: point.x, z: point.z, + isShiftKey: event.shiftKey, + isCtrlKey: event.ctrlKey, + isAltKey: event.altKey, + isMetaKey: event.metaKey, mouse: mouse } @@ -73,4 +81,4 @@ export default class MouseMoveInspect implements IControls { animate(): void { } -} \ No newline at end of file +} diff --git a/src/core/controls/SelectInspect.ts b/src/core/controls/SelectInspect.ts index d25fb7e..d78e97a 100644 --- a/src/core/controls/SelectInspect.ts +++ b/src/core/controls/SelectInspect.ts @@ -89,6 +89,9 @@ export default class SelectInspect implements IControls { EventBus.on('selectedObjectPropertyChanged', (data) => { this.updateSelectionBox(this.viewport.state.selectedObject) }) + EventBus.on('multiselectedObjectChanged', (data) => { + this.updateMultiSelectionBoxes(data.multiSelectedObjects) + }) EventBus.on('entityDeleted', (data) => { const id = data.deleteEntityId diff --git a/src/core/manager/StateManager.ts b/src/core/manager/StateManager.ts index 2cf149e..a1c13d9 100644 --- a/src/core/manager/StateManager.ts +++ b/src/core/manager/StateManager.ts @@ -6,6 +6,7 @@ import { markRaw, reactive, ref } from 'vue' import type Viewport from '@/core/engine/Viewport.ts' import { getQueryParams, setQueryParam } from '@/utils/webutils.ts' import { ensureEntityRelationsConsistency } from '@/core/ModelUtils.ts' +import EventBus from '@/runtime/EventBus.ts' // 差异类型定义 interface DataDiff { @@ -278,6 +279,11 @@ export default class StateManager { * 撤销 */ undo() { + const viewport: Viewport = window['viewport'] + if (!viewport) { + system.msg('没有找到当前视图') + return + } if (!this.undoEnabled()) return const step = this.historySteps[this.historyIndex] @@ -292,6 +298,16 @@ export default class StateManager { this.pendingChanges = true system.msg('撤销完成') + if (viewport.state.multiSelectedObjects.length > 0) { + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: viewport.state.multiSelectedObjects + }) + } + if (viewport.state.selectedObject) { + EventBus.dispatch('selectedObjectPropertyChanged', { + selectedObject: viewport.state.selectedObject + }) + } this.startAutoSave() } @@ -299,6 +315,11 @@ export default class StateManager { * 重做 */ redo() { + const viewport: Viewport = window['viewport'] + if (!viewport) { + system.msg('没有找到当前视图') + return + } if (!this.redoEnabled()) return this.historyIndex++ @@ -312,6 +333,16 @@ export default class StateManager { this.pendingChanges = true system.msg('重做完成') + if (viewport.state.multiSelectedObjects.length > 0) { + EventBus.dispatch('multiselectedObjectChanged', { + multiSelectedObjects: viewport.state.multiSelectedObjects + }) + } + if (viewport.state.selectedObject) { + EventBus.dispatch('selectedObjectPropertyChanged', { + selectedObject: viewport.state.selectedObject + }) + } this.startAutoSave() } diff --git a/src/editor/menus/EditMenu.ts b/src/editor/menus/EditMenu.ts index 9c7ab33..bad759a 100644 --- a/src/editor/menus/EditMenu.ts +++ b/src/editor/menus/EditMenu.ts @@ -1,7 +1,7 @@ import { renderIcon } from '@/utils/webutils.ts' import { defineMenu } from '@/runtime/DefineMenu.ts' import SvgCode from '@/components/icons/SvgCode' -import { escByKeyboard, quickCopyByMouse, deletePointByKeyboard } from '@/core/ModelUtils' +import { escByKeyboard, quickCopyByMouse, deletePointByKeyboard, moveSelectedItem } from '@/core/ModelUtils' export default defineMenu((menus) => { menus.insertChildren('modelFile', @@ -88,28 +88,52 @@ export default defineMenu((menus) => { { name: 'edit_up', label: '上移', tip: 'key-up', click() { - system.msg('↑') + moveSelectedItem('↑') } }, { name: 'edit_down', label: '下移', tip: 'key-down', click() { - system.msg('↓') + moveSelectedItem('↓') } }, { name: 'edit_left', label: '左移', tip: 'key-left', click() { - system.msg('←') + moveSelectedItem('←') } }, { name: 'edit_right', label: '右移', tip: 'key-right', click() { - system.msg('→') + moveSelectedItem('→') + } + }, + { + name: 'edit_up', label: '上移', tip: 'shift-up', + click() { + moveSelectedItem('↑') + } + }, + { + name: 'edit_down', label: '下移', tip: 'shift-down', + click() { + moveSelectedItem('↓') + } + }, + { + name: 'edit_left', label: '左移', tip: 'shift-left', + click() { + moveSelectedItem('←') + } + }, + { + name: 'edit_right', label: '右移', tip: 'shift-right', + click() { + moveSelectedItem('→') } } ] } ]) -}) \ No newline at end of file +}) diff --git a/src/example/example1.js b/src/example/example1.js index 6e9a311..746bb0e 100644 --- a/src/example/example1.js +++ b/src/example/example1.js @@ -127,12 +127,6 @@ export default { tf: [[-4, 0.1, 2], [90, 0, 0], [0.25, 0.25, 0.1]], dt: { in: [], out: [], center: ['6wrGKiVJniwgKkoggOoEy6'] } }, { - id: '39zML1rnSOOQGQYQ2YUMGy', - t: 'way', - v: true, - tf: [[-4, 0.1, 2], [90, 0, 0], [0.25, 0.25, 0.1]], - dt: { in: [], out: [], center: ['6wrGKiVJniwgKkoggOoEy6'] } - }, { id: '6wrGKiVJniwgKkoggOoEy6', t: 'way', v: true, diff --git a/src/modules/way/WayRenderer.ts b/src/modules/way/WayRenderer.ts index df76046..19caaf9 100644 --- a/src/modules/way/WayRenderer.ts +++ b/src/modules/way/WayRenderer.ts @@ -7,6 +7,7 @@ import { getLineId } from '@/core/ModelUtils.ts' import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' import { numberToString } from '@/utils/webutils.ts' import Constract from '@/core/Constract.ts' +import type { ExtrudeGeometryOptions } from 'three/src/geometries/ExtrudeGeometry' /** * 辅助测量工具渲染器 @@ -78,16 +79,38 @@ export default class WayRenderer extends BaseRenderer { ) } + private _createOrUpdateLine(startPosition: THREE.Vector3, endPosition: THREE.Vector3, type: LinkType) { + const width = 1 + const halfWidth = width / 2 + + const path = new THREE.LineCurve3( + new THREE.Vector3(startPosition.x, startPosition.z, halfWidth - Constract.HEIGHT_WAY_LINE), + new THREE.Vector3(endPosition.x, endPosition.z, halfWidth - Constract.HEIGHT_WAY_LINE) + ) + + const shape = new THREE.Shape() + shape.moveTo(halfWidth, -halfWidth) + shape.lineTo(halfWidth, halfWidth) + + const extrudeSettings: ExtrudeGeometryOptions = { + steps: 2, // 沿路径的分段数 + depth: 1, // 实际由路径长度决定 + bevelEnabled: false, // 禁用倒角 + extrudePath: path // 挤出路径 + } + + const extrudedGeometry = new THREE.ExtrudeGeometry(shape, extrudeSettings) + extrudedGeometry.rotateX(Math.PI / 2) + + return new THREE.Mesh(extrudedGeometry, this.lineMaterial) + } createLineBasic(start: ItemJson, end: ItemJson, type: LinkType): THREE.Object3D[] { - const group = new THREE.Group() - const startPosition = new THREE.Vector3(start.tf[0][0], 0.01, start.tf[0][2]) - const endPosition = new THREE.Vector3(end.tf[0][0], 0.01, end.tf[0][2]) - const width = 1 + const startPosition = new THREE.Vector3(start.tf[0][0], 0, start.tf[0][2]) + const endPosition = new THREE.Vector3(end.tf[0][0], 0, end.tf[0][2]) - const curve = new THREE.LineCurve3(startPosition, endPosition) - const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 2, false) - const lineMesh = new THREE.Mesh(tubeGeometry, this.lineMaterial) + const group = new THREE.Group() + const lineMesh = this._createOrUpdateLine(startPosition, endPosition, type) group.add(lineMesh) @@ -110,7 +133,7 @@ export default class WayRenderer extends BaseRenderer { label.name = WayRenderer.LABEL_NAME label.quaternion.copy(this.tempViewport.camera.quaternion) label.sync() - label.position.set(midPoint.x, midPoint.y, midPoint.z) + label.position.set(midPoint.x, Constract.HEIGHT_WAY_LABEL, midPoint.z) group.add(label) @@ -122,7 +145,6 @@ export default class WayRenderer extends BaseRenderer { const startPosition = new THREE.Vector3(start.tf[0][0], 0.01, start.tf[0][2]) const endPosition = new THREE.Vector3(end.tf[0][0], 0.01, end.tf[0][2]) - const width = 1 const lineId = getLineId(start.id, end.id, type) const lines = this.tempViewport.entityManager.findLineObjectsById(lineId) @@ -132,9 +154,7 @@ export default class WayRenderer extends BaseRenderer { const label: Text = group.children[1] group.clear() - const curve = new THREE.LineCurve3(startPosition, endPosition) - const tubeGeometry = new THREE.TubeGeometry(curve, 1, width / 2, 8, false) - const lineMesh = new THREE.Mesh(tubeGeometry, this.lineMaterial) + const lineMesh = this._createOrUpdateLine(startPosition, endPosition, type) group.add(lineMesh) const midPoint = new THREE.Vector3() diff --git a/src/runtime/EventBus.ts b/src/runtime/EventBus.ts index 160ce28..4394bdb 100644 --- a/src/runtime/EventBus.ts +++ b/src/runtime/EventBus.ts @@ -2,7 +2,7 @@ import mitt from 'mitt' const instance = mitt() -export type DispatchNames = 'selectedObjectChanged' | +export type DispatchNames = 'selectedObjectChanged' | 'multiselectedObjectChanged' | 'catalogChanged' | 'dataLoadComplete' | 'entityDeleted' | diff --git a/src/types/model.d.ts b/src/types/model.d.ts index 9d8cdca..ddd2d11 100644 --- a/src/types/model.d.ts +++ b/src/types/model.d.ts @@ -20,6 +20,11 @@ interface CurrentMouseInfo { * 鼠标在设计图上的坐标, 归一化到 [0, 1] 范围 */ mouse: any + + isShiftKey: boolean + isCtrlKey: boolean + isAltKey: boolean + isMetaKey: boolean } interface InitThreeOption { @@ -206,4 +211,4 @@ interface ItemJson { */ [key: string]: any }, -} \ No newline at end of file +}