From 662d7260ca0690f32f39180265c8eee9ec632d7c Mon Sep 17 00:00:00 2001 From: joylink_zhaoerwei Date: Wed, 9 Oct 2024 18:52:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=AC=E5=85=B1=E7=9A=84=E7=BA=BF=E6=9D=A1?= =?UTF-8?q?=E5=A4=87=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 2 +- package.json | 2 +- .../draw-app/IscsDrawProperties.vue | 12 +- .../draw-app/properties/LineProperty.vue | 58 +++ .../draw-app/properties/RectProperty.vue | 5 +- .../draw-app/templates/LineTemplate.vue | 42 ++ src/drawApp/commonApp.ts | 11 + src/drawApp/graphics/LineInteraction.ts | 68 +++ src/graphics/line/Line.ts | 108 ++++ src/graphics/line/LineDrawAssistant.ts | 480 ++++++++++++++++++ src/graphics/line/LineUtils.ts | 42 ++ src/graphics/lineGraphic/LineGraphic.ts | 51 ++ src/layouts/IscsDrawLayout.vue | 4 +- src/protos/iscs_graphic_data.ts | 258 +++++++++- src/protos/sync_data_message.ts | 2 +- 15 files changed, 1135 insertions(+), 10 deletions(-) create mode 100644 src/components/draw-app/properties/LineProperty.vue create mode 100644 src/components/draw-app/templates/LineTemplate.vue create mode 100644 src/drawApp/graphics/LineInteraction.ts create mode 100644 src/graphics/line/Line.ts create mode 100644 src/graphics/line/LineDrawAssistant.ts create mode 100644 src/graphics/line/LineUtils.ts create mode 100644 src/graphics/lineGraphic/LineGraphic.ts diff --git a/package-lock.json b/package-lock.json index f58eb17..3043e29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "@quasar/extras": "^1.16.4", "axios": "^1.2.1", "google-protobuf": "^3.21.4", - "jl-graphic": "git+http://120.46.212.6:3000/joylink/graphic-pixi.git#v0.1.15", + "jl-graphic": "git+http://120.46.212.6:3000/joylink/graphic-pixi.git#v0.1.18", "js-base64": "^3.7.5", "pinia": "^2.0.11", "quasar": "^2.16.0", diff --git a/package.json b/package.json index acfc897..e89c56b 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@quasar/extras": "^1.16.4", "axios": "^1.2.1", "google-protobuf": "^3.21.4", - "jl-graphic": "git+https://gitea.joylink.club/joylink/graphic-pixi.git#v0.1.17", + "jl-graphic": "git+https://gitea.joylink.club/joylink/graphic-pixi.git#v0.1.18", "js-base64": "^3.7.5", "pinia": "^2.0.11", "quasar": "^2.16.0", diff --git a/src/components/draw-app/IscsDrawProperties.vue b/src/components/draw-app/IscsDrawProperties.vue index 0ff94f3..94d894a 100644 --- a/src/components/draw-app/IscsDrawProperties.vue +++ b/src/components/draw-app/IscsDrawProperties.vue @@ -6,7 +6,11 @@
{{ drawStore.drawGraphicName + ' 模板' }}
- + + + @@ -29,6 +33,9 @@ + @@ -50,6 +57,9 @@ import cctvButtonProperty from './properties/CCTV/CCTVButtonProperty.vue'; import { CCTVButton } from 'src/graphics/CCTV/cctvButton/CCTVButton'; import RectProperty from './properties/RectProperty.vue'; import { Rect } from 'src/graphics/rect/Rect'; +import LineTemplate from './templates/LineTemplate.vue'; +import LineProperty from './properties/LineProperty.vue'; +import { Line } from 'src/graphics/line/Line'; const drawStore = useDrawStore(); diff --git a/src/components/draw-app/properties/LineProperty.vue b/src/components/draw-app/properties/LineProperty.vue new file mode 100644 index 0000000..87dcf27 --- /dev/null +++ b/src/components/draw-app/properties/LineProperty.vue @@ -0,0 +1,58 @@ + + + diff --git a/src/components/draw-app/properties/RectProperty.vue b/src/components/draw-app/properties/RectProperty.vue index dcb9842..a86742e 100644 --- a/src/components/draw-app/properties/RectProperty.vue +++ b/src/components/draw-app/properties/RectProperty.vue @@ -1,6 +1,6 @@ + + + + diff --git a/src/drawApp/commonApp.ts b/src/drawApp/commonApp.ts index d8a2123..62cc9ad 100644 --- a/src/drawApp/commonApp.ts +++ b/src/drawApp/commonApp.ts @@ -23,6 +23,9 @@ import { common } from 'src/protos/common'; import { errorNotify, successNotify } from 'src/utils/CommonNotify'; import { saveDraft } from 'src/api/DraftApi'; import { sync_data_message } from 'src/protos/sync_data_message'; +import { LineDraw } from 'src/graphics/line/LineDrawAssistant'; +import { Line, LineTemplate } from 'src/graphics/line/Line'; +import { LineData } from './graphics/LineInteraction'; const UndoOptions: MenuItemOptions = { name: '撤销', @@ -50,6 +53,7 @@ export function initCommonDrawApp(app: IDrawApp) { new ArrowDraw(app, new ArrowTemplate(new ArrowData())); new TextContentDraw(app, new TextContentTemplate(new IscsTextData())); new RectDraw(app, new RectTemplate(new RectData())); + new LineDraw(app, new LineTemplate(new LineData())); // 画布右键菜单 app.registerMenu(DefaultCanvasMenu); @@ -77,6 +81,7 @@ interface ICommonStorage { arrows: iscsGraphicData.Arrow[]; iscsTexts: iscsGraphicData.IscsText[]; rects: iscsGraphicData.Rect[]; + lines: iscsGraphicData.Line[]; } export function loadCommonDrawDatas(storage: ICommonStorage): GraphicData[] { const datas: GraphicData[] = []; @@ -90,6 +95,9 @@ export function loadCommonDrawDatas(storage: ICommonStorage): GraphicData[] { storage.rects.forEach((rect) => { datas.push(new RectData(rect)); }); + storage.lines.forEach((line) => { + datas.push(new LineData(line)); + }); return datas; } @@ -112,6 +120,9 @@ export function saveCommonDrawDatas(app: IDrawApp, storage: ICommonStorage) { } else if (g instanceof Rect) { const rectData = g.saveData(); storage.rects.push((rectData as RectData).data); + } else if (g instanceof Line) { + const lineData = g.saveData(); + storage.lines.push((lineData as LineData).data); } }); diff --git a/src/drawApp/graphics/LineInteraction.ts b/src/drawApp/graphics/LineInteraction.ts new file mode 100644 index 0000000..b2f518c --- /dev/null +++ b/src/drawApp/graphics/LineInteraction.ts @@ -0,0 +1,68 @@ +import * as pb_1 from 'google-protobuf'; +import { GraphicDataBase } from './GraphicDataBase'; +import { ILineData, Line } from 'src/graphics/line/Line'; +import { IPointData } from 'pixi.js'; +import { iscsGraphicData } from 'src/protos/iscs_graphic_data'; +import { common } from 'src/protos/common'; + +export class LineData extends GraphicDataBase implements ILineData { + constructor(data?: iscsGraphicData.Line) { + let line; + if (!data) { + line = new iscsGraphicData.Line({ + common: GraphicDataBase.defaultCommonInfo(Line.Type), + }); + } else { + line = data; + } + super(line); + } + public get data(): iscsGraphicData.Line { + return this.getData(); + } + get code(): string { + return this.data.code; + } + set code(v: string) { + this.data.code = v; + } + get points(): IPointData[] { + return this.data.points; + } + set points(points: IPointData[]) { + this.data.points = points.map((p) => new common.Point({ x: p.x, y: p.y })); + } + get isCurve(): boolean { + return this.data.isCurve; + } + set isCurve(v: boolean) { + this.data.isCurve = v; + } + get segmentsCount(): number { + return this.data.segmentsCount; + } + set segmentsCount(v: number) { + this.data.segmentsCount = v; + } + get lineWidth(): number { + return this.data.lineWidth; + } + set lineWidth(v: number) { + this.data.lineWidth = v; + } + get lineColor(): string { + return this.data.lineColor; + } + set lineColor(v: string) { + this.data.lineColor = v; + } + clone(): LineData { + return new LineData(this.data.cloneMessage()); + } + copyFrom(data: LineData): void { + pb_1.Message.copyInto(data.data, this.data); + } + eq(other: LineData): boolean { + return pb_1.Message.equals(this.data, other.data); + } +} diff --git a/src/graphics/line/Line.ts b/src/graphics/line/Line.ts new file mode 100644 index 0000000..387687d --- /dev/null +++ b/src/graphics/line/Line.ts @@ -0,0 +1,108 @@ +import { IPointData } from 'pixi.js'; +import { + GraphicData, + JlGraphic, + JlGraphicTemplate, + VectorText, + convertToBezierParams, + ILineGraphic, +} from 'jl-graphic'; + +import { LineGraphic } from '../lineGraphic/LineGraphic'; + +export interface ILineData extends GraphicData { + get code(): string; // 编号 + set code(v: string); + get points(): IPointData[]; // 线坐标点 + set points(points: IPointData[]); + get isCurve(): boolean; // 是否曲线 + set isCurve(v: boolean); + get segmentsCount(): number; // 曲线分段数 + set segmentsCount(v: number); + get lineWidth(): number; // 线宽 + set lineWidth(v: number); + get lineColor(): string; // 线色 + set lineColor(v: string); + clone(): ILineData; + copyFrom(data: ILineData): void; + eq(other: ILineData): boolean; +} + +export class Line extends JlGraphic implements ILineGraphic { + static Type = 'Line'; + lineGraphic: LineGraphic; + labelGraphic: VectorText; + childLines: Line[] = []; + + constructor() { + super(Line.Type); + this.lineGraphic = new LineGraphic(); + this.transformSave = true; + this.addChild(this.lineGraphic); + } + + doRepaint() { + if (this.datas.points.length < 2) { + throw new Error('Link坐标数据异常'); + } + this.lineGraphic.clear(); + this.lineGraphic.isCurve = this.datas.isCurve; + if (this.lineGraphic.isCurve) { + this.lineGraphic.segmentsCount = this.datas.segmentsCount; + } + this.lineGraphic.points = this.datas.points; + this.lineGraphic.lineStyle(this.datas.lineWidth, this.datas.lineColor); + this.lineGraphic.paint(); + } + + getVerticesList(): IPointData[] { + if (this.datas.isCurve) { + return [ + this.datas.points[0], + ...convertToBezierParams(this.datas.points).map((param) => param.p2), + ]; + } else { + return this.datas.points; + } + } + getStartPoint() { + return this.datas.points[0]; + } + getEndPoint(): IPointData { + return this.datas.points[this.datas.points.length - 1]; + } + + get datas(): ILineData { + return this.getDatas(); + } + + get linePoints(): IPointData[] { + return this.datas.points; + } + set linePoints(points: IPointData[]) { + const old = this.datas.clone(); + old.points = points; + this.updateData(old); + } +} + +export interface ILineTemplateProperty { + isCurve: boolean; + segmentsCount: number; +} + +export class LineTemplate + extends JlGraphicTemplate + implements ILineTemplateProperty +{ + isCurve = false; + segmentsCount = 10; + constructor(dataTemplate: ILineData) { + super(Line.Type, { dataTemplate }); + } + new() { + const g = new Line(); + g.loadData(this.datas); + return g; + } +} diff --git a/src/graphics/line/LineDrawAssistant.ts b/src/graphics/line/LineDrawAssistant.ts new file mode 100644 index 0000000..e6384e9 --- /dev/null +++ b/src/graphics/line/LineDrawAssistant.ts @@ -0,0 +1,480 @@ +import { + DisplayObject, + FederatedMouseEvent, + Graphics, + IHitArea, + IPointData, + Point, +} from 'pixi.js'; +import { + DraggablePoint, + GraphicDrawAssistant, + GraphicInteractionPlugin, + GraphicTransformEvent, + IDrawApp, + IGraphicApp, + JlGraphic, + KeyListener, + VectorText, + calculateMirrorPoint, + convertToBezierParams, + linePoint, + pointPolygon, + BezierCurveEditPlugin, + IEditPointOptions, + ILineGraphic, + PolylineEditPlugin, + addWayPoint, + clearWayPoint, + ContextMenu, + MenuItemOptions, + AbsorbableLine, + AbsorbablePosition, + AbsorbablePoint, + distance, +} from 'jl-graphic'; + +import { ILineData, Line, LineTemplate } from './Line'; +import { getWayLineIndex } from './LineUtils'; + +export const LineConsts = { + lineColor: '#f00', + lineWidth: 3, +}; + +export class LineDraw extends GraphicDrawAssistant { + points: Point[] = []; + graphic = new Graphics(); + + keyQListener = new KeyListener({ + value: 'KeyQ', + global: true, + onPress: () => { + if (this.points.length === 0) { + this.graphicTemplate.isCurve = true; + } + }, + }); + keyZListener = new KeyListener({ + value: 'KeyZ', + global: true, + onPress: () => { + if (this.points.length === 0) { + this.graphicTemplate.isCurve = false; + } + }, + }); + + constructor(app: IDrawApp, template: LineTemplate) { + super(app, template, 'sym_o_timeline', '线条Line'); + this.container.addChild(this.graphic); + + LinePointEditPlugin.init(app, this); + } + + bind(): void { + super.bind(); + this.app.addKeyboardListener(this.keyQListener, this.keyZListener); + } + unbind(): void { + super.unbind(); + this.app.removeKeyboardListener(this.keyQListener, this.keyZListener); + } + + onLeftDown(e: FederatedMouseEvent): void { + const { x, y } = this.toCanvasCoordinates(e.global); + const p = new Point(x, y); + if (this.graphicTemplate.isCurve) { + if (this.points.length == 0) { + this.points.push(p); + } else { + this.points.push(p, p.clone()); + } + } else { + this.points.push(p); + } + } + + onLeftUp(e: FederatedMouseEvent): void { + const template = this.graphicTemplate; + if (template.isCurve) { + const mp = this.toCanvasCoordinates(e.global); + if (this.points.length === 1) { + this.points.push(new Point(mp.x, mp.y)); + } else if (this.points.length > 1) { + const cp2 = this.points[this.points.length - 2]; + const p = this.points[this.points.length - 1]; + cp2.copyFrom(calculateMirrorPoint(p, mp)); + this.points.push(mp); + } + } + } + + onRightClick(): void { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + + onEsc(): void { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + + redraw(p: Point): void { + if (this.points.length < 1) return; + const template = this.graphicTemplate; + this.graphic.clear(); + this.graphic.lineStyle(LineConsts.lineWidth, LineConsts.lineColor); + + const ps = [...this.points]; + if (template.isCurve) { + if (ps.length === 1) { + this.graphic.moveTo(ps[0].x, ps[0].y); + this.graphic.lineTo(p.x, p.y); + } else { + if ((ps.length + 1) % 3 === 0) { + ps.push(p.clone(), p.clone()); + } else { + const cp = ps[ps.length - 2]; + const p1 = ps[ps.length - 1]; + const mp = calculateMirrorPoint(p1, p); + cp.copyFrom(mp); + } + const bps = convertToBezierParams(ps); + bps.forEach((bp) => { + this.graphic.drawBezierCurve( + bp.p1, + bp.p2, + bp.cp1, + bp.cp2, + template.segmentsCount + ); + }); + } + } else { + ps.push(p); + ps.forEach((p, i) => { + if (i !== 0) { + this.graphic.lineTo(p.x, p.y); + } else { + this.graphic.moveTo(p.x, p.y); + } + }); + } + } + + prepareData(data: ILineData): boolean { + const template = this.graphicTemplate; + if ( + (!template.isCurve && this.points.length < 2) || + (template.isCurve && this.points.length < 4) + ) { + console.log('Line绘制因点不够取消绘制'); + return false; + } + if (template.isCurve) { + this.points.pop(); + } + data.isCurve = template.isCurve; + data.segmentsCount = template.segmentsCount; + data.points = this.points; + data.lineColor = LineConsts.lineColor; + data.lineWidth = LineConsts.lineWidth; + + return true; + } + + clearCache(): void { + this.points = []; + this.graphic.clear(); + } +} + +export class LineGraphicHitArea implements IHitArea { + line: Line; + constructor(line: Line) { + this.line = line; + } + contains(x: number, y: number): boolean { + if (this.line.datas.isCurve) { + const bps = convertToBezierParams(this.line.datas.points); + for (let i = 0; i < bps.length; i++) { + const bp = bps[i]; + if ( + pointPolygon( + { x, y }, + [bp.p1, bp.cp1, bp.cp2, bp.p2], + this.line.datas.lineWidth + ) + ) { + return true; + } + } + } else { + for (let i = 1; i < this.line.datas.points.length; i++) { + const p1 = this.line.datas.points[i - 1]; + const p2 = this.line.datas.points[i]; + if (linePoint(p1, p2, { x, y }, this.line.datas.lineWidth)) { + return true; + } + } + } + return false; + } +} + +function buildAbsorbablePositions( + line: Line, + dp: DraggablePoint +): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + const changePoints = line.localToCanvasPoints(...line.datas.points); + for (let i = 0; i < changePoints.length; i++) { + if (changePoints[i].equals(dp)) { + const { width, height } = line.getGraphicApp().canvas; + if (i == 0 || i == changePoints.length - 1) { + const ps = + i == 0 ? changePoints[1] : changePoints[changePoints.length - 2]; + const x = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y }); + const y = new AbsorbableLine({ x: ps.x, y: 0 }, { x: ps.x, y: height }); + aps.push(x, y); + } else { + const generateAxisPoint = [changePoints[i - 1], changePoints[i + 1]]; + generateAxisPoint.forEach((point) => { + const x = new AbsorbableLine( + { x: 0, y: point.y }, + { x: width, y: point.y } + ); + const y = new AbsorbableLine( + { x: point.x, y: 0 }, + { x: point.x, y: height } + ); + aps.push(x, y); + }); + } + break; + } + } + if (line instanceof Line) { + const lines = line.queryStore.queryByType(Line.Type); + lines.forEach((item) => { + if (item.id !== line.id) { + item.localToCanvasPoints(...item.datas.points).forEach((p) => { + aps.push(new AbsorbablePoint(p)); + }); + } + }); + } + return aps; +} + +export function onEditPointCreate(g: ILineGraphic, dp: DraggablePoint): void { + const line = g as Line; + dp.on('transformstart', (e: GraphicTransformEvent) => { + if (e.isShift()) { + line.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(line, dp), + }); + } + }); +} + +class LinePolylineEditPlugin extends PolylineEditPlugin { + static Name = 'LinePolylineEditPlugin'; + labels: VectorText[] = []; + constructor(g: ILineGraphic, options?: IEditPointOptions) { + super(g, options); + this.name = LinePolylineEditPlugin.Name; + } +} + +class LineBazierCurveEditPlugin extends BezierCurveEditPlugin { + static Name = 'LineBazierCurveEditPlugin'; + labels: VectorText[] = []; + constructor(g: ILineGraphic, options?: IEditPointOptions) { + super(g, options); + this.name = LineBazierCurveEditPlugin.Name; + } +} + +export const addWaypointConfig: MenuItemOptions = { + name: '添加路径点', +}; +export const clearWaypointsConfig: MenuItemOptions = { + name: '清除所有路径点', +}; +const LineEditMenu: ContextMenu = ContextMenu.init({ + name: '区段编辑菜单', + groups: [ + { + items: [addWaypointConfig, clearWaypointsConfig], + }, + ], +}); + +export class LinePointEditPlugin extends GraphicInteractionPlugin { + static Name = 'LinePointDrag'; + drawAssistant: LineDraw; + + constructor(app: IGraphicApp, da: LineDraw) { + super(LinePointEditPlugin.Name, app); + this.drawAssistant = da; + app.registerMenu(LineEditMenu); + } + static init(app: IGraphicApp, da: LineDraw) { + return new LinePointEditPlugin(app, da); + } + filter(...grahpics: JlGraphic[]): Line[] | undefined { + return grahpics.filter((g) => g.type == Line.Type) as Line[]; + } + bind(g: Line): void { + g.lineGraphic.eventMode = 'static'; + g.lineGraphic.cursor = 'pointer'; + g.lineGraphic.hitArea = new LineGraphicHitArea(g); + g.transformSave = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnselected, this); + g.on('transformstart', this.onDragMove, this); + g.on('_rightclick', this.onContextMenu, this); + } + unbind(g: Line): void { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnselected, this); + g.off('transformstart', this.onDragMove, this); + g.off('_rightclick', this.onContextMenu, this); + } + + onContextMenu(e: FederatedMouseEvent) { + const target = e.target as DisplayObject; + const line = target.getGraphic() as Line; + this.app.updateSelected(line); + const p = line.screenToLocalPoint(e.global); + addWaypointConfig.handler = () => { + const linePoints = line.linePoints; + const { start, end } = getWayLineIndex(linePoints, p); + addWayPoint(line, false, start, end, p); + }; + clearWaypointsConfig.handler = () => { + clearWayPoint(line, false); + }; + LineEditMenu.open(e.global); + } + + onSelected(g: DisplayObject): void { + const line = g as Line; + if (line.datas.isCurve) { + let lep = line.getAssistantAppend( + LineBazierCurveEditPlugin.Name + ); + if (!lep) { + lep = new LineBazierCurveEditPlugin(line, { + onEditPointCreate, + }); + line.addAssistantAppend(lep); + } + lep.showAll(); + } else { + let lep = line.getAssistantAppend( + LinePolylineEditPlugin.Name + ); + if (!lep) { + lep = new LinePolylineEditPlugin(line, { onEditPointCreate }); + line.addAssistantAppend(lep); + } + lep.showAll(); + } + } + onUnselected(g: DisplayObject): void { + const line = g as Line; + if (line.datas.isCurve) { + const lep = line.getAssistantAppend( + LineBazierCurveEditPlugin.Name + ); + if (lep) { + lep.hideAll(); + } + } else { + const lep = line.getAssistantAppend( + LinePolylineEditPlugin.Name + ); + if (lep) { + lep.hideAll(); + } + } + } + onDragMove(e: GraphicTransformEvent) { + const line = e.target as Line; + this.app.setOptions({ + absorbablePositions: buildDragMoveAbsorbablePositions(line), + }); + } +} + +type dragType = Line; +function buildDragMoveAbsorbablePositions( + target: dragType +): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + + const lines = target.queryStore.queryByType(Line.Type); + lines.forEach((line) => { + if (line.id !== target.id) { + line.localToCanvasPoints(...line.datas.points).forEach((p) => { + aps.push(new DragMoveAbsorbablePoint(p)); //区段端点 + }); + } + }); + + return aps; +} + +class DragMoveAbsorbablePoint extends AbsorbablePoint { + moveTarget: + | { + position: IPointData; + portPos: IPointData[]; + } + | undefined; + constructor(point: IPointData, absorbRange = 15) { + super(point, absorbRange); + } + tryAbsorb(...dragTargets: dragType[]): void { + const dragTarget = dragTargets[0]; + if (this.moveTarget == undefined) { + this.moveTarget = { + position: dragTarget + .getGraphicApp() + .toCanvasCoordinates(dragTarget.getGlobalPosition()), + portPos: [ + dragTarget.localToCanvasPoint(dragTarget.getStartPoint()), + dragTarget.localToCanvasPoint(dragTarget.getEndPoint()), + ], + }; + } + dragTarget + .localToCanvasPoints(...dragTarget.datas.points) + .forEach((p, i) => { + if ( + distance(this._point.x, this._point.y, p.x, p.y) < this.absorbRange && + this.moveTarget + ) { + dragTarget.updatePositionByCanvasPosition( + new Point( + this.moveTarget.position.x + + this._point.x - + this.moveTarget.portPos[i].x, + this.moveTarget.position.y + + this._point.y - + this.moveTarget.portPos[i].y + ) + ); + } + }); + } +} diff --git a/src/graphics/line/LineUtils.ts b/src/graphics/line/LineUtils.ts new file mode 100644 index 0000000..4c0a2bd --- /dev/null +++ b/src/graphics/line/LineUtils.ts @@ -0,0 +1,42 @@ +import { calculateDistanceFromPointToLine, calculateFootPointFromPointToLine } from "jl-graphic"; +import { IPointData } from 'pixi.js'; + +//获取所选线段的索引 +export function getWayLineIndex( + points: IPointData[], + p: IPointData +): { start: number; end: number } { + let start = 0; + let end = 0; + let minDistance = 0; + for (let i = 1; i < points.length; i++) { + const sp = points[i - 1]; + const ep = points[i]; + let distance = calculateDistanceFromPointToLine(sp, ep, p); + distance = Math.round(distance * 100) / 100; + if (i == 1) { + minDistance = distance; + } + if (distance == minDistance) { + const minX = Math.min(sp.x, ep.x); + const maxX = Math.max(sp.x, ep.x); + const minY = Math.min(sp.y, ep.y); + const maxY = Math.max(sp.y, ep.y); + const point = calculateFootPointFromPointToLine(sp, ep, p); + if ( + point.x >= minX && + point.x <= maxX && + point.y >= minY && + point.y <= maxY + ) { + start = i - 1; + } + } + if (distance < minDistance) { + minDistance = distance; + start = i - 1; + } + } + end = start + 1; + return { start, end }; +} diff --git a/src/graphics/lineGraphic/LineGraphic.ts b/src/graphics/lineGraphic/LineGraphic.ts new file mode 100644 index 0000000..80000bd --- /dev/null +++ b/src/graphics/lineGraphic/LineGraphic.ts @@ -0,0 +1,51 @@ +import { Graphics, IPointData } from 'pixi.js'; +import { assertBezierPoints, convertToBezierParams } from 'jl-graphic'; + +export class LineGraphic extends Graphics { + static Type = 'LineGraphic'; + private _points: IPointData[] = []; + public get points(): IPointData[] { + return this._points; + } + public set points(value: IPointData[]) { + if (!this.isCurve) { + if (value.length < 2) { + throw Error('Polyline must have at least 2 points'); + } + } else { + assertBezierPoints(value); + } + this._points = value; + } + + private _segmentsCount = 10; + public get segmentsCount(): number { + return this._segmentsCount; + } + public set segmentsCount(value: number) { + if (value < 1) { + throw Error('segmentsCount must be at least 1'); + } + this._segmentsCount = value; + } + + isCurve = false; + + constructor() { + super(); + } + + paint() { + if (this.isCurve) { + const bps = convertToBezierParams(this.points); + bps.forEach((bp) => { + this.drawBezierCurve(bp.p1, bp.p2, bp.cp1, bp.cp2, this.segmentsCount); + }); + } else { + this.moveTo(this.points[0].x, this.points[0].y); + for (let i = 1; i < this.points.length; i++) { + this.lineTo(this.points[i].x, this.points[i].y); + } + } + } +} diff --git a/src/layouts/IscsDrawLayout.vue b/src/layouts/IscsDrawLayout.vue index d8786f1..0d22442 100644 --- a/src/layouts/IscsDrawLayout.vue +++ b/src/layouts/IscsDrawLayout.vue @@ -258,6 +258,7 @@ import { saveDrawDatas } from 'src/drawApp/iscsApp'; import { saveDrawToServer } from 'src/drawApp/commonApp'; import { sync_data_message } from 'src/protos/sync_data_message'; import { useAuthStore } from 'src/stores/auth-store'; +import { Line } from 'src/graphics/line/Line'; const $q = useQuasar(); const route = useRoute(); @@ -365,6 +366,7 @@ onMounted(() => { Arrow.Type, TextContent.Type, Rect.Type, + Line.Type, CCTVButton.Type, ]; drawAssistantsTypes.forEach((type) => { @@ -554,7 +556,7 @@ function handleRecordData(op) { function forceReloadDate() { const drawApp = drawStore.getDrawApp(); const graphics = drawApp.queryStore.getAllGraphics(); - graphics.forEach((graphic) => (graphic.visible = false)); + drawApp.deleteGraphics(...graphics); drawApp.updateSelected(); drawApp.forceReload(); } diff --git a/src/protos/iscs_graphic_data.ts b/src/protos/iscs_graphic_data.ts index 804705c..0606f14 100644 --- a/src/protos/iscs_graphic_data.ts +++ b/src/protos/iscs_graphic_data.ts @@ -721,6 +721,214 @@ export namespace iscsGraphicData { return Rect.deserialize(bytes); } } + export class Line extends pb_1.Message { + #one_of_decls: number[][] = []; + constructor(data?: any[] | { + common?: dependency_1.common.CommonInfo; + code?: string; + points?: dependency_1.common.Point[]; + isCurve?: boolean; + segmentsCount?: number; + lineWidth?: number; + lineColor?: string; + }) { + super(); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3], this.#one_of_decls); + if (!Array.isArray(data) && typeof data == "object") { + if ("common" in data && data.common != undefined) { + this.common = data.common; + } + if ("code" in data && data.code != undefined) { + this.code = data.code; + } + if ("points" in data && data.points != undefined) { + this.points = data.points; + } + if ("isCurve" in data && data.isCurve != undefined) { + this.isCurve = data.isCurve; + } + if ("segmentsCount" in data && data.segmentsCount != undefined) { + this.segmentsCount = data.segmentsCount; + } + if ("lineWidth" in data && data.lineWidth != undefined) { + this.lineWidth = data.lineWidth; + } + if ("lineColor" in data && data.lineColor != undefined) { + this.lineColor = data.lineColor; + } + } + } + get common() { + return pb_1.Message.getWrapperField(this, dependency_1.common.CommonInfo, 1) as dependency_1.common.CommonInfo; + } + set common(value: dependency_1.common.CommonInfo) { + pb_1.Message.setWrapperField(this, 1, value); + } + get has_common() { + return pb_1.Message.getField(this, 1) != null; + } + get code() { + return pb_1.Message.getFieldWithDefault(this, 2, "") as string; + } + set code(value: string) { + pb_1.Message.setField(this, 2, value); + } + get points() { + return pb_1.Message.getRepeatedWrapperField(this, dependency_1.common.Point, 3) as dependency_1.common.Point[]; + } + set points(value: dependency_1.common.Point[]) { + pb_1.Message.setRepeatedWrapperField(this, 3, value); + } + get isCurve() { + return pb_1.Message.getFieldWithDefault(this, 4, false) as boolean; + } + set isCurve(value: boolean) { + pb_1.Message.setField(this, 4, value); + } + get segmentsCount() { + return pb_1.Message.getFieldWithDefault(this, 5, 0) as number; + } + set segmentsCount(value: number) { + pb_1.Message.setField(this, 5, value); + } + get lineWidth() { + return pb_1.Message.getFieldWithDefault(this, 6, 0) as number; + } + set lineWidth(value: number) { + pb_1.Message.setField(this, 6, value); + } + get lineColor() { + return pb_1.Message.getFieldWithDefault(this, 7, "") as string; + } + set lineColor(value: string) { + pb_1.Message.setField(this, 7, value); + } + static fromObject(data: { + common?: ReturnType; + code?: string; + points?: ReturnType[]; + isCurve?: boolean; + segmentsCount?: number; + lineWidth?: number; + lineColor?: string; + }): Line { + const message = new Line({}); + if (data.common != null) { + message.common = dependency_1.common.CommonInfo.fromObject(data.common); + } + if (data.code != null) { + message.code = data.code; + } + if (data.points != null) { + message.points = data.points.map(item => dependency_1.common.Point.fromObject(item)); + } + if (data.isCurve != null) { + message.isCurve = data.isCurve; + } + if (data.segmentsCount != null) { + message.segmentsCount = data.segmentsCount; + } + if (data.lineWidth != null) { + message.lineWidth = data.lineWidth; + } + if (data.lineColor != null) { + message.lineColor = data.lineColor; + } + return message; + } + toObject() { + const data: { + common?: ReturnType; + code?: string; + points?: ReturnType[]; + isCurve?: boolean; + segmentsCount?: number; + lineWidth?: number; + lineColor?: string; + } = {}; + if (this.common != null) { + data.common = this.common.toObject(); + } + if (this.code != null) { + data.code = this.code; + } + if (this.points != null) { + data.points = this.points.map((item: dependency_1.common.Point) => item.toObject()); + } + if (this.isCurve != null) { + data.isCurve = this.isCurve; + } + if (this.segmentsCount != null) { + data.segmentsCount = this.segmentsCount; + } + if (this.lineWidth != null) { + data.lineWidth = this.lineWidth; + } + if (this.lineColor != null) { + data.lineColor = this.lineColor; + } + return data; + } + serialize(): Uint8Array; + serialize(w: pb_1.BinaryWriter): void; + serialize(w?: pb_1.BinaryWriter): Uint8Array | void { + const writer = w || new pb_1.BinaryWriter(); + if (this.has_common) + writer.writeMessage(1, this.common, () => this.common.serialize(writer)); + if (this.code.length) + writer.writeString(2, this.code); + if (this.points.length) + writer.writeRepeatedMessage(3, this.points, (item: dependency_1.common.Point) => item.serialize(writer)); + if (this.isCurve != false) + writer.writeBool(4, this.isCurve); + if (this.segmentsCount != 0) + writer.writeInt32(5, this.segmentsCount); + if (this.lineWidth != 0) + writer.writeInt32(6, this.lineWidth); + if (this.lineColor.length) + writer.writeString(7, this.lineColor); + if (!w) + return writer.getResultBuffer(); + } + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Line { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Line(); + while (reader.nextField()) { + if (reader.isEndGroup()) + break; + switch (reader.getFieldNumber()) { + case 1: + reader.readMessage(message.common, () => message.common = dependency_1.common.CommonInfo.deserialize(reader)); + break; + case 2: + message.code = reader.readString(); + break; + case 3: + reader.readMessage(message.points, () => pb_1.Message.addToRepeatedWrapperField(message, 3, dependency_1.common.Point.deserialize(reader), dependency_1.common.Point)); + break; + case 4: + message.isCurve = reader.readBool(); + break; + case 5: + message.segmentsCount = reader.readInt32(); + break; + case 6: + message.lineWidth = reader.readInt32(); + break; + case 7: + message.lineColor = reader.readString(); + break; + default: reader.skipField(); + } + } + return message; + } + serializeBinary(): Uint8Array { + return this.serialize(); + } + static deserializeBinary(bytes: Uint8Array): Line { + return Line.deserialize(bytes); + } + } export class CCTVButton extends pb_1.Message { #one_of_decls: number[][] = []; constructor(data?: any[] | { @@ -1318,9 +1526,10 @@ export namespace iscsGraphicData { iscsTexts?: IscsText[]; rects?: Rect[]; cctvButtons?: CCTVButton[]; + lines?: Line[]; }) { super(); - pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3, 4, 5, 6], this.#one_of_decls); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3, 4, 5, 6, 7], this.#one_of_decls); if (!Array.isArray(data) && typeof data == "object") { if ("stationName" in data && data.stationName != undefined) { this.stationName = data.stationName; @@ -1340,6 +1549,9 @@ export namespace iscsGraphicData { if ("cctvButtons" in data && data.cctvButtons != undefined) { this.cctvButtons = data.cctvButtons; } + if ("lines" in data && data.lines != undefined) { + this.lines = data.lines; + } } } get stationName() { @@ -1381,6 +1593,12 @@ export namespace iscsGraphicData { set cctvButtons(value: CCTVButton[]) { pb_1.Message.setRepeatedWrapperField(this, 6, value); } + get lines() { + return pb_1.Message.getRepeatedWrapperField(this, Line, 7) as Line[]; + } + set lines(value: Line[]) { + pb_1.Message.setRepeatedWrapperField(this, 7, value); + } static fromObject(data: { stationName?: string; canvas?: ReturnType; @@ -1388,6 +1606,7 @@ export namespace iscsGraphicData { iscsTexts?: ReturnType[]; rects?: ReturnType[]; cctvButtons?: ReturnType[]; + lines?: ReturnType[]; }): CCTVOfStationControlStorage { const message = new CCTVOfStationControlStorage({}); if (data.stationName != null) { @@ -1408,6 +1627,9 @@ export namespace iscsGraphicData { if (data.cctvButtons != null) { message.cctvButtons = data.cctvButtons.map(item => CCTVButton.fromObject(item)); } + if (data.lines != null) { + message.lines = data.lines.map(item => Line.fromObject(item)); + } return message; } toObject() { @@ -1418,6 +1640,7 @@ export namespace iscsGraphicData { iscsTexts?: ReturnType[]; rects?: ReturnType[]; cctvButtons?: ReturnType[]; + lines?: ReturnType[]; } = {}; if (this.stationName != null) { data.stationName = this.stationName; @@ -1437,6 +1660,9 @@ export namespace iscsGraphicData { if (this.cctvButtons != null) { data.cctvButtons = this.cctvButtons.map((item: CCTVButton) => item.toObject()); } + if (this.lines != null) { + data.lines = this.lines.map((item: Line) => item.toObject()); + } return data; } serialize(): Uint8Array; @@ -1455,6 +1681,8 @@ export namespace iscsGraphicData { writer.writeRepeatedMessage(5, this.rects, (item: Rect) => item.serialize(writer)); if (this.cctvButtons.length) writer.writeRepeatedMessage(6, this.cctvButtons, (item: CCTVButton) => item.serialize(writer)); + if (this.lines.length) + writer.writeRepeatedMessage(7, this.lines, (item: Line) => item.serialize(writer)); if (!w) return writer.getResultBuffer(); } @@ -1482,6 +1710,9 @@ export namespace iscsGraphicData { case 6: reader.readMessage(message.cctvButtons, () => pb_1.Message.addToRepeatedWrapperField(message, 6, CCTVButton.deserialize(reader), CCTVButton)); break; + case 7: + reader.readMessage(message.lines, () => pb_1.Message.addToRepeatedWrapperField(message, 7, Line.deserialize(reader), Line)); + break; default: reader.skipField(); } } @@ -1502,9 +1733,10 @@ export namespace iscsGraphicData { arrows?: Arrow[]; iscsTexts?: IscsText[]; rects?: Rect[]; + lines?: Line[]; }) { super(); - pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3, 4, 5], this.#one_of_decls); + pb_1.Message.initialize(this, Array.isArray(data) ? data : [], 0, -1, [3, 4, 5, 6], this.#one_of_decls); if (!Array.isArray(data) && typeof data == "object") { if ("stationName" in data && data.stationName != undefined) { this.stationName = data.stationName; @@ -1521,6 +1753,9 @@ export namespace iscsGraphicData { if ("rects" in data && data.rects != undefined) { this.rects = data.rects; } + if ("lines" in data && data.lines != undefined) { + this.lines = data.lines; + } } } get stationName() { @@ -1556,12 +1791,19 @@ export namespace iscsGraphicData { set rects(value: Rect[]) { pb_1.Message.setRepeatedWrapperField(this, 5, value); } + get lines() { + return pb_1.Message.getRepeatedWrapperField(this, Line, 6) as Line[]; + } + set lines(value: Line[]) { + pb_1.Message.setRepeatedWrapperField(this, 6, value); + } static fromObject(data: { stationName?: string; canvas?: ReturnType; arrows?: ReturnType[]; iscsTexts?: ReturnType[]; rects?: ReturnType[]; + lines?: ReturnType[]; }): FASPlatformAlarmStorage { const message = new FASPlatformAlarmStorage({}); if (data.stationName != null) { @@ -1579,6 +1821,9 @@ export namespace iscsGraphicData { if (data.rects != null) { message.rects = data.rects.map(item => Rect.fromObject(item)); } + if (data.lines != null) { + message.lines = data.lines.map(item => Line.fromObject(item)); + } return message; } toObject() { @@ -1588,6 +1833,7 @@ export namespace iscsGraphicData { arrows?: ReturnType[]; iscsTexts?: ReturnType[]; rects?: ReturnType[]; + lines?: ReturnType[]; } = {}; if (this.stationName != null) { data.stationName = this.stationName; @@ -1604,6 +1850,9 @@ export namespace iscsGraphicData { if (this.rects != null) { data.rects = this.rects.map((item: Rect) => item.toObject()); } + if (this.lines != null) { + data.lines = this.lines.map((item: Line) => item.toObject()); + } return data; } serialize(): Uint8Array; @@ -1620,6 +1869,8 @@ export namespace iscsGraphicData { writer.writeRepeatedMessage(4, this.iscsTexts, (item: IscsText) => item.serialize(writer)); if (this.rects.length) writer.writeRepeatedMessage(5, this.rects, (item: Rect) => item.serialize(writer)); + if (this.lines.length) + writer.writeRepeatedMessage(6, this.lines, (item: Line) => item.serialize(writer)); if (!w) return writer.getResultBuffer(); } @@ -1644,6 +1895,9 @@ export namespace iscsGraphicData { case 5: reader.readMessage(message.rects, () => pb_1.Message.addToRepeatedWrapperField(message, 5, Rect.deserialize(reader), Rect)); break; + case 6: + reader.readMessage(message.lines, () => pb_1.Message.addToRepeatedWrapperField(message, 6, Line.deserialize(reader), Line)); + break; default: reader.skipField(); } } diff --git a/src/protos/sync_data_message.ts b/src/protos/sync_data_message.ts index ec6694c..e3da8fa 100644 --- a/src/protos/sync_data_message.ts +++ b/src/protos/sync_data_message.ts @@ -198,7 +198,7 @@ export namespace sync_data_message { pb_1.Message.setField(this, 2, value); } get data() { - return pb_1.Message.getFieldWithDefault(this, 3, new Uint8Array()) as Uint8Array; + return pb_1.Message.getFieldWithDefault(this, 3, new Uint8Array(0)) as Uint8Array; } set data(value: Uint8Array) { pb_1.Message.setField(this, 3, value);