Browse Source

Measure 测量工具

master
修宁 7 months ago
parent
commit
1485116898
  1. 23
      src/designer/Constract.ts
  2. 24
      src/designer/Viewport.ts
  3. 21
      src/designer/model2DEditor/EsDragControls.ts
  4. 20
      src/designer/model2DEditor/Model2DEditor.vue
  5. 2
      src/designer/model2DEditor/Model2DEditorJs.js
  6. 17
      src/designer/model2DEditor/tools/CursorTool.ts
  7. 237
      src/designer/model2DEditor/tools/MeasureTool.ts
  8. 14
      src/types/Types.d.ts
  9. 6
      src/utils/webutils.ts

23
src/designer/Constract.ts

@ -0,0 +1,23 @@
export default Object.freeze({
// 光标相关
CursorModeNormal: 'normal',
// 关联相关
CursorModeALink: 'ALink',
CursorModeSLink: 'SLink',
CursorModePointCallback: 'PointCallback',
CursorModePointAdd: 'PointAdd',
CursorModeLinkAdd: 'LinkAdd',
CursorModeLinkAdd2: 'LinkAdd2',
// 测量相关的光标模式
CursorModeMeasure: 'Measure',
CursorModeMeasureArea: 'MeasureArea',
CursorModeMeasureAngle: 'MeasureAngle',
MeasureMarker: 'measure-marker',
MeasureLine: 'measure-line',
MeasureLabel: 'measure-label',
// 选择模式
CursorModeSelectByRec: 'selectByRec'
})

24
src/designer/Viewport.ts

@ -9,9 +9,10 @@ import $ from 'jquery'
import { reactive, watch } from 'vue'
import MouseMoveInspect from '@/designer/model2DEditor/tools/MouseMoveInspect.ts'
import type { ITool } from '@/designer/model2DEditor/tools/ITool.ts'
import RulerTool from '@/designer/model2DEditor/tools/RulerTool.ts'
import MeasureTool from '@/designer/model2DEditor/tools/MeasureTool.ts'
import { CSS3DRenderer } from 'three/examples/jsm/renderers/CSS3DRenderer'
import { CSS2DRenderer } from 'three/examples/jsm/renderers/CSS2DRenderer'
import type { ICursorTool } from '@/designer/model2DEditor/tools/CursorTool.ts'
/**
*
@ -29,9 +30,10 @@ export default class Viewport {
worldModel: WorldModel
raycaster: Raycaster
dragControl: EsDragControls
measure: MeasureTool
animationFrameId: any = null
currentTool: ITool | null = null
currentTool: ICursorTool | null = null
tools: ITool[] = [
new MouseMoveInspect()
]
@ -80,7 +82,7 @@ export default class Viewport {
* THREE
*/
initThree(viewerDom: HTMLElement, floor: string) {
console.log('init floor', floor)
console.log('viewport on floor', floor)
this.state.currentFloor = floor
this.viewerDom = viewerDom
const rect = viewerDom.getBoundingClientRect()
@ -105,7 +107,7 @@ export default class Viewport {
renderer.clearDepth()
renderer.shadowMap.enabled = true
renderer.toneMapping = THREE.ACESFilmicToneMapping
renderer.setPixelRatio(Math.max(Math.ceil(window.devicePixelRatio), 1));
renderer.setPixelRatio(Math.max(Math.ceil(window.devicePixelRatio), 1))
renderer.setViewport(0, 0, this.viewerDom.offsetWidth, this.viewerDom.offsetHeight)
renderer.setSize(this.viewerDom.offsetWidth, this.viewerDom.offsetHeight)
@ -195,12 +197,12 @@ export default class Viewport {
}
this.updateGridVisibility()
}))
this.watchList.push(watch(() => this.state.cursorMode, (newVal) => {
this.watchList.push(watch(() => this.state.cursorMode, (newVal: CursorMode) => {
if (!this.state.isReady) {
return
}
if (this.currentTool) {
this.currentTool.destory()
this.currentTool.stop()
this.currentTool = null
}
if (newVal === 'normal' || !newVal) {
@ -208,9 +210,9 @@ export default class Viewport {
return
}
if (newVal === 'Ruler' || newVal === 'RulerArea' || newVal === 'RulerAngle') {
if (newVal === 'Measure' || newVal === 'MeasureArea' || newVal === 'MeasureAngle') {
// 选择标尺工具
this.currentTool = new RulerTool()
this.currentTool = this.measure
this.dragControl.dragControls.enabled = false
} else {
@ -218,7 +220,7 @@ export default class Viewport {
}
if (this.currentTool) {
this.currentTool.init(this)
this.currentTool.start()
}
}))
@ -239,6 +241,10 @@ export default class Viewport {
// 注册拖拽组件
this.dragControl = new EsDragControls(this)
// 注册测量工具
this.measure = new MeasureTool()
this.measure.init(this)
this.state.isReady = true
}

21
src/designer/model2DEditor/EsDragControls.ts

@ -1,6 +1,7 @@
import { DragControls } from './DragControls.js'
import * as THREE from 'three'
import type Viewport from '@/designer/Viewport.ts'
import Constract from '@/designer/Constract.ts'
// dragControls 绑定函数
let dragStartFn, dragFn, dragEndFn, clickblankFn
@ -76,11 +77,11 @@ export default class EsDragControls {
// 记录拖拽按下的位置和对象
this.onDownPosition = { x: e.e.clientX, y: e.e.clientY }
// switch (e.object.userData.type) {
// case 'measure-marker':
// this.viewport.modules.measure.redraw(e.object)
// break
// }
switch (e.object.userData.type) {
case Constract.MeasureMarker:
this.viewport.measure.redraw(e.object)
break
}
}
// 拖拽中
@ -107,11 +108,11 @@ export default class EsDragControls {
}
}
// switch (e.object.userData.type) {
// case 'measure-marker':
// this.viewport.modules.measure.redrawComplete()
// break
// }
switch (e.object.userData.type) {
case 'measure-marker':
this.viewport.measure.redrawComplete()
break
}
}
// 点击可拖拽物体之外

20
src/designer/model2DEditor/Model2DEditor.vue

@ -15,28 +15,28 @@
<div class="section-bottom-toolbar section-toolbar">
<div class="section-toolbar-left">
<el-button title="鼠标状态 (ESC)" :icon="renderIcon('fa MousePointer')" link
:type="state.cursorMode==='normal'?'primary':''"
@click="()=>state.cursorMode = 'normal'"></el-button>
:type="state.cursorMode===Constract.CursorModeNormal?'primary':''"
@click="()=>state.cursorMode = Constract.CursorModeNormal"></el-button>
<span class="section-toolbar-line"></span>
<el-button title="框选模式 (T)" :icon="renderIcon('FullScreen')" link
:type="state.cursorMode==='selectByRec'?'primary':''"
@click="()=>state.cursorMode = 'selectByRec'"></el-button>
:type="state.cursorMode===Constract.CursorModeSelectByRec?'primary':''"
@click="()=>state.cursorMode = Constract.CursorModeSelectByRec"></el-button>
<span class="section-toolbar-line"></span>
<el-button title="物理流动线 (Z)" :icon="renderIcon('antd EnterOutlined')" link
:type="state.cursorMode==='ALink'?'primary':''"
@click="()=>state.cursorMode = 'ALink'"></el-button>
:type="state.cursorMode===Constract.CursorModeALink?'primary':''"
@click="()=>state.cursorMode = Constract.CursorModeALink"></el-button>
<span class="section-toolbar-line"></span>
<el-button title="逻辑关联 (X)" :icon="renderIcon('antd LinkOutlined')" link
:type="state.cursorMode==='SLink'?'primary':''"
@click="()=>state.cursorMode = 'SLink'"></el-button>
:type="state.cursorMode===Constract.CursorModeSLink?'primary':''"
@click="()=>state.cursorMode = Constract.CursorModeSLink"></el-button>
<span class="section-toolbar-line"></span>
<el-button title="测量工具" :icon="renderIcon('fa Ruler')" link
:type="state.cursorMode==='Ruler'?'primary':''"
@click="()=>state.cursorMode = 'Ruler'"></el-button>
:type="state.cursorMode===Constract.CursorModeMeasure?'primary':''"
@click="()=>state.cursorMode = Constract.CursorModeMeasure"></el-button>
</div>
<div class="section-toolbar-right">
<el-input v-model="searchKeyword" size="small" style="width: 110px; margin-right: 5px;"

2
src/designer/model2DEditor/Model2DEditorJs.js

@ -2,6 +2,7 @@ import * as THREE from 'three'
import { renderIcon } from '@/utils/webutils.ts'
import { defineComponent, markRaw } from 'vue'
import Viewport from '@/designer/Viewport.ts'
import Constract from '@/designer/Constract.js'
export default defineComponent({
name: 'Model2DEditor',
@ -9,6 +10,7 @@ export default defineComponent({
const viewport = new Viewport(worldModel)
return {
Constract,
isReady: false,
viewport: viewport,
currentFloor: '',

17
src/designer/model2DEditor/tools/CursorTool.ts

@ -1,11 +1,22 @@
export interface ITools {
export interface ICursorTool {
/**
* , cursor
*
*/
init(viewport: any): void
/**
* , cursor
*/
start(): void
/**
* , , ESC 退 cursor
*/
destory(): void
stop(): void
/**
* ,
*/
destory():void
}

237
src/designer/model2DEditor/tools/RulerTool.ts → src/designer/model2DEditor/tools/MeasureTool.ts

@ -6,27 +6,28 @@ import Viewport from '@/designer/Viewport.ts'
import { numberToString, getUnitString } from '@/utils/webutils'
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer'
import { Vector3 } from 'three'
import Constract from '@/designer/Constract.ts'
import type { ICursorTool } from '@/designer/model2DEditor/tools/CursorTool.ts'
let pdFn, pmFn, puFn, kdFn
/**
* threejs object_for_measure
*/
export default class RulerTool implements ITool {
static OBJ_NAME = 'object_for_measure'
static LABEL_NAME = 'label_for_measure'
export default class MeasureTool implements ICursorTool {
static OBJ_NAME = 'measure_'
static LABEL_NAME = 'measure_label'
static LINE_NAME = 'measure_line'
static MAX_DISTANCE = 500 //当相交物体的距离太远时,忽略它
viewport: Viewport
// 当前测绘内容组
group: THREE.Group
isCompleted = false
mouseMoved = false
canvas: HTMLCanvasElement
// 用户在测量时绘制的线的当前实例
protected polyline?: THREE.Line
// 用于存储临时点
protected tempPointMarker?: THREE.Mesh
@ -36,13 +37,12 @@ export default class RulerTool implements ITool {
// 用于在鼠标移动时存储临时标签,只有测量距离时才有
protected tempLabel?: CSS2DObject
// 存储
// 当次绘制
protected pointArray: THREE.Vector3[] = []
//保存上次点击时间,以便检测双击事件
protected lastClickTime: number = 0
static LINE_MATERIAL = new THREE.LineBasicMaterial({
color: 0xE63C17,
linewidth: 2,
@ -58,36 +58,32 @@ export default class RulerTool implements ITool {
const viewerDom = this.viewport.viewerDom
this.canvas = $(viewerDom).children('canvas')[0] as HTMLCanvasElement
pdFn = this.mousedown.bind(this)
this.canvas.addEventListener('pointerdown', pdFn)
pmFn = this.mousemove.bind(this)
this.canvas.addEventListener('pointermove', pmFn)
puFn = this.mouseup.bind(this)
this.canvas.addEventListener('pointerup', puFn)
// 初始化group
this.group = new THREE.Group()
this.group.name = `${RulerTool.OBJ_NAME}_group`
this.group.name = `${MeasureTool.OBJ_NAME}_group`
this.group.userData = {
mode: this.viewport.state.cursorMode
}
this.viewport.scene.add(this.group)
}
start() {
pdFn = this.mousedown.bind(this)
this.canvas.addEventListener('pointerdown', pdFn)
pmFn = this.mousemove.bind(this)
this.canvas.addEventListener('pointermove', pmFn)
puFn = this.mouseup.bind(this)
this.canvas.addEventListener('pointerup', puFn)
// 测量距离、面积和角度需要折线
this.polyline = this.createLine()
this.group.add(this.polyline)
this.isCompleted = false
this.viewport.viewerDom.style.cursor = 'crosshair'
// 当次绘制点
this.pointArray = []
system.msg('进入鼠标测距模式')
system.msg('进入测量模式')
}
destory(): void {
system.msg('退出鼠标测距模式')
stop(): void {
system.msg('退出测量模式')
const viewerDom = this.viewport.viewerDom
@ -101,6 +97,7 @@ export default class RulerTool implements ITool {
this.canvas.removeEventListener('pointerup', puFn)
puFn = undefined
// 清空所有临时点
this.tempPointMarker && this.viewport.scene.remove(this.tempPointMarker)
this.tempLine && this.viewport.scene.remove(this.tempLine)
this.tempLabel && this.viewport.scene.remove(this.tempLabel)
@ -109,16 +106,20 @@ export default class RulerTool implements ITool {
this.tempLabel = undefined
}
mousedown = () => {
destory() {
}
mousedown() {
this.mouseMoved = false
}
// 鼠标移动,创建对应的临时点与线
mousemove = (e: MouseEvent) => {
mousemove(e: MouseEvent) {
if (this.isCompleted) return
this.mouseMoved = true
// 当前鼠标所在的点
const point = this.getClosestIntersection(e)
if (!point) {
return
@ -134,50 +135,56 @@ export default class RulerTool implements ITool {
// 移动时绘制临时线
if (this.pointArray.length > 0) {
const p0 = this.pointArray[this.pointArray.length - 1] // 获取最后一个点
const line = this.tempLine || this.createLine()
// 获取最后一个点
const p0 = this.pointArray[this.pointArray.length - 1]
if (!this.tempLine) {
this.tempLine = this.createLine()
this.viewport.scene.add(this.tempLine)
}
const line = this.tempLine
const geom = line.geometry
const startPoint = this.pointArray[0]
const lastPoint = this.pointArray[this.pointArray.length - 1]
if (this.viewport.state.cursorMode === 'RulerArea') {
if (this.viewport.state.cursorMode === Constract.CursorModeMeasureArea) {
geom.setFromPoints([lastPoint, point, startPoint])
} else {
geom.setFromPoints([lastPoint, point])
}
if (this.viewport.state.cursorMode === 'Ruler') {
if (this.viewport.state.cursorMode === Constract.CursorModeMeasure) {
const dist = p0.distanceTo(point)
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}`
const position = new THREE.Vector3((point.x + p0.x) / 2, (point.y + p0.y) / 2, (point.z + p0.z) / 2)
this.addOrUpdateTempLabel(label, position)
}
// tempLine 只需添加到场景一次
if (!this.tempLine) {
this.viewport.scene.add(line)
this.tempLine = line
}
}
// this.viewport.dispatchSignal('sceneGraphChanged')
}
mouseup = (e: MouseEvent) => {
mouseup(e: MouseEvent) {
// 如果mouseMoved是true,那么它可能在移动,而不是点击
if (!this.mouseMoved) {
// 右键点击表示完成绘图操作
if (e.button === 2) {
// 右键点击, 完成绘图操作
this.viewport.state.cursorMode = 'normal'
} else if (e.button === 0) { // 左键点击表示添加点
} else if (e.button === 0) {
// 左键点击, 添加点
this.onMouseClicked(e)
}
}
}
onMouseClicked = (e: MouseEvent) => {
onMouseClicked(e: MouseEvent) {
if (this.isCompleted) {
return
}
// 获取鼠标点击位置的三维坐标
const point = this.getClosestIntersection(e)
if (!point) {
return
@ -185,10 +192,12 @@ export default class RulerTool implements ITool {
// 双击触发两次点击事件,我们需要避免这里的第二次点击
const now = Date.now()
if (this.lastClickTime && (now - this.lastClickTime < 10)) return
if (this.lastClickTime && (now - this.lastClickTime < 50)) {
return
}
this.lastClickTime = now
// 添加正式点
this.pointArray.push(point)
const count = this.pointArray.length
@ -196,22 +205,24 @@ export default class RulerTool implements ITool {
marker.userData.point = point
marker.userData.pointIndex = count - 1
this.group.add(marker)
// 把点加入拖拽控制器
this.viewport.dragControl.setDragObjects([marker], 'push')
if (this.polyline) {
this.polyline.geometry.setFromPoints(this.pointArray)
if (this.tempLabel && count > 1) {
const p0 = this.pointArray[count - 2]
this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2)
this.group.add(this.tempLabel)
if (this.tempLabel && count > 1) {
// 临时 点/label/line 将作为正式的使用
const p0 = this.pointArray[count - 2]
// 创建距离测量线时,此处的 临时label 将作为正式的使用,不在this.clearTemp()中清除,故置为undefined
this.tempLabel = undefined
}
this.tempLabel.position.set((p0.x + point.x) / 2, (p0.y + point.y) / 2, (p0.z + point.z) / 2)
this.group.add(this.tempLabel)
this.tempLabel = undefined
// 临时线
this.tempLine.geometry.setFromPoints([p0, point])
this.group.add(this.tempLine)
this.tempLine = undefined
}
// this.redrawComplete()
// this.viewport.dispatchSignal('sceneGraphChanged')
}
@ -220,11 +231,11 @@ export default class RulerTool implements ITool {
*/
private createLine(): THREE.Line {
const geom = new THREE.BufferGeometry()
const obj = new THREE.Line(geom, RulerTool.LINE_MATERIAL)
const obj = new THREE.Line(geom, MeasureTool.LINE_MATERIAL)
obj.frustumCulled = false
obj.name = RulerTool.OBJ_NAME
obj.name = MeasureTool.LINE_NAME
obj.userData = {
type: 'line'
type: Constract.MeasureLine
}
return obj
}
@ -244,11 +255,11 @@ export default class RulerTool implements ITool {
obj.position.set(p.x, p.y, p.z)
}
obj.name = RulerTool.OBJ_NAME
obj.name = MeasureTool.OBJ_NAME
obj.userData = {
mode: this.viewport.state.cursorMode,
type: 'measure-marker'
type: Constract.MeasureMarker
}
return obj
}
@ -264,7 +275,7 @@ export default class RulerTool implements ITool {
const intersects = this.viewport.getIntersects(_point)
if (intersects && intersects.length > 2) {
if (intersects.length > 0 && intersects[0].distance < RulerTool.MAX_DISTANCE) {
if (intersects.length > 0 && intersects[0].distance < MeasureTool.MAX_DISTANCE) {
return new Vector3(
intersects[0].point.x,
0.1,
@ -306,14 +317,85 @@ export default class RulerTool implements ITool {
div.style.left = '0px'
// div.style.pointerEvents = 'none' //避免HTML元素影响场景的鼠标事件
const obj = new CSS2DObject(div)
obj.name = RulerTool.LABEL_NAME
obj.name = MeasureTool.LABEL_NAME
obj.userData = {
type: 'label'
type: Constract.MeasureLabel
}
return obj
}
// 重绘完成
/**
* ,
*/
redraw(point: THREE.Mesh) {
// 当次绘制点
this.pointArray = [];
(point.parent as THREE.Group).children.forEach(child => {
switch (child.userData.type) {
case Constract.MeasureMarker:
// 当前点正在操作,不加入
if (child.uuid !== point.uuid) {
this.pointArray[child.userData.pointIndex] = child.userData.point
} else {
this.tempPointMarker = child as THREE.Mesh
}
break
case Constract.MeasureLine:
this.tempLine = this.createLine()
this.group.add(this.tempLine)
break
}
})
// 重写move事件
pmFn = this.redrawMousemove.bind(this)
this.canvas.addEventListener('pointermove', pmFn)
this.group = point.parent as THREE.Group
this.isCompleted = false
}
/**
*
*/
redrawMousemove(e: MouseEvent) {
let point = this.getClosestIntersection(e)
if (!point && this.tempPointMarker) {
this.tempPointMarker.position.set(this.tempPointMarker.userData.point.x, this.tempPointMarker.userData.point.y, this.tempPointMarker.userData.point.z)
return
}
if (!point || !this.tempPointMarker) return
// 在鼠标移动时绘制临时点
this.tempPointMarker.position.set(point.x, point.y, point.z)
this.tempPointMarker.userData.point = point
// 当前点的索引
const cIndex = this.tempPointMarker.userData.pointIndex
// 移动时绘制临时线
if (this.pointArray.length > 0) {
const line = this.tempLine || this.createLine()
const geom = line.geometry
let startPoint = this.pointArray[cIndex + 1]
let lastPoint = this.pointArray[cIndex - 1]
if (startPoint && lastPoint) {
geom.setFromPoints([lastPoint, point, startPoint])
} else {
geom.setFromPoints([startPoint || lastPoint, point])
}
}
}
/**
*
*/
redrawComplete() {
if (!this.tempPointMarker) return
@ -321,24 +403,21 @@ export default class RulerTool implements ITool {
this.pointArray[this.tempPointMarker.userData.pointIndex] = point
const count = this.pointArray.length
if (this.polyline) {
this.polyline.geometry.setFromPoints(this.pointArray)
// 如果是距离测量,则清除group中已有的label,再重新创建
if (this.viewport.state.cursorMode === 'Ruler' && count > 1) {
this.clearCurrentLabel()
// 绘制label
for (let i = 0; i < count - 1; i++) {
const p0 = this.pointArray[i]
const p1 = this.pointArray[i + 1]
if (!p0 || !p1) continue
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}`
const position = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2)
const labelObj = this.createLabel(label)
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
this.group.add(labelObj)
}
// 如果是距离测量,则清除group中已有的label,再重新创建
if (this.viewport.state.cursorMode === 'Measure' && count > 1) {
this.clearCurrentLabel()
// 绘制label
for (let i = 0; i < count - 1; i++) {
const p0 = this.pointArray[i]
const p1 = this.pointArray[i + 1]
if (!p0 || !p1) continue
const dist = p0.distanceTo(p1)
const label = `${numberToString(dist)} ${getUnitString(this.viewport.state.cursorMode)}`
const position = new THREE.Vector3((p0.x + p1.x) / 2, (p0.y + p1.y) / 2, (p0.z + p1.z) / 2)
const labelObj = this.createLabel(label)
labelObj.position.set(position.x, position.y, position.z)
labelObj.element.innerHTML = label
this.group.add(labelObj)
}
}

14
src/types/Types.d.ts

@ -6,7 +6,15 @@ type CursorMode =
| 'PointAdd'
| 'LinkAdd'
| 'LinkAdd2'
| 'Ruler'
| 'RulerArea'
| 'RulerAngle'
| 'Measure'
| 'MeasureArea'
| 'MeasureAngle'
| 'selectByRec'
type PointType =
'measure-marker'
| 'pointMarker'
| 'pointMarker2'
| 'pointMarker3'
| 'pointMarker4'
| 'pointMarker5'

6
src/utils/webutils.ts

@ -26,9 +26,9 @@ export function numberToString(num: number) {
*
*/
export function getUnitString(mode: CursorMode): string {
if (mode === 'Ruler') return "m";
if (mode === 'RulerArea') return "m²";
if (mode === 'RulerAngle') return "°";
if (mode === 'Measure') return "m";
if (mode === 'MeasureArea') return "m²";
if (mode === 'MeasureAngle') return "°";
return "";
}

Loading…
Cancel
Save