唯杰地图 logo唯杰地图
VJMAP
EN

绘图编辑拾取工具

vjmap3d/references/interaction/editor.md 31.9 KB888 行
原文

实现原理

工具Tool有一个“状态图”,即StateNode,其中包含工具(例如选择工具或绘制工具)的逻辑。用户交互(例如移动光标)将根据哪些节点处于活动状态而产生不同的状态变化。

每个节点都可以处于活动状态或非活动状态。每个状态节点也可能有零个或多个子节点。当某个状态处于活动状态时,如果该状态有子节点,则其子节点中必须有一个(且只有一个)处于活动状态。当状态节点从其父节点接收到事件时,它有机会在将事件传递给其活动子节点之前处理该事件。节点可以以任何方式处理事件:它可以忽略事件、更新存储中的记录或运行转换,以更改哪些状态节点处于活动状态。

当用户交互通过App将事件方法发送到工具时,此事件将被发送到工具的根状态节点rootState,然后向下传递通过下面的活动状态,直到到达叶节点或其中一个节点响应。

Tool工具

vjmap3d 编辑器带有一些内置核心工具:缺省工具、绘图工具、拾取工具和变换控制器工具。这些工具始终添加到状态图中。

工具切换

您可以使用app.setCurrentTool更改当前活动工具。

app.setCurrentTool('draw')

您可以使用活动工具 ID 的路径进行工具状态切换。如直接进入绘制线工具状态

app.setCurrentTool('draw.drawline')

工具实现规范

每个工具都有一个id。这用于在状态图中识别它。

class MyTool extends StateNode {
	static override id = 'my-tool'
}

工具可以包含子项。例如,绘图工具的子状态有:idle、drawpoint、drawline、drawpolygon和drawedit。如果某个状态有子项,那么它也必须有一个initial状态,这样它才知道从哪个状态开始。

class MyIdleState extends StateNode {
	static override id = 'my-idle-state'
}

class MyPointingState extends StateNode {
	static override id = 'my-pointing-state'
}

class MyTool extends StateNode {
	static override id = 'my-tool'
	static override initial = 'my-idle-state'
	// children 必须是返回类数组的函数;写成数组会在构造时抛 "children is not a function"
	static override children = () => [MyIdleState, MyPointingState]
}

事件处理

从根节点开始,每个节点将首先处理事件,然后将事件传递给其当前活动的子状态。此过程持续到以下任一情况:在没有任何子节点的状态下处理事件;或者以产生转换的方式处理事件。

父节点会在子节点前处理事件

class MyIdleState extends StateNode {
	static override id = 'my-idle-state'

	onPointerDown(info) {
		console.log('world')
	}
}

class MyTool extends StateNode {
	static override id = 'my-tool'
	static override initial = 'my-idle-state'
	static override children = () => [MyIdleState]

	onPointerDown(info) {
		console.log('hello')
	}
}

使用上面的例子,当前工具为MyTool的MyIdleState时,有点击事件onPointerDown发生时,将事件传递给MyTool,则将首先调用MyTool.onPointerDown的方法,然后再调用的MyIdleState.onPointerDown的方法。

class MyIdleState extends StateNode {
	static override id = 'my-idle-state'

	onPointerDown = (info) => {
		console.log("this won't run")
	}
}

class MyTool extends StateNode {
	static override id = 'my-tool'
	static override initial = 'my-idle-state'
	static override children = () => [MyIdleState]

	onPointerDown = (info) => {
		// 注意:'select' 是 pick 工具的子状态而非根工具,setCurrentTool('select') 会抛
		// "root - no child state exists with the id select"。应写 'pick' 或 'pick.select'。
		this.app.setCurrentTool('pick') // StateNode 内通过 this.app(或 this.editor)访问 App
	}
}

如果MyTool的onPointerDown处理程序产生了转换(改变了任何活动状态),那么事件链条将停止,并且不会处理该事件MyIdleState。

Path

您可以通过app.getStatePath 获取当前的活动状态“路径” 。

您可以通过 检查一条路径是否处于活动状态app.stateIsIn,或者通过 检查多条路径是否处于活动状态app.stateInAny。(这些都是 app 实例上的方法,不是静态方法。)

⚠️ 运行时限制:状态节点的当前子状态存放在响应式 ref 里,经代理读取时下层子状态取不到——getStatePath() 实际只返回顶层工具 id(如 'draw',未设置过任何工具时为 undefined),stateIsIn/stateInAny 传带点的子路径(如 'pick.idle'、'draw.drawpoint')恒为 false。判断子状态是否激活请用 app.getStateDescendant(path)?.getIsActive()(按 children 逐级查找,结果可靠)。setCurrentTool('draw.drawpoint') 这类带点路径的切换不受影响。

app.getStatePath() // 'draw'(只有顶层工具 id)

app.stateIsIn('draw') // true
app.stateInAny('draw', 'pick') // true(只用顶层 id)
app.getStateDescendant('draw.drawpoint')?.getIsActive() // 判断子状态

App.getCurrentToolId可以获取当前选择的工具。

常用工具

缺省工具

缺省工具下不会执行任何动作。

  • 缺省工具id: "default"
  • 子状态节点: "idle"

绘图工具

  • 缺省工具id: "draw"
子状态节点id 描述
idle 初始空闲
drawpoint 绘制点
drawline 绘制线
drawpolygon 绘制多边形
drawedit 编辑

拾取工具

  • 缺省工具id: "pick"
子状态节点id 描述
idle 初始空闲
select 选择对象

变换控制器工具

  • 缺省工具id: "transform"
子状态节点id 描述
idle 初始空闲
edit 控制物体变换

示例

绘制点

app.setCurrentTool("draw.drawpoint", {
    color: 0xffffff,
    size: 20
})

绘制线

app.setCurrentTool("draw.drawline", {
    color: Math.random() * 0xffffff,
    lineWidth: +((Math.random() * 6).toFixed(0)) + 1
})

绘制多边形

app.setCurrentTool("draw.drawpolygon", {
    color: Math.random() * 0xffffff
})

编辑形状

app.setCurrentTool("draw.drawedit")

控制物体变换

app.setCurrentTool("transform")

拾取对象

 let opts = {
    pickCallBack: (param) => {
        console.log(param.oldPick) // ⚠️ 运行时恒为 null(实现缺陷),需要上一次拾取结果请自行记录 curPick
        console.log(param.curPick)
        if (param.curPick) app.logInfo("拾取到了", "success")
    }
}
app.setCurrentTool("pick", opts)

取消操作

app.setCurrentTool("default")
let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
for(let i = 0; i < 3; i++) {
    let box = new THREE.BoxGeometry(Math.random() * 3 + 0.5, Math.random() * 3 + 0.5, Math.random() * 3 + 0.5)
    const material = new THREE.MeshStandardMaterial({
        color: vjmap3d.randColor()
    });
    let mesh = new THREE.Mesh(box, material);
    mesh.position.copy(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5})))
    let entity = vjmap3d.Entity.fromObject3d(mesh);
    entity.addTo(app);
}
const ui = await app.getConfigPane({ title: "工具", style: { width: "250px"}});
let gui = new vjmap3d.UiPanelJsonConfig();
gui.addButton("绘制点", () => {
    app.setCurrentTool("draw.drawpoint", {
        color: 0xffffff,
        size: 20
    })
})
gui.addButton("绘制线", () => {
    app.setCurrentTool("draw.drawline", {
        color: Math.random() * 0xffffff,
        lineWidth: +((Math.random() * 6).toFixed(0)) + 1
    })
})
gui.addButton("绘制多边形", () => {
    app.setCurrentTool("draw.drawpolygon", {
        color: Math.random() * 0xffffff
    })
})
gui.addButton("编辑形状", () => {
    app.setCurrentTool("draw.drawedit")
})
gui.addButton("控制物体变换", () => {
    app.setCurrentTool("transform")
})
gui.addButton("拾取对象", () => {
    let opts = {
        pickCallBack: (param) => {
            console.log(param.oldPick) // ⚠️ 运行时恒为 null
            console.log(param.curPick)
            if (param.curPick) app.logInfo("拾取到了", "success")
        }
    }
    app.setCurrentTool("pick", opts)
})
gui.addButton("取消操作", () => {
    app.setCurrentTool("default")
})
gui.toJson().forEach(c => ui.appendChild(c));

封装的工具函数

为了异步编程,简单代码逻辑,针对上面的工具,封装了如下常用工具函数:

方法名 描述
isFreeStatus 当前应该是否处于自由状态
isFreeStatusRef 自由状态响应式对象
pickPoint 拾取一个点
pickEntity 拾取实体
drawSelectBox 绘制一个选择框
actionDrawPoint 绘制一个点,获取点坐标
actionDrawLineSting 绘制一条线,获取线坐标
actionDrawPolygon 绘制一个多边形,获取多边形坐标
actionDrawEdit 编辑绘制的对象
actionPickSelect 选中一个实体对象,返回 {isCancel, info, select}。⚠️ 右键和 ESC 都结束选择且 isCancel=false(之前点选过对象时 select 仍保留);按 Enter 反而 isCancel=true
getDrawLayer 通过图层名称获取当前绘图层的实体
transformObject 对一个物体进行变换控制器操作。⚠️ ESC 与 Enter 都只是结束操作,不会还原已做的变换(ESC 仅在拖拽进行中时撤销当前这一次拖拽)

返回值形状:

方法 resolve 的结果
pickPoint(opts) {cancel: false, point, intersectObject, event};ESC 为 {cancel: true, reason: "escCancel"},rightClickCancel: true 时右键为 {cancel: true, reason: "rightClickCancel"}。beforeConfirm 不返回 true 时本次点击不确认、继续拾取
pickEntity(opts) {cancel, entities, clearHighlight}(ESC 时另有 reason: "escCancel")。⚠️ 传了 beforeConfirm/onResult 时,它们必须返回 true 才会结束——否则包括 ESC 在内的所有确认/取消都被拦下,Promise 永不 resolve
drawSelectBox(style?, shiftSelectBox?) 框选完成为屏幕坐标 [左上 Vector2, 右下 Vector2];未框选就结束(如 ESC)为 null
actionDrawPoint/LineSting/Polygon(opts) {isCancel, data}。⚠️ 默认 isAddToApp: false——只返回数据、不加入绘图层(而 setCurrentTool('draw.*') 默认会加入),需要保留请传 isAddToApp: true
actionDrawEdit(opts) {}(空对象,编辑结果直接写回 editEntity 的数据)
actionPickSelect(opts) {isCancel, info, select}(取消语义见上表)

示例

let svc = new vjmap3d.Service(env.serviceUrl, env.accessToken);
let app = new vjmap3d.App(svc, {
    container: "map", // 容器id
    stat: { show: true, left: "0" },
    scene: {
        gridHelper: { visible: true } // 是否显示坐标网格
    },
    camera: {  // 相机设置
        viewHelper: { enable: true, position: "leftBottom" } // 是否显示视角指示器
    }
})
let firstEntity;
for(let i = 0; i < 3; i++) {
    let box = new THREE.BoxGeometry(Math.random() * 3 + 0.5, Math.random() * 3 + 0.5, Math.random() * 3 + 0.5)
    const material = new THREE.MeshStandardMaterial({
        color: vjmap3d.randColor()
    });
    let mesh = new THREE.Mesh(box, material);
    mesh.position.copy(vjmap3d.toVector3(vjmap3d.randPoint3D({maxX: 5, maxY: 2, maxZ: 5, minX: -5, minY: -2, minZ: -5})))
    let entity = vjmap3d.Entity.fromObject3d(mesh);
    entity.addTo(app);
    if (i == 0) firstEntity = entity;
}
const ui = await app.getConfigPane({ title: "工具", style: { width: "250px"}});
let gui = new vjmap3d.UiPanelJsonConfig();
gui.addButton("第一个实体变换", async () => {
    app.logInfo("请按Enter或ESC结束操作(ESC不会撤销已做的变换)")
    let options = {
        target: firstEntity.node,
        clickNoSelfEntityExit: false,
        clickNoEntityExit: false,
        transformSize: 1,
    }
    let res = await app.transformObject(options)
    console.log(res)
    app.logInfo("实体变换操作完成", "success")
})
gui.addButton("选择多个进行变换", async () => {
    app.logInfo("请左键选择要变换的对象,右键结束确定选择")
    app.setCurrentTool(); // 取消之前的工具操作
    let res = await app.pickEntity({
        rightClickConfirm: true,
        highlightUseBoxHelper: true
    });
    if (res.entities?.length == 0) return;
    let entities = res.entities || [];
    let positions = entities.map(p => p.position.clone())
    let options = {
        target: entities[entities.length - 1].position,
        pressEKeyModeRotate: false,
        pressRKeyModeScale: false,
        clickEntityAttach: false,
        clickNoSelfEntityExit: false,
        clickNoEntityExit: false,
        rightClickConfirmExit: true,
        editAsCommand: true,
        dragCallBack: (param) => {
            if (param.isReset) {
                // 重新开始了
                positions = entities.map(p => p.position.clone())
            } else if (param.isOnceFinish && param.command) {
                // 一次完成,加入命令
                for(let n = 0; n < entities.length; n++) {
                    param.command.addCommand(new vjmap3d.SetPositionCommand(entities[n].node, entities[n].position, positions[n]));
                }
            }
            res.entities?.forEach(ent => {
              ent.position.add(param.deltaPosition)
            })
            for(let n = 0; n < entities.length; n++) {
                entities[n].position.copy(positions[n].clone().add(param.deltaPosition));
            }
        },
        // onExitCallBack(node, context) {
        //     if (node.id != "edit") return;
        //     app.setCurrentTool()
        // },
    }
    await app.transformObject(options)
    res.clearHighlight();
})
gui.addButton("自定义绘制多边形", async () => {
    app.logInfo("绘制过程中按 Ctrl+I 输入坐标,右键弹出菜单", 5000)
    let lastPoint;
    let drawRes = await app.actionDrawPolygon({
        isAddToApp: true,
        //pointMaxCount: 3, // 三个点结束
        updateCoordinate(point, isMoveing, data) {
            let text;
            text = `当前坐标:${point.x.toFixed(2)}, ${point.y.toFixed(2)}, ${point.z.toFixed(2)}`
            if (!isMoveing) {
                lastPoint = point;
            }
            if (lastPoint) {
                text += `
距离上一个点: ${point.distanceTo(lastPoint).toFixed(2)}` } app.popup2d.show() app.popup2d.setHTML(text) app.popup2d.setPosition(point) }, onKeyUp(info, context) { let isCtrl = vjmap3d.isCtrlKeyDown(info); let keyCode = vjmap3d.eventKeyCode(info) ; if (isCtrl && keyCode == 73) { // Ctrl + I let info = prompt("请输入坐标,格式x,y,z如 10,0,20"); let pts = info?.split(",").map(p => +p); // @ts-ignore context.addPoint(new THREE.Vector3().fromArray(pts)) } }, onContextMenu(info, context) { new vjmap3d.ContextMenu({ // @ts-ignore event: info.event.originalEvent, theme: "dark", //light width: "250px", items: [ { label: '取消', onClick: () => { context.cancel() } } ] }); }, onComplete() { app.popup2d.hide() } }) console.log(drawRes) if (drawRes.isCancel) { app.logInfo("取消绘制", "warn") } else { app.logInfo("绘制完成", "success") } }); gui.addButton("编辑绘制的第一个多边形", async () => { let res = await app.actionDrawEdit({ editEntity: app.getDrawLayer()?.polygons(), editIndex: 0, //editShowVertex: false, onUnEditEntity(ent, ctx) { ctx.complete() } }) console.log(res) app.logInfo("编辑完成", "success") }); gui.addButton("第一个多边形显示或隐藏", async () => { let polygons = app.getDrawLayer()?.polygons() let data = polygons.getData() data[0].hidden = !data[0].hidden; polygons.setData(data) }); gui.addButton("删除第一个多边形", async () => { let layer = app.getDrawLayer() // id 是实体内自增的字符串、删除后不复用,不要写死 "0",从当前数据里取 let first = layer.polygons()?.getData()[0] if (first) layer.deletePolygonData({ id: first.id, coordinates: [] }) }); gui.addButton("更新第一个多边形坐标", async () => { let layer = app.getDrawLayer() let first = layer.polygons()?.getData()[0] // update*Data 按 id 整项替换,未带的字段(颜色等)会被重置,先展开原数据 if (first) layer.updatePolygonData({ ...first, coordinates: [vjmap3d.randPoint3D(),vjmap3d.randPoint3D(), vjmap3d.randPoint3D()] }) }); gui.addButton("给定坐标进行编辑坐标", async () => { let data = []; data.push({ coordinates: [vjmap3d.randPoint3D(), vjmap3d.randPoint3D(), vjmap3d.randPoint3D()], color: "#00ff00", lineWidth: 3, dashScale: 2, dashOffset: 0, dashSize: 3, gapSize: 3, dashed: true }) let polylines = new vjmap3d.PolylinesEntity({ data: data }) polylines.addTo(app) await app.actionDrawEdit({ editEntity: polylines, queryEntities: [polylines], editIndex: 0, onUnEditEntity(ent, ctx) { ctx.complete() } }) console.log(polylines.getData()) polylines.remove(); app.logInfo("完成", "success") }); gui.addButton("选择对象进行删除", async () => { app.logInfo("选择要删除的对象,按右键进行删除,ESC取消") let escPressed = false; let res = await app.actionPickSelect({ // 默认 ESC 与右键都返回 isCancel=false(会照样删除),这里把 ESC/Enter 交给 onKeyUp 自行区分 enterEscConfirmExit: false, onKeyUp(info, ctx) { if (vjmap3d.isEscKey(info)) { escPressed = true; ctx.complete(); } }, onPointerMove(info, context, oldHover, newHover) { if (oldHover?.target == newHover?.target) return; if (oldHover?.target) oldHover.target.selected = false; if (newHover?.target) newHover.target.selected = true; }, }); if (escPressed || res.isCancel || !res.select) return; res.select.target.remove() }); gui.addButton("选择物体移动", async () => { app.logInfo("请选择物体,按右键结果选择,移动位置按 Ctrl+I 可输入坐标", 5000) let res = await app.actionPickSelect({ onPointerMove(info, context, oldHover, newHover) { if (oldHover?.target == newHover?.target) return; if (oldHover?.target) oldHover.target.selected = false; if (newHover?.target) newHover.target.selected = true; } }); if (res.isCancel || !res.select) return; let ent = res.select.target; let pos = ent.position.clone() let ptRes = await app.actionDrawPoint({ updateCoordinate(point, isMoveing, data, symbol) { ent.position.copy(point) },onKeyUp(info, context) { let isCtrl = vjmap3d.isCtrlKeyDown(info); let keyCode = vjmap3d.eventKeyCode(info) ; if (isCtrl && keyCode == 73) { // Ctrl + I let info = prompt("请输入坐标,格式x,y,z如 10,0,20"); let pts = info?.split(",").map(p => +p); // @ts-ignore context.addPoint(new THREE.Vector3().fromArray(pts)) } } }) ent.selected = false; if(ptRes.isCancel) { ent.position.copy(pos) app.logInfo("取消", "warn") } else { app.logInfo("完成", "success") } }); gui.addButton("绘制矩形", async () => { let rect = new vjmap3d.PolylinesEntity({ data: [] }) let ptRes1 = await app.actionDrawPoint(); console.log(ptRes1) if (ptRes1.isCancel) return let pt = ptRes1.data.position rect.setData([{ coordinates: [pt, pt, pt, pt, pt] }]) rect.addTo(app) let ptRes2 = await app.actionDrawPoint({ updateCoordinate(point, isMoveing, data, symbol) { let pt2 = [pt[0], pt[1], point.z] let pt3 = point.toArray(); let pt4 = [point.x, point.y, pt[2]] rect.setData([{ coordinates: [pt, pt2, pt3, pt4, pt] }]) }, }) console.log(ptRes2) let data = rect.getData()[0]; rect.remove() if (ptRes2.isCancel) { return } let drawLayer = app.getDrawLayer("", {}); drawLayer.addPolylineData(data) }); gui.toJson().forEach(c => ui.appendChild(c));

第一个实体变换

 app.logInfo("请按Enter或ESC结束操作(ESC不会撤销已做的变换)")
let options = {
    target: firstEntity.node,
    clickNoSelfEntityExit: false,
    clickNoEntityExit: false,
    transformSize: 1,
}
let res = await app.transformObject(options)
console.log(res)
app.logInfo("实体变换操作完成", "success")

选择多个进行变换

app.logInfo("请左键选择要变换的对象,右键结束确定选择")
app.setCurrentTool(); // 取消之前的工具操作
let res = await app.pickEntity({
    rightClickConfirm: true,
    highlightUseBoxHelper: true
});
if (res.entities?.length == 0) return;
let entities = res.entities || [];
let positions = entities.map(p => p.position.clone())
let options = {
    target: entities[entities.length - 1].position,
    pressEKeyModeRotate: false,
    pressRKeyModeScale: false,
    clickEntityAttach: false,
    clickNoSelfEntityExit: false,
    clickNoEntityExit: false,
    rightClickConfirmExit: true,
    editAsCommand: true,
    dragCallBack: (param) => {
        if (param.isReset) {
            // 重新开始了
            positions = entities.map(p => p.position.clone())
        } else if (param.isOnceFinish && param.command) {
            // 一次完成,加入命令
            for(let n = 0; n < entities.length; n++) {
                param.command.addCommand(new vjmap3d.SetPositionCommand(entities[n].node, entities[n].position, positions[n]));
            }
        }
        res.entities?.forEach(ent => {
            ent.position.add(param.deltaPosition)
        })
        for(let n = 0; n < entities.length; n++) {
            entities[n].position.copy(positions[n].clone().add(param.deltaPosition));
        }
    },
    // onExitCallBack(node, context) {
    //     if (node.id != "edit") return;
    //     app.setCurrentTool()
    // },
}
await app.transformObject(options)
res.clearHighlight();

自定义绘制多边形

app.logInfo("绘制过程中按 Ctrl+I 输入坐标,右键弹出菜单", 5000)
let lastPoint;
let drawRes = await app.actionDrawPolygon({
    isAddToApp: true,
    //pointMaxCount: 3, // 三个点结束
    updateCoordinate(point, isMoveing, data) {
        let text;
        text = `当前坐标:${point.x.toFixed(2)}, ${point.y.toFixed(2)}, ${point.z.toFixed(2)}`
        if (!isMoveing) {
            lastPoint = point;
        }
        if (lastPoint) {
            text += `<br/>距离上一个点: ${point.distanceTo(lastPoint).toFixed(2)}`
        }
        app.popup2d.show()
        app.popup2d.setHTML(text)
        app.popup2d.setPosition(point)
    },
    onKeyUp(info, context) {
        let isCtrl = vjmap3d.isCtrlKeyDown(info);
        let keyCode = vjmap3d.eventKeyCode(info) ;
        if (isCtrl && keyCode == 73) {
            // Ctrl + I
            let info = prompt("请输入坐标,格式x,y,z如 10,0,20");
            let pts = info?.split(",").map(p => +p);
            // @ts-ignore
            context.addPoint(new THREE.Vector3().fromArray(pts))
        }
    },
    onContextMenu(info, context) {
        new vjmap3d.ContextMenu({
            // @ts-ignore
            event: info.event.originalEvent,
            theme: "dark", //light
            width: "250px",
            items: [
                {
                    label: '取消',
                    onClick: () => {
                            context.cancel()
                    }
                }
            ]
        });
    },
    onComplete() {
        app.popup2d.hide()
    }
})
console.log(drawRes)
if (drawRes.isCancel) {
    app.logInfo("取消绘制", "warn")
} else {
    app.logInfo("绘制完成", "success")
}

编辑绘制的第一个多边形

let res = await app.actionDrawEdit({
    editEntity: app.getDrawLayer()?.polygons(),
    editIndex: 0,
    //editShowVertex: false,
    onUnEditEntity(ent, ctx) {
        ctx.complete()
    }
})
console.log(res)
app.logInfo("编辑完成", "success")

第一个多边形显示或隐藏

let  polygons = app.getDrawLayer()?.polygons()
let data = polygons.getData()
data[0].hidden = !data[0].hidden;
polygons.setData(data)

删除第一个多边形

let  layer = app.getDrawLayer()
// id 是实体内自增的字符串、删除后不复用,不要写死 "0",从当前数据里取
let first = layer.polygons()?.getData()[0]
if (first) layer.deletePolygonData({
    id: first.id,
    coordinates: []
})

更新第一个多边形坐标

let  layer = app.getDrawLayer()
let first = layer.polygons()?.getData()[0]
// update*Data 按 id 整项替换,未带的字段(颜色等)会被重置,先展开原数据
if (first) layer.updatePolygonData({
    ...first,
    coordinates: [vjmap3d.randPoint3D(),vjmap3d.randPoint3D(), vjmap3d.randPoint3D()]
})

给定坐标进行编辑坐标

let data = [];
data.push({
    coordinates: [vjmap3d.randPoint3D(), vjmap3d.randPoint3D(), vjmap3d.randPoint3D()],
    color: "#00ff00",
    lineWidth: 3,
    dashScale: 2,
    dashOffset: 0,
    dashSize: 3,
    gapSize: 3,
    dashed: true
})
let polylines = new vjmap3d.PolylinesEntity({
    data: data
})
polylines.addTo(app)
await app.actionDrawEdit({
    editEntity: polylines,
    queryEntities: [polylines],
    editIndex: 0,
    onUnEditEntity(ent, ctx) {
        ctx.complete()
    }
})
console.log(polylines.getData())
polylines.remove();
app.logInfo("完成", "success")

选择对象进行删除

app.logInfo("选择要删除的对象,按右键进行删除,ESC取消")
let escPressed = false;
let res = await app.actionPickSelect({
    // 默认 ESC 与右键都返回 isCancel=false(会照样删除),这里把 ESC/Enter 交给 onKeyUp 自行区分
    enterEscConfirmExit: false,
    onKeyUp(info, ctx) {
        if (vjmap3d.isEscKey(info)) { escPressed = true; ctx.complete(); }
    },
    onPointerMove(info, context, oldHover, newHover) {
        if (oldHover?.target == newHover?.target) return;
        if (oldHover?.target) oldHover.target.selected = false;
        if (newHover?.target) newHover.target.selected = true;
    },
});
if (escPressed || res.isCancel || !res.select) return;
res.select.target.remove()

选择物体移动

app.logInfo("请选择物体,按右键结果选择,移动位置按 Ctrl+I 可输入坐标", 5000)
let res = await app.actionPickSelect({
    onPointerMove(info, context, oldHover, newHover) {
        if (oldHover?.target == newHover?.target) return;
        if (oldHover?.target) oldHover.target.selected = false;
        if (newHover?.target) newHover.target.selected = true;
    }
});
if (res.isCancel || !res.select) return;
let ent = res.select.target;
let pos = ent.position.clone()
let ptRes = await app.actionDrawPoint({
    updateCoordinate(point, isMoveing, data, symbol) {
        ent.position.copy(point)
    },onKeyUp(info, context) {
        let isCtrl = vjmap3d.isCtrlKeyDown(info);
        let keyCode = vjmap3d.eventKeyCode(info) ;
        if (isCtrl && keyCode == 73) {
            // Ctrl + I
            let info = prompt("请输入坐标,格式x,y,z如 10,0,20");
            let pts = info?.split(",").map(p => +p);
            // @ts-ignore
            context.addPoint(new THREE.Vector3().fromArray(pts))
        }
    }
})
ent.selected = false;
if(ptRes.isCancel) {
    ent.position.copy(pos)
    app.logInfo("取消", "warn")
} else {
    app.logInfo("完成", "success")
}

绘制矩形

let rect = new vjmap3d.PolylinesEntity({
    data: []
})
let ptRes1 = await app.actionDrawPoint();
console.log(ptRes1)
if (ptRes1.isCancel) return
let pt = ptRes1.data.position
rect.setData([{
    coordinates: [pt, pt, pt, pt, pt]
}])
rect.addTo(app)
let ptRes2 = await app.actionDrawPoint({
    updateCoordinate(point, isMoveing, data, symbol) {
        let pt2 = [pt[0], pt[1], point.z]
        let pt3 = point.toArray();
        let pt4 = [point.x, point.y, pt[2]]
        rect.setData([{
            coordinates: [pt, pt2, pt3, pt4, pt]
        }])
    },
})
console.log(ptRes2)
let data = rect.getData()[0];
rect.remove()
if (ptRes2.isCancel) {
    return
}
let drawLayer = app.getDrawLayer("", {});
drawLayer.addPolylineData(data)