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 @@
+
+
+
+
+
+
+
+
+ {
+ lineModel.lineColor = val;
+ onUpdate();
+ }
+ "
+ />
+
+
+
+
+
+
+
+
+
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);