From 7f70cb15d035f0c233b9e62b9e43aa985317c908 Mon Sep 17 00:00:00 2001 From: luxiaotao1123 <t1341870251@163.com> Date: 星期一, 04 十一月 2024 10:22:45 +0800 Subject: [PATCH] # --- zy-acs-flow/src/map/tool.js | 405 +++++++++++++++++++++++++++++++++++++++------------------ 1 files changed, 276 insertions(+), 129 deletions(-) diff --git a/zy-acs-flow/src/map/tool.js b/zy-acs-flow/src/map/tool.js index 8f942a9..2993d4e 100644 --- a/zy-acs-flow/src/map/tool.js +++ b/zy-acs-flow/src/map/tool.js @@ -4,7 +4,14 @@ DEVICE_TYPE, DEVICE_Z_INDEX, AGV_STATUS_MODE, + DEVICE_SPRITE_TINT, + DEVICE_SELECTED_EFFECT_PADDING, + DEVICE_SELECTED_EFFECT_COLOR, + POINT_ROUTE_DIRECTION, + DEVICE_SPRITE_TINT_DARK, } from './constants'; +import { getRouteList } from './http'; +import PointRoute from "./PointRoute"; import shelf from '/map/shelf.svg'; import charge from '/map/charge.svg'; @@ -20,7 +27,8 @@ import ctuLoadedFullBattery from '/map/agv/ctuLoadedFullBattery.svg'; let app, mapContainer, themeMode; -let selectedSprite, effectTick, effectHalfCircle, effectRectangle; +let selectedSprite, effectCircle, effectTicker; +let tooltip; export function getApp() { return app; @@ -44,6 +52,21 @@ export function setThemeMode(param) { themeMode = param; + if (mapContainer) { + // mapContainer.children.forEach(child => { + // const deviceType = child.data?.type; + // if (deviceType) { + // if (themeMode === 'dark') { + // const tint = DEVICE_SPRITE_TINT_DARK[deviceType] + // if (tint) { + // child.tint = tint; + // } + // } else { + // DEVICE_SPRITE_TINT[deviceType] != null && (child.tint = DEVICE_SPRITE_TINT[deviceType]); + // } + // } + // }) + } } @@ -55,7 +78,7 @@ } } -export const generateSprite = (deviceType, source) => { +export const generateSprite = (deviceType, texture) => { let sprite; switch (deviceType) { case DEVICE_TYPE.SHELF: @@ -85,11 +108,11 @@ case DEVICE_TYPE.AGV: - if (source) { - sprite = new PIXI.Sprite(PIXI.Texture.from(source, { resourceOptions: { scale: 1 } })); + if (texture) { + sprite = new PIXI.Sprite(PIXI.Texture.from(texture, { resourceOptions: { scale: 1 } })); } - sprite.width = 300; - sprite.height = 500; + sprite.width = 400; + sprite.height = 700; sprite.zIndex = DEVICE_Z_INDEX.AGV; break case DEVICE_TYPE.POINT: @@ -101,6 +124,9 @@ default: break; } + const tintType = themeMode === 'dark' ? DEVICE_SPRITE_TINT_DARK : DEVICE_SPRITE_TINT; + tintType[deviceType] != null && (sprite.tint = tintType[deviceType]); + if (sprite && deviceType !== DEVICE_TYPE.AGV) { sprite.blendMode = PIXI.BLEND_MODES.MULTIPLY; } @@ -218,23 +244,30 @@ } export const beInsight = (sprite, setCurSprite) => { + if (!sprite?.data?.type) { return } + sprite.off('pointerup'); sprite.off('pointermove'); sprite.off('pointerdown'); + sprite.off('pointerover'); + sprite.off('pointerout'); sprite.off('click'); - sprite.on("click", onClick); - - function onClick(event) { + sprite.on("click", (event) => { setCurSprite(sprite); - } + }); + sprite.on('pointerover', onSpriteMouseOver); + sprite.on('pointermove', onSpriteMouseMove); + sprite.on('pointerout', onSpriteMouseOut); } export const beMovable = (sprite) => { sprite.off('pointerup'); sprite.off('pointermove'); sprite.off('pointerdown'); + sprite.off('pointerover'); + sprite.off('pointerout'); sprite.off('click'); sprite.on("pointerdown", onDragStart); @@ -269,15 +302,71 @@ } export const beSettings = (sprite, setSpriteSettings) => { + if (!sprite?.data?.type) { return } + sprite.off('pointerup'); sprite.off('pointermove'); sprite.off('pointerdown'); + sprite.off('pointerover'); + sprite.off('pointerout'); sprite.off('click'); - sprite.on("click", onClick); - - function onClick(event) { + sprite.on("click", (event) => { setSpriteSettings(sprite); + }); + + sprite.on('pointerover', onSpriteMouseOver); + sprite.on('pointermove', onSpriteMouseMove); + sprite.on('pointerout', onSpriteMouseOut); +} + +const createSpriteTooltip = (sprite) => { + const style = new PIXI.TextStyle({ + fontFamily: 'Roboto', + fontSize: 12, + fill: themeMode === 'light' ? '#000' : '#eee', + }); + const text = new PIXI.Text(`${sprite.data?.type} ${sprite.data?.no}`, style); + const background = new PIXI.Graphics(); + // shadow + background.beginFill(themeMode === 'light' ? '#000' : '#eee', 0.1); + background.drawRoundedRect(4, 4, text.width + 6, text.height + 6, 4); + background.endFill(); + // background + background.beginFill(themeMode === 'light' ? '#fff' : '#333', 1); + background.drawRoundedRect(0, 0, text.width + 8, text.height + 8, 4); + background.endFill(); + + text.x = 5; + text.y = 3; + background.addChild(text); + tooltip = new PIXI.Container(); + tooltip.name = "tooltip"; + tooltip.addChild(background); + return tooltip; +} + +function onSpriteMouseOver(event) { + if (tooltip) { + app?.stage.removeChild(tooltip); + } + tooltip = createSpriteTooltip(this);// this => sprite + tooltip.x = event.data.global.x + 10; + tooltip.y = event.data.global.y + 10; + app.stage.addChild(tooltip); +} + +function onSpriteMouseMove(event) { + if (tooltip) { + tooltip.x = event.data.global.x + 10; + tooltip.y = event.data.global.y + 10; + } +} + +function onSpriteMouseOut() { + if (tooltip && tooltip.parent) { + tooltip.parent?.removeChild(tooltip); + tooltip = null; } } @@ -330,124 +419,44 @@ } } - -// export const showSelectedEffect = (sprite) => { -// if (!sprite?.texture || !sprite?.texture?.valid) { -// return; -// } -// const { width, height } = sprite; -// const scale = sprite.scale.x; -// const sideLen = (Math.max(width, height) + 10) * scale; -// const color = themeMode === 'light' ? 0x273c75 : 0xffffff; - -// effectHalfCircle = new PIXI.Graphics(); -// effectHalfCircle.beginFill(color); -// effectHalfCircle.lineStyle(2 * scale, color); -// effectHalfCircle.arc(0, 0, sideLen, 0, Math.PI); -// effectHalfCircle.endFill(); -// effectHalfCircle.position.set(sprite.x, sprite.y); -// effectHalfCircle.scale.set(1 / scale); -// effectHalfCircle.zIndex = 9999; - -// effectRectangle = new PIXI.Graphics(); -// effectRectangle.lineStyle(5 * scale, color, 1); -// effectRectangle.drawRoundedRect(0, 0, sideLen, sideLen, 16 * scale); -// effectRectangle.endFill(); -// effectRectangle.mask = effectHalfCircle; -// effectRectangle.zIndex = 9999; - -// const scaledWidth = sideLen * (1 / scale); -// const scaledHeight = sideLen * (1 / scale); - -// effectRectangle.scale.set(1 / scale); -// effectRectangle.position.set(sprite.x - scaledWidth / 2, sprite.y - scaledHeight / 2); - -// mapContainer.addChild(effectRectangle); -// mapContainer.addChild(effectHalfCircle); - -// selectedSprite = sprite; - -// let phase = 0; -// effectTick = (delta) => { -// phase += delta / 10; -// phase %= (Math.PI * 2); -// if (effectHalfCircle) { -// effectHalfCircle.rotation = phase; -// } -// }; - -// app.ticker.add(effectTick); -// } - -// export const removeSelectedEffect = () => { -// if (effectTick) { -// app.ticker.remove(effectTick); -// } -// if (effectHalfCircle) { -// mapContainer.removeChild(effectHalfCircle); -// effectHalfCircle = null; -// } -// if (effectRectangle) { -// mapContainer.removeChild(effectRectangle); -// effectRectangle = null; -// } -// selectedSprite = null; -// } - -// export const updateEffect = (sprite) => { -// if (!sprite || sprite !== selectedSprite || !effectRectangle || !effectHalfCircle) { -// return -// } -// const { width, height } = sprite; -// const scale = sprite?.scale.x; -// const sideLen = (Math.max(width, height) + 10) * scale; -// const scaledWidth = sideLen * (1 / scale); -// const scaledHeight = sideLen * (1 / scale); - -// effectRectangle.position.set(sprite.x - scaledWidth / 2, sprite.y - scaledHeight / 2); - -// effectHalfCircle.position.set(sprite.x, sprite.y); -// } - -let effectCircle = null; -let effectTicker = null; - export const showSelectedEffect = (sprite) => { if (!sprite?.texture || !sprite?.texture?.valid) { return; } - // 濡傛灉宸茬粡鏈夌壒鏁堬紝鍏堢Щ闄� - removeSelectedEffect(); - const { width, height } = sprite; - const scale = sprite.scale.x; const maxDimension = Math.max(width, height); - const radius = (maxDimension / 2) * scale * 1.2; // 鏀惧ぇ1.2鍊嶏紝浣垮渾绋嶅ぇ浜巗prite + const radius = (maxDimension / 2) + (DEVICE_SELECTED_EFFECT_PADDING[sprite.data?.type] || 10); - const color = themeMode === 'light' ? 0x273c75 : 0xffffff; + let color; + const spriteEffectColor = DEVICE_SELECTED_EFFECT_COLOR[sprite.data?.type]; + if (spriteEffectColor) { + color = spriteEffectColor; + } else { + color = themeMode === 'light' ? '#747d8c' : '#718093'; + } - // 鍒涘缓鍦嗗舰鐗规晥 + const alpha = 1; + effectCircle = new PIXI.Graphics(); - effectCircle.lineStyle(2 * scale, color, 1); + effectCircle.beginFill(color, alpha); effectCircle.drawCircle(0, 0, radius); effectCircle.endFill(); effectCircle.position.set(sprite.x, sprite.y); - effectCircle.zIndex = sprite.zIndex - 1; // 纭繚涓嶉伄鎸prite + effectCircle.zIndex = -1; + effectCircle.blendMode = PIXI.BLEND_MODES.NORMAL; - // 灏嗙壒鏁堟坊鍔犲埌瀹瑰櫒涓� mapContainer.addChild(effectCircle); selectedSprite = sprite; - // 瀹炵幇鑴夊啿鏁堟灉 let pulseScale = 1; let scalingUp = true; effectTicker = (delta) => { - const pulseSpeed = 0.005 * delta; + const pulseSpeed = 0.003 * delta; if (scalingUp) { pulseScale += pulseSpeed; - if (pulseScale >= 1.2) { + if (pulseScale >= 1.1) { scalingUp = false; } } else { @@ -457,8 +466,7 @@ } } effectCircle.scale.set(pulseScale); - // 濮嬬粓璺熼殢sprite鐨勪綅缃� - effectCircle.position.set(selectedSprite.x, selectedSprite.y); + effectCircle.position.set(sprite.x, sprite.y); }; app.ticker.add(effectTicker); @@ -470,8 +478,7 @@ effectTicker = null; } if (effectCircle) { - mapContainer.removeChild(effectCircle); - effectCircle.destroy(); + mapContainer?.removeChild(effectCircle); effectCircle = null; } selectedSprite = null; @@ -481,9 +488,36 @@ if (!selectedSprite || !effectCircle) { return; } - // 鏇存柊鍦嗙殑浣嶇疆锛岀‘淇濊窡闅弒prite effectCircle.position.set(selectedSprite.x, selectedSprite.y); }; + +export const showRoutes = (curZone, setShowRoutes) => { + setShowRoutes(true); + getRouteList(curZone, (routeList) => { + routeList.forEach(route => { + const startPoint = querySprite(DEVICE_TYPE.POINT, route.startCodeStr); + const endPoint = querySprite(DEVICE_TYPE.POINT, route.endCodeStr); + const pointRoute = new PointRoute(POINT_ROUTE_DIRECTION[route.direction]); + pointRoute.setPoint(startPoint, endPoint); + pointRoute.clear(); + pointRoute.lineStyle(Math.max(7, 2 * (1 / mapContainer.scale.x)), themeMode === 'light' ? '#ced6e0' : '#535c68'); + pointRoute.moveTo(startPoint.position.x, startPoint.position.y); + pointRoute.lineTo(endPoint.position.x, endPoint.position.y); + pointRoute.alpha = 1; + mapContainer.addChild(pointRoute); + }) + }) +}; + +export const hideRoutes = (curZone, setShowRoutes) => { + setShowRoutes(false); + for (let i = mapContainer.children.length - 1; i >= 0; i--) { + const child = mapContainer.children[i]; + if (child?.type === DEVICE_TYPE.ROUTE) { + mapContainer.removeChild(child); + } + } +} export const multipleSelectEnhancer = (selectedSprites, setCurSprite, setBatchSprites) => { selectedSprites = selectedSprites.filter(sprite => sprite.data?.type); @@ -507,8 +541,39 @@ } export const rotationParseNum = (num) => { - return num * Math.PI / 180; + return num * (Math.PI / 180); } + +const animateRotation = (sprite, targetRotation, rotationOffset = 0) => { + if (!mapContainer || !sprite) { + return; + } + // origin + let currentRotation = sprite.rotation + rotationOffset; + // target + targetRotation += rotationOffset; + + // diff + let rotationDifference = targetRotation - currentRotation; + if (rotationDifference === 0) { + return; + } + rotationDifference = ((rotationDifference + Math.PI) % (2 * Math.PI)) - Math.PI; + + // destination + const endRotation = sprite.rotation + rotationDifference; + + new TWEEDLE.Tween(sprite) + .to({ rotation: endRotation }, 1000) + .easing(TWEEDLE.Easing.Linear.None) + .onUpdate(() => { + // agv + if (sprite.updateTextRotation) { + sprite.updateTextRotation(); + } + }) + .start(); +}; export const incrementSpriteNo = (str, incrementValue) => { const match = str.match(/(\D*)(\d+)/); @@ -585,30 +650,112 @@ // dynamic graphic ---------------- export const generateDynamicGraphic = (curZone, data, setCurSprite) => { - // console.log("ws", curZone, data); - - for (const agv of data.agvVos) { - showAgvSprite(curZone, agv, setCurSprite) + for (const agvVo of data.agvVos) { + showAgvSprite(curZone, agvVo, setCurSprite); + drawerAgvPath(curZone, agvVo); } - } -const showAgvSprite = (curZone, agv, setCurSprite) => { - const { agvNo, code, direction, backpack, battery, ...rest } = agv; - if (!code) { return } +const agvRotationOffsetDegrees = 0; +const agvRotationOffset = rotationParseNum(agvRotationOffsetDegrees); + +const showAgvSprite = (curZone, agvVo, setCurSprite) => { + const { agvNo, code, direction, battery, backpack } = agvVo; + if (!code) { return; } const codeSprite = querySprite(DEVICE_TYPE.POINT, code); - if (!codeSprite) { return } + if (!codeSprite) { return; } + + const targetRotation = rotationParseNum(direction); + const backpackCount = backpack?.filter(item => item.loaded === true).length || 0; + let agvSprite = querySprite(DEVICE_TYPE.AGV, agvNo); if (!agvSprite) { - agvSprite = generateSprite(DEVICE_TYPE.AGV, - generateAgvSpriteTexture(getAgvStatusMode(backpack.filter(item => item.loaded === true).length, battery)) - ); + const agvStatusMode = getAgvStatusMode(backpackCount, battery); + const agvTexture = generateAgvSpriteTexture(agvStatusMode); + agvSprite = generateSprite(DEVICE_TYPE.AGV, agvTexture); + initSprite(agvSprite, DEVICE_TYPE.AGV); + agvSprite.data.no = agvNo; + agvSprite.data.backpackCount = backpackCount; + agvSprite.data.battery = battery; + agvSprite.position.set(codeSprite.position.x, codeSprite.position.y); - agvSprite.rotation = rotationParseNum(direction); + agvSprite.rotation = targetRotation + agvRotationOffset; mapContainer.addChild(agvSprite); beInsight(agvSprite, setCurSprite); - // agv no on sprite + + // agvNo sprite + const agvText = new PIXI.Text(agvNo.toString(), { + fontSize: 60, + fill: 0x000000, + }); + agvText.anchor.set(0.5, 0.5); + agvText.position.set(0, 0); + agvSprite.addChild(agvText); + + agvSprite.updateTextRotation = () => { + if (agvText && agvSprite) { + agvText.rotation = -agvSprite.rotation; + } + }; + agvSprite.updateTextRotation(); + + } else { + const prevBackpackCount = agvSprite.data.backpackCount; + const prevBattery = agvSprite.data.battery; + + if (backpackCount !== prevBackpackCount || battery !== prevBattery) { + const agvStatusMode = getAgvStatusMode(backpackCount, battery); + const agvTexture = generateAgvSpriteTexture(agvStatusMode); + agvSprite.texture = PIXI.Texture.from(agvTexture, { resourceOptions: { scale: 1 } }); + // update backpackCount and battery + agvSprite.data.backpackCount = backpackCount; + agvSprite.data.battery = battery; + } + + animateRotation(agvSprite, targetRotation, agvRotationOffset); } -} \ No newline at end of file + + new TWEEDLE.Tween(agvSprite.position) + .to({ + x: codeSprite.position.x, + y: codeSprite.position.y + }, 1000) + .easing(TWEEDLE.Easing.Linear.None) + .start(); +} + +const drawerAgvPath = (curZone, agvVo) => { + if (!mapContainer) { + return; + } + const { agvNo, code: curCode, dynamicRoute } = agvVo; + + const agvPathName = 'agvPath-' + agvNo; + let agvPath = mapContainer.getChildByName(agvPathName); + if (agvPath) { + mapContainer.removeChild(agvPath); + } + agvPath = new PIXI.Graphics(); + agvPath.name = agvPathName; + agvPath.lineStyle(Math.max(20, 4 * (1 / mapContainer.scale.x)), 0x2f68ac, 0.8); + agvPath.zIndex = DEVICE_Z_INDEX.DYNAMIC_ROUTE; + // agvPath.blendMode = PIXI.BLEND_MODES.NORMAL; + + let firstNode = true; + for (let i = Math.max(0, dynamicRoute.indexOf(curCode)); i < dynamicRoute.length; i++) { + const node = dynamicRoute[i]; + const codeSprite = querySprite(DEVICE_TYPE.POINT, node); + if (!codeSprite) { continue }; + const { x, y } = codeSprite.position; + if (firstNode) { + agvPath.moveTo(x, y); + firstNode = false; + } else { + agvPath.lineTo(x, y); + } + } + + mapContainer.addChild(agvPath); +} -- Gitblit v1.9.1