From fd50783b117dda02072f5b21da94820e0ad03979 Mon Sep 17 00:00:00 2001 From: Yuan Date: Tue, 9 Jan 2024 14:11:27 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8C=BA=E6=AE=B5=E6=9A=82=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../packages/Section/bjrtss/Section.d.ts | 3 +- components/packages/Section/bjrtss/Section.js | 3 +- .../packages/Section/common/Section.d.ts | 3 + components/packages/Section/common/Section.js | 4 +- .../Section/common/SectionDrawAssistant.d.ts | 40 ++ .../Section/common/SectionDrawAssistant.js | 433 ++++++++++++ components/packages/Turnout/Turnout.d.ts | 35 +- components/packages/Turnout/Turnout.js | 23 +- src/packages/Section/bjrtss/Section.ts | 13 +- src/packages/Section/common/Section.ts | 4 +- .../Section/common/SectionDrawAssistant.ts | 629 ++++++++++++++++++ src/packages/Turnout/Turnout.ts | 51 +- 12 files changed, 1226 insertions(+), 15 deletions(-) create mode 100644 components/packages/Section/common/SectionDrawAssistant.d.ts create mode 100644 components/packages/Section/common/SectionDrawAssistant.js create mode 100644 src/packages/Section/common/SectionDrawAssistant.ts diff --git a/components/packages/Section/bjrtss/Section.d.ts b/components/packages/Section/bjrtss/Section.d.ts index ee718ba..8a9a334 100644 --- a/components/packages/Section/bjrtss/Section.d.ts +++ b/components/packages/Section/bjrtss/Section.d.ts @@ -1,4 +1,5 @@ -import { Section as SectionBase } from "../common/Section"; +import { Section as SectionBase } from '../common/Section'; export declare class Section extends SectionBase { constructor(); } +export { SectionTemplate } from '../common/Section'; diff --git a/components/packages/Section/bjrtss/Section.js b/components/packages/Section/bjrtss/Section.js index 110d4e4..406ca04 100644 --- a/components/packages/Section/bjrtss/Section.js +++ b/components/packages/Section/bjrtss/Section.js @@ -1,9 +1,10 @@ import { Section as Section$1 } from '../common/Section.js'; +export { SectionTemplate } from '../common/Section.js'; const displayConfig = { lineColor: '#5578b6', occupiedColor: '#f00', - lineWidth: 5 + lineWidth: 5, }; class Section extends Section$1 { constructor() { diff --git a/components/packages/Section/common/Section.d.ts b/components/packages/Section/common/Section.d.ts index 26ab829..c1e522e 100644 --- a/components/packages/Section/common/Section.d.ts +++ b/components/packages/Section/common/Section.d.ts @@ -34,6 +34,7 @@ export interface SectionDisplayConfig { occupiedColor: string; lineWidth: number; } +export declare const defaultDisplayConfig: SectionDisplayConfig; export declare class Section extends JlGraphic { static Type: string; lineGraphic: SectionGraphic; @@ -60,6 +61,8 @@ export declare class Section extends JlGraphic { loadRelations(): void; } export declare class SectionTemplate extends JlGraphicTemplate
{ + isCurve: boolean; + segmentsCount: number; constructor(dataTemplate: ISectionData, stateTemplate?: ISectionState); new(): Section; } diff --git a/components/packages/Section/common/Section.js b/components/packages/Section/common/Section.js index 9648a88..3700389 100644 --- a/components/packages/Section/common/Section.js +++ b/components/packages/Section/common/Section.js @@ -213,6 +213,8 @@ let Section$1 = class Section extends JlGraphic { } }; class SectionTemplate extends JlGraphicTemplate { + isCurve = false; + segmentsCount = 10; constructor(dataTemplate, stateTemplate) { super(Section$1.Type, { dataTemplate, @@ -227,4 +229,4 @@ class SectionTemplate extends JlGraphicTemplate { } } -export { Section$1 as Section, SectionTemplate, SectionType }; +export { Section$1 as Section, SectionTemplate, SectionType, defaultDisplayConfig }; diff --git a/components/packages/Section/common/SectionDrawAssistant.d.ts b/components/packages/Section/common/SectionDrawAssistant.d.ts new file mode 100644 index 0000000..0ad1ca6 --- /dev/null +++ b/components/packages/Section/common/SectionDrawAssistant.d.ts @@ -0,0 +1,40 @@ +import { GraphicDrawAssistant, GraphicInteractionPlugin, IDrawApp, IGraphicApp, JlGraphic, KeyListener, MenuItemOptions } from 'jl-graphic'; +import { ISectionData, SectionTemplate } from './Section'; +import { Point, Graphics, type FederatedMouseEvent, type IHitArea, type DisplayObject } from 'pixi.js'; +import { Section } from '../bjrtss/Section'; +export declare class SectionDraw extends GraphicDrawAssistant { + points: Point[]; + graphic: Graphics; + keyQListener: KeyListener; + keyZListener: KeyListener; + constructor(app: IDrawApp, template: SectionTemplate); + bind(): void; + unbind(): void; + onLeftDown(e: FederatedMouseEvent): void; + onLeftUp(e: FederatedMouseEvent): void; + onRightClick(): void; + onEsc(): void; + redraw(p: Point): void; + prepareData(data: ISectionData): boolean; + clearCache(): void; +} +export declare class SectionGraphicHitArea implements IHitArea { + section: Section; + constructor(section: Section); + contains(x: number, y: number): boolean; +} +export declare const addWaypointConfig: MenuItemOptions; +export declare const clearWaypointsConfig: MenuItemOptions; +export declare const splitSectionConfig: MenuItemOptions; +export declare class SectionPointEditPlugin extends GraphicInteractionPlugin
{ + static Name: string; + drawAssistant: SectionDraw; + constructor(app: IGraphicApp, da: SectionDraw); + static init(app: IGraphicApp, da: SectionDraw): SectionPointEditPlugin; + filter(...grahpics: JlGraphic[]): Section[] | undefined; + bind(g: Section): void; + unbind(g: Section): void; + onContextMenu(e: FederatedMouseEvent): void; + onSelected(g: DisplayObject): void; + onUnselected(g: DisplayObject): void; +} diff --git a/components/packages/Section/common/SectionDrawAssistant.js b/components/packages/Section/common/SectionDrawAssistant.js new file mode 100644 index 0000000..82122fc --- /dev/null +++ b/components/packages/Section/common/SectionDrawAssistant.js @@ -0,0 +1,433 @@ +import { ContextMenu, GraphicDrawAssistant, KeyListener, calculateMirrorPoint, convertToBezierParams, pointPolygon, linePoint, GraphicInteractionPlugin, getWaypointRangeIndex, addWayPoint, clearWayPoint, PolylineEditPlugin, VectorText, AppConsts, BezierCurveEditPlugin, AbsorbableLine, AbsorbablePoint } from 'jl-graphic'; +import { SectionType, defaultDisplayConfig } from './Section.js'; +import { Graphics, Point } from 'pixi.js'; +import { Turnout } from '../../Turnout/Turnout.js'; +import { Section } from '../bjrtss/Section.js'; + +class SectionDraw extends GraphicDrawAssistant { + points = []; + graphic = new Graphics(); + keyQListener = new KeyListener({ + value: 'KeyQ', + global: true, + onPress: () => { + if (this.points.length === 0) ; + }, + }); + keyZListener = new KeyListener({ + value: 'KeyZ', + global: true, + onPress: () => { + if (this.points.length === 0) ; + }, + }); + constructor(app, template) { + super(app, template, 'sym_o_timeline', '区段Section'); + this.container.addChild(this.graphic); + SectionPointEditPlugin.init(app, this); + } + bind() { + super.bind(); + this.app.addKeyboardListener(this.keyQListener, this.keyZListener); + } + unbind() { + super.unbind(); + this.app.removeKeyboardListener(this.keyQListener, this.keyZListener); + } + onLeftDown(e) { + 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) { + 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() { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + onEsc() { + if (this.points.length < 2) { + this.finish(); + return; + } + this.createAndStore(true); + } + redraw(p) { + if (this.points.length < 1) + return; + const template = this.graphicTemplate; + this.graphic.clear(); + this.graphic.lineStyle(3, '#555'); + 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) { + const template = this.graphicTemplate; + if ((!template.isCurve && this.points.length < 2) || + (template.isCurve && this.points.length < 4)) { + console.log('Section绘制因点不够取消绘制'); + return false; + } + if (template.isCurve) { + this.points.pop(); + } + data.isCurve = template.isCurve; + data.segmentsCount = template.segmentsCount; + data.points = this.points; + data.code = 'G000'; + return true; + } + clearCache() { + this.points = []; + this.graphic.clear(); + } +} +class SectionGraphicHitArea { + section; + constructor(section) { + this.section = section; + } + contains(x, y) { + if (this.section.datas.sectionType === SectionType.TurnoutPhysical) { + return false; + } + if (this.section.datas.isCurve) { + const bps = convertToBezierParams(this.section.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], defaultDisplayConfig.lineWidth)) { + return true; + } + } + } + else { + for (let i = 1; i < this.section.datas.points.length; i++) { + const p1 = this.section.datas.points[i - 1]; + const p2 = this.section.datas.points[i]; + if (linePoint(p1, p2, { x, y }, defaultDisplayConfig.lineWidth)) { + return true; + } + } + } + return false; + } +} +function buildAbsorbablePositions(section) { + const aps = []; + const sections = section.queryStore.queryByType(Section.Type); + sections.forEach((other) => { + const [ps, pe] = [ + other.localToCanvasPoint(other.getStartPoint()), + other.localToCanvasPoint(other.getEndPoint()), + ]; + const { width, height } = section.getGraphicApp().canvas; + const xs = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y }); + const ys = new AbsorbableLine({ x: ps.x, y: 0 }, { x: ps.x, y: height }); + const xe = new AbsorbableLine({ x: 0, y: pe.y }, { x: width, y: pe.y }); + const ye = new AbsorbableLine({ x: pe.x, y: 0 }, { x: pe.x, y: height }); + aps.push(xs, ys, xe, ye); + }); + const turnouts = section.queryStore.queryByType(Turnout.Type); + turnouts.forEach((turnout) => { + turnout.getPortPoints().forEach((points) => { + turnout.localToCanvasPoints(...points).forEach((p) => { + aps.push(new AbsorbablePoint(p)); + }); + }); + }); + return aps; +} +function onEditPointCreate(g, dp) { + const section = g; + dp.on('transformstart', (e) => { + if (e.isShift()) { + section.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(section), + }); + } + }); +} +class SectionPolylineEditPlugin extends PolylineEditPlugin { + static Name = 'SectionPolylineEditPlugin'; + labels = []; + constructor(g, options) { + super(g, options); + this.name = SectionPolylineEditPlugin.Name; + this.initLabels(); + } + initLabels() { + this.labels = ['A', 'B'].map((str) => { + const vc = new VectorText(str, { fill: AppConsts.assistantElementColor }); + vc.setVectorFontSize(14); + vc.anchor.set(0.5); + return vc; + }); + this.addChild(...this.labels); + this.updateEditedPointsPosition(); + } + updateEditedPointsPosition() { + super.updateEditedPointsPosition(); + this.labels[0]?.position.set(this.editedPoints[0].x, this.editedPoints[0].y + 10); + this.labels[1]?.position.set(this.editedPoints[this.editedPoints.length - 1].x, this.editedPoints[this.editedPoints.length - 1].y + 10); + } +} +class SectionBazierCurveEditPlugin extends BezierCurveEditPlugin { + static Name = 'SectionBazierCurveEditPlugin'; + labels = []; + constructor(g, options) { + super(g, options); + this.name = SectionBazierCurveEditPlugin.Name; + this.initLabels(); + } + initLabels() { + this.labels = [new VectorText('A'), new VectorText('B')]; + this.labels.forEach((label) => { + label.setVectorFontSize(14); + this.addChild(label); + }); + this.updateEditedPointsPosition(); + } + updateEditedPointsPosition() { + super.updateEditedPointsPosition(); + this.labels[0]?.position.set(this.editedPoints[0].x, this.editedPoints[0].y + 10); + this.labels[1]?.position.set(this.editedPoints[this.editedPoints.length - 1].x, this.editedPoints[this.editedPoints.length - 1].y + 10); + } +} +const addWaypointConfig = { + name: '添加路径点', +}; +const clearWaypointsConfig = { + name: '清除所有路径点', +}; +const splitSectionConfig = { + name: '拆分', + // disabled: true, +}; +const SectionEditMenu = ContextMenu.init({ + name: '区段编辑菜单', + groups: [ + { + items: [addWaypointConfig, clearWaypointsConfig], + }, + { + items: [splitSectionConfig], + }, + ], +}); +class SectionPointEditPlugin extends GraphicInteractionPlugin { + static Name = 'SectionPointDrag'; + drawAssistant; + constructor(app, da) { + super(SectionPointEditPlugin.Name, app); + this.drawAssistant = da; + app.registerMenu(SectionEditMenu); + } + static init(app, da) { + return new SectionPointEditPlugin(app, da); + } + filter(...grahpics) { + return grahpics.filter((g) => g.type == Section.Type); + } + bind(g) { + g.lineGraphic.eventMode = 'static'; + g.lineGraphic.cursor = 'pointer'; + g.lineGraphic.hitArea = new SectionGraphicHitArea(g); + g.transformSave = true; + g.labelGraphic.eventMode = 'static'; + g.labelGraphic.cursor = 'pointer'; + g.labelGraphic.selectable = true; + g.labelGraphic.draggable = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnselected, this); + // g.on('_rightclick', this.onContextMenu, this); + } + unbind(g) { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnselected, this); + // g.off('_rightclick', this.onContextMenu, this); + } + onContextMenu(e) { + const target = e.target; + const section = target.getGraphic(); + this.app.updateSelected(section); + if (section.datas.sectionType === SectionType.TurnoutPhysical) { + return; + } + const p = section.screenToLocalPoint(e.global); + addWaypointConfig.handler = () => { + const linePoints = section.linePoints; + const { start, end } = getWaypointRangeIndex(linePoints, false, p, defaultDisplayConfig.lineWidth); + addWayPoint(section, false, start, end, p); + }; + clearWaypointsConfig.handler = () => { + clearWayPoint(section, false); + }; + // splitSectionConfig.disabled = false; + // splitSectionConfig.handler = () => { + // Dialog.create({ + // title: '拆分区段', + // message: '请选择生成数量和方向', + // component: SectionSplitDialog, + // cancel: true, + // persistent: true, + // }).onOk((data: { num: number; dir: 'ltr' | 'rtl' }) => { + // const { num, dir } = data; + // const sectionData = section.datas; + // const points = section.getSplitPoints(num); + // const children: LogicSection[] = []; + // let codeAppend = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.slice(0, num); + // if ( + // (dir === 'ltr' && + // sectionData.points[0].x > + // sectionData.points[sectionData.points.length - 1].x) || + // (dir === 'rtl' && + // sectionData.points[0].x < + // sectionData.points[sectionData.points.length - 1].x) + // ) { + // codeAppend = codeAppend.split('').reverse().join(''); + // } + // points.forEach((ps, i) => { + // const data = new LogicSectionData(); + // const logicSectionDraw = + // this.drawAssistant.app.getDrawAssistant( + // LogicSection.Type, + // ); + // data.id = logicSectionDraw.nextId(); + // data.code = `${sectionData.code}-${codeAppend.charAt(i % 26)}`; + // data.points = ps.map( + // (p) => new graphicData.Point({ x: p.x, y: p.y }), + // ); + // data.id = this.drawAssistant.nextId(); + // data.childTransforms = [ + // new ChildTransform( + // 'label', + // new GraphicTransform( + // { + // x: + // data.points[0].x + + // (data.points[1].x - data.points[0].x) / 2, + // y: + // data.points[0].y + + // (data.points[1].y - data.points[0].y) / 2 + + // 20, + // }, + // { x: 0, y: 0 }, + // 0, + // { x: 0, y: 0 }, + // ), + // ), + // ]; + // const g = logicSectionDraw.graphicTemplate.new(); + // g.loadData(data); + // logicSectionDraw.storeGraphic(g); + // children.push(g); + // }); + // // sectionData.children = children.map((g) => g.datas.id); + // section.repaint(); + // section.buildRelation(); + // section.draggable = false; + // children.forEach((c) => c.buildRelation()); + // this.app.updateSelected(...children); + // }); + // }; + SectionEditMenu.open(e.global); + } + onSelected(g) { + const section = g; + if (section.datas.sectionType === SectionType.TurnoutPhysical) { + return; + } + if (section.datas.isCurve) { + let lep = section.getAssistantAppend(SectionBazierCurveEditPlugin.Name); + if (!lep) { + lep = new SectionBazierCurveEditPlugin(section, { + onEditPointCreate, + }); + section.addAssistantAppend(lep); + } + lep.showAll(); + } + else { + let lep = section.getAssistantAppend(SectionPolylineEditPlugin.Name); + if (!lep) { + lep = new SectionPolylineEditPlugin(section, { onEditPointCreate }); + section.addAssistantAppend(lep); + } + lep.showAll(); + } + } + onUnselected(g) { + const section = g; + if (section.datas.isCurve) { + const lep = section.getAssistantAppend(SectionBazierCurveEditPlugin.Name); + if (lep) { + lep.hideAll(); + } + } + else { + const lep = section.getAssistantAppend(SectionPolylineEditPlugin.Name); + if (lep) { + lep.hideAll(); + } + } + } +} + +export { SectionDraw, SectionGraphicHitArea, SectionPointEditPlugin, addWaypointConfig, clearWaypointsConfig, splitSectionConfig }; diff --git a/components/packages/Turnout/Turnout.d.ts b/components/packages/Turnout/Turnout.d.ts index 1d59c65..8785448 100644 --- a/components/packages/Turnout/Turnout.d.ts +++ b/components/packages/Turnout/Turnout.d.ts @@ -1,4 +1,33 @@ -import { JlGraphic } from "jl-graphic"; -export declare class Turnout extends JlGraphic { - doRepaint(): void; +import { DevicePort, IRelatedRef, KilometerSystem } from 'common/common'; +import { GraphicData, JlGraphic } from 'jl-graphic'; +import { IPointData } from 'pixi.js'; +export declare enum SwitchMachineType { + Unknown = 0, + ZDJ9_Single = 1, + ZDJ9_Double = 2 +} +export interface ITurnoutData extends GraphicData { + code: string; + pointA: IPointData[]; + pointB: IPointData[]; + pointC: IPointData[]; + paRef?: IRelatedRef; + pbRef?: IRelatedRef; + pcRef?: IRelatedRef; + kilometerSystem: KilometerSystem; + paTrackSectionId?: number; + pbTrackSectionId?: number; + pcTrackSectionId?: number; + switchMachineType?: SwitchMachineType; + centralizedStations?: number[]; + clone(): ITurnoutData; + copyFrom(data: ITurnoutData): void; + eq(other: ITurnoutData): boolean; +} +export declare class Turnout extends JlGraphic { + static Type: string; + doRepaint(): void; + get datas(): ITurnoutData; + getGraphicOfPort(port: DevicePort): JlGraphic[]; + getPortPoints(): IPointData[][]; } diff --git a/components/packages/Turnout/Turnout.js b/components/packages/Turnout/Turnout.js index c8f47ee..ce460b7 100644 --- a/components/packages/Turnout/Turnout.js +++ b/components/packages/Turnout/Turnout.js @@ -1,9 +1,30 @@ import { JlGraphic } from 'jl-graphic'; +var SwitchMachineType; +(function (SwitchMachineType) { + SwitchMachineType[SwitchMachineType["Unknown"] = 0] = "Unknown"; + SwitchMachineType[SwitchMachineType["ZDJ9_Single"] = 1] = "ZDJ9_Single"; + SwitchMachineType[SwitchMachineType["ZDJ9_Double"] = 2] = "ZDJ9_Double"; +})(SwitchMachineType || (SwitchMachineType = {})); class Turnout extends JlGraphic { + static Type = 'Turnout'; doRepaint() { console.log(111); } + get datas() { + return this.getDatas(); + } + getGraphicOfPort(port) { + return this.relationManage + .getRelationsOfGraphic(this) + .filter((relation) => relation.getRelationParam(this).getParam() === port) + .map((relation) => { + return relation.getOtherGraphic(this); + }); + } + getPortPoints() { + return [this.datas.pointA, this.datas.pointB, this.datas.pointC]; + } } -export { Turnout }; +export { SwitchMachineType, Turnout }; diff --git a/src/packages/Section/bjrtss/Section.ts b/src/packages/Section/bjrtss/Section.ts index bc79d61..55c8021 100644 --- a/src/packages/Section/bjrtss/Section.ts +++ b/src/packages/Section/bjrtss/Section.ts @@ -1,14 +1,19 @@ -import { Section as SectionBase, SectionDisplayConfig } from "../common/Section"; +import { + Section as SectionBase, + SectionDisplayConfig, +} from '../common/Section'; const displayConfig: SectionDisplayConfig = { lineColor: '#5578b6', occupiedColor: '#f00', - lineWidth: 5 -} + lineWidth: 5, +}; export class Section extends SectionBase { constructor() { super(); - this.setDisplayConfig(displayConfig) + this.setDisplayConfig(displayConfig); } } + +export { SectionTemplate } from '../common/Section'; diff --git a/src/packages/Section/common/Section.ts b/src/packages/Section/common/Section.ts index 7a2f8d2..0de803e 100644 --- a/src/packages/Section/common/Section.ts +++ b/src/packages/Section/common/Section.ts @@ -53,7 +53,7 @@ export interface SectionDisplayConfig { lineWidth: number; } -const defaultDisplayConfig: SectionDisplayConfig = { +export const defaultDisplayConfig: SectionDisplayConfig = { lineColor: '#5578b6', occupiedColor: '#f00', lineWidth: 5, @@ -340,6 +340,8 @@ export class Section extends JlGraphic { } export class SectionTemplate extends JlGraphicTemplate
{ + isCurve = false; + segmentsCount = 10; constructor(dataTemplate: ISectionData, stateTemplate?: ISectionState) { super(Section.Type, { dataTemplate, diff --git a/src/packages/Section/common/SectionDrawAssistant.ts b/src/packages/Section/common/SectionDrawAssistant.ts new file mode 100644 index 0000000..513de4e --- /dev/null +++ b/src/packages/Section/common/SectionDrawAssistant.ts @@ -0,0 +1,629 @@ +import { + AbsorbableLine, + AbsorbablePoint, + AbsorbablePosition, + AppConsts, + BezierCurveEditPlugin, + ChildTransform, + ContextMenu, + DraggablePoint, + GraphicDrawAssistant, + GraphicInteractionPlugin, + GraphicTransform, + GraphicTransformEvent, + IDrawApp, + IEditPointOptions, + IGraphicApp, + ILineGraphic, + JlGraphic, + KeyListener, + MenuItemOptions, + PolylineEditPlugin, + VectorText, + addWayPoint, + calculateLineMidpoint, + calculateMirrorPoint, + clearWayPoint, + convertToBezierParams, + getWaypointRangeIndex, + linePoint, + pointPolygon, +} from 'jl-graphic'; +import { + ISectionData, + SectionTemplate, + SectionType, + defaultDisplayConfig, +} from './Section'; +import { + Point, + Graphics, + type FederatedMouseEvent, + type IHitArea, + type IPointData, + type DisplayObject, +} from 'pixi.js'; +import { Turnout } from 'src/packages/Turnout/Turnout'; +import { AxleCounting } from 'src/packages/AxleCounting/AxleCounting'; +import { DevicePort } from 'common/common'; +import { Section } from '../bjrtss/Section'; + +export class SectionDraw extends GraphicDrawAssistant< + SectionTemplate, + ISectionData +> { + 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: SectionTemplate) { + super(app, template, 'sym_o_timeline', '区段Section'); + this.container.addChild(this.graphic); + + SectionPointEditPlugin.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(3, '#555'); + + 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: ISectionData): boolean { + const template = this.graphicTemplate; + if ( + (!template.isCurve && this.points.length < 2) || + (template.isCurve && this.points.length < 4) + ) { + console.log('Section绘制因点不够取消绘制'); + return false; + } + if (template.isCurve) { + this.points.pop(); + } + data.isCurve = template.isCurve; + data.segmentsCount = template.segmentsCount; + data.points = this.points; + data.code = 'G000'; + + return true; + } + + clearCache(): void { + this.points = []; + this.graphic.clear(); + } + + // generateTurnoutSection() { + // const turnoutIds: number[] = []; /* 已遍历的道岔id列表 */ + // const dfs = (turnout: Turnout) => { + // const axleCountings: AxleCounting[] = []; + // const turnouts: Turnout[] = []; + // if (turnoutIds.includes(turnout.datas.id)) return; + // turnoutIds.push(turnout.datas.id); + + // turnouts.push(turnout); + + // [DevicePort.A, DevicePort.B, DevicePort.C].forEach((port) => { + // const currentPortRelated = turnout.getGraphicOfPort(port); + // if ( + // currentPortRelated.some((graphic) => graphic instanceof AxleCounting) + // ) { + // const axleCounting = currentPortRelated.find( + // (graphic) => graphic instanceof AxleCounting, + // ) as AxleCounting; + // axleCountings.push(axleCounting); + // } else { + // const nextTurnout = currentPortRelated.find( + // (graphic) => graphic instanceof Turnout, + // ) as Turnout; + // const result = dfs(nextTurnout); + // if (result?.axleCountings) { + // axleCountings.push(...result.axleCountings); + // turnouts.push(...result.turnouts); + // } + // } + // }); + // return { axleCountings, turnouts }; + // }; + + // const graphics: Section[] = []; + // this.app.queryStore + // .queryByType(Turnout.Type) + // .forEach((turnout) => { + // const result = dfs(turnout); + // if (!result || !result.axleCountings.length) { + // return; + // } + // const turnoutSections = this.app.queryStore + // .queryByType
(Section.Type) + // .filter((s) => s.datas.sectionType === SectionType.TurnoutPhysical); + // //判重 + // const existed = turnoutSections.some((sec) => + // result.axleCountings.every( + // (ac) => + // sec.datas.axleCountings && + // sec.datas.axleCountings.includes(ac.datas.id), + // ), + // ); + // if (existed) return; + // const turnoutPhysicalSectionData = new SectionData(); + // turnoutPhysicalSectionData.id = this.nextId(); + // turnoutPhysicalSectionData.sectionType = SectionType.TurnoutPhysical; + // turnoutPhysicalSectionData.points = result.axleCountings.map((ac) => { + // return new Point(ac.position.x, ac.position.y); + // }); + // turnoutPhysicalSectionData.axleCountings = result.axleCountings.map( + // (ac) => ac.datas.id, + // ); + // turnoutPhysicalSectionData.code = result.turnouts + // .map((t) => t.datas.code) + // .join('-'); + // let labelPosition: IPointData; + // if (result.turnouts.length === 2) { + // labelPosition = calculateLineMidpoint( + // result.turnouts[0].position, + // result.turnouts[1].position, + // ); + // } else { + // labelPosition = { x: result.turnouts[0].x, y: result.turnouts[0].y }; + // } + // labelPosition.y += 20; + // const labelTransform = GraphicTransform.default(); + // labelTransform.position = labelPosition; + // turnoutPhysicalSectionData.childTransforms = [ + // new ChildTransform('label', labelTransform), + // ]; + // const g = this.graphicTemplate.new(); + // g.position.set(turnout.datas.pointC[0].x, turnout.datas.pointC[0].y); + // g.loadData(turnoutPhysicalSectionData); + // graphics.push(g); + // }); + // this.storeGraphic(...graphics); + // graphics.forEach((g) => { + // g.loadRelations(); + // }); + // } +} + +export class SectionGraphicHitArea implements IHitArea { + section: Section; + constructor(section: Section) { + this.section = section; + } + contains(x: number, y: number): boolean { + if (this.section.datas.sectionType === SectionType.TurnoutPhysical) { + return false; + } + if (this.section.datas.isCurve) { + const bps = convertToBezierParams(this.section.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], + defaultDisplayConfig.lineWidth, + ) + ) { + return true; + } + } + } else { + for (let i = 1; i < this.section.datas.points.length; i++) { + const p1 = this.section.datas.points[i - 1]; + const p2 = this.section.datas.points[i]; + if (linePoint(p1, p2, { x, y }, defaultDisplayConfig.lineWidth)) { + return true; + } + } + } + return false; + } +} + +function buildAbsorbablePositions(section: Section): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + + const sections = section.queryStore.queryByType
(Section.Type); + sections.forEach((other) => { + const [ps, pe] = [ + other.localToCanvasPoint(other.getStartPoint()), + other.localToCanvasPoint(other.getEndPoint()), + ]; + const { width, height } = section.getGraphicApp().canvas; + const xs = new AbsorbableLine({ x: 0, y: ps.y }, { x: width, y: ps.y }); + const ys = new AbsorbableLine({ x: ps.x, y: 0 }, { x: ps.x, y: height }); + const xe = new AbsorbableLine({ x: 0, y: pe.y }, { x: width, y: pe.y }); + const ye = new AbsorbableLine({ x: pe.x, y: 0 }, { x: pe.x, y: height }); + aps.push(xs, ys, xe, ye); + }); + + const turnouts = section.queryStore.queryByType(Turnout.Type); + turnouts.forEach((turnout) => { + turnout.getPortPoints().forEach((points) => { + turnout.localToCanvasPoints(...points).forEach((p) => { + aps.push(new AbsorbablePoint(p)); + }); + }); + }); + + return aps; +} + +function onEditPointCreate(g: ILineGraphic, dp: DraggablePoint): void { + const section = g as Section; + dp.on('transformstart', (e: GraphicTransformEvent) => { + if (e.isShift()) { + section.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(section), + }); + } + }); +} + +class SectionPolylineEditPlugin extends PolylineEditPlugin { + static Name = 'SectionPolylineEditPlugin'; + labels: VectorText[] = []; + constructor(g: ILineGraphic, options?: IEditPointOptions) { + super(g, options); + this.name = SectionPolylineEditPlugin.Name; + this.initLabels(); + } + + initLabels() { + this.labels = ['A', 'B'].map((str) => { + const vc = new VectorText(str, { fill: AppConsts.assistantElementColor }); + vc.setVectorFontSize(14); + vc.anchor.set(0.5); + return vc; + }); + this.addChild(...this.labels); + this.updateEditedPointsPosition(); + } + + updateEditedPointsPosition() { + super.updateEditedPointsPosition(); + this.labels[0]?.position.set( + this.editedPoints[0].x, + this.editedPoints[0].y + 10, + ); + this.labels[1]?.position.set( + this.editedPoints[this.editedPoints.length - 1].x, + this.editedPoints[this.editedPoints.length - 1].y + 10, + ); + } +} + +class SectionBazierCurveEditPlugin extends BezierCurveEditPlugin { + static Name = 'SectionBazierCurveEditPlugin'; + labels: VectorText[] = []; + constructor(g: ILineGraphic, options?: IEditPointOptions) { + super(g, options); + this.name = SectionBazierCurveEditPlugin.Name; + this.initLabels(); + } + initLabels() { + this.labels = [new VectorText('A'), new VectorText('B')]; + this.labels.forEach((label) => { + label.setVectorFontSize(14); + this.addChild(label); + }); + this.updateEditedPointsPosition(); + } + updateEditedPointsPosition() { + super.updateEditedPointsPosition(); + this.labels[0]?.position.set( + this.editedPoints[0].x, + this.editedPoints[0].y + 10, + ); + this.labels[1]?.position.set( + this.editedPoints[this.editedPoints.length - 1].x, + this.editedPoints[this.editedPoints.length - 1].y + 10, + ); + } +} + +export const addWaypointConfig: MenuItemOptions = { + name: '添加路径点', +}; +export const clearWaypointsConfig: MenuItemOptions = { + name: '清除所有路径点', +}; +export const splitSectionConfig: MenuItemOptions = { + name: '拆分', + // disabled: true, +}; +const SectionEditMenu: ContextMenu = ContextMenu.init({ + name: '区段编辑菜单', + groups: [ + { + items: [addWaypointConfig, clearWaypointsConfig], + }, + { + items: [splitSectionConfig], + }, + ], +}); + +export class SectionPointEditPlugin extends GraphicInteractionPlugin
{ + static Name = 'SectionPointDrag'; + drawAssistant: SectionDraw; + + constructor(app: IGraphicApp, da: SectionDraw) { + super(SectionPointEditPlugin.Name, app); + this.drawAssistant = da; + app.registerMenu(SectionEditMenu); + } + static init(app: IGraphicApp, da: SectionDraw) { + return new SectionPointEditPlugin(app, da); + } + filter(...grahpics: JlGraphic[]): Section[] | undefined { + return grahpics.filter((g) => g.type == Section.Type) as Section[]; + } + bind(g: Section): void { + g.lineGraphic.eventMode = 'static'; + g.lineGraphic.cursor = 'pointer'; + g.lineGraphic.hitArea = new SectionGraphicHitArea(g); + g.transformSave = true; + g.labelGraphic.eventMode = 'static'; + g.labelGraphic.cursor = 'pointer'; + g.labelGraphic.selectable = true; + g.labelGraphic.draggable = true; + g.on('selected', this.onSelected, this); + g.on('unselected', this.onUnselected, this); + // g.on('_rightclick', this.onContextMenu, this); + } + unbind(g: Section): void { + g.off('selected', this.onSelected, this); + g.off('unselected', this.onUnselected, this); + // g.off('_rightclick', this.onContextMenu, this); + } + + onContextMenu(e: FederatedMouseEvent) { + const target = e.target as DisplayObject; + const section = target.getGraphic() as Section; + this.app.updateSelected(section); + if (section.datas.sectionType === SectionType.TurnoutPhysical) { + return; + } + const p = section.screenToLocalPoint(e.global); + addWaypointConfig.handler = () => { + const linePoints = section.linePoints; + const { start, end } = getWaypointRangeIndex( + linePoints, + false, + p, + defaultDisplayConfig.lineWidth, + ); + addWayPoint(section, false, start, end, p); + }; + clearWaypointsConfig.handler = () => { + clearWayPoint(section, false); + }; + // splitSectionConfig.disabled = false; + // splitSectionConfig.handler = () => { + // Dialog.create({ + // title: '拆分区段', + // message: '请选择生成数量和方向', + // component: SectionSplitDialog, + // cancel: true, + // persistent: true, + // }).onOk((data: { num: number; dir: 'ltr' | 'rtl' }) => { + // const { num, dir } = data; + // const sectionData = section.datas; + // const points = section.getSplitPoints(num); + // const children: LogicSection[] = []; + // let codeAppend = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.slice(0, num); + // if ( + // (dir === 'ltr' && + // sectionData.points[0].x > + // sectionData.points[sectionData.points.length - 1].x) || + // (dir === 'rtl' && + // sectionData.points[0].x < + // sectionData.points[sectionData.points.length - 1].x) + // ) { + // codeAppend = codeAppend.split('').reverse().join(''); + // } + // points.forEach((ps, i) => { + // const data = new LogicSectionData(); + // const logicSectionDraw = + // this.drawAssistant.app.getDrawAssistant( + // LogicSection.Type, + // ); + // data.id = logicSectionDraw.nextId(); + // data.code = `${sectionData.code}-${codeAppend.charAt(i % 26)}`; + // data.points = ps.map( + // (p) => new graphicData.Point({ x: p.x, y: p.y }), + // ); + // data.id = this.drawAssistant.nextId(); + // data.childTransforms = [ + // new ChildTransform( + // 'label', + // new GraphicTransform( + // { + // x: + // data.points[0].x + + // (data.points[1].x - data.points[0].x) / 2, + // y: + // data.points[0].y + + // (data.points[1].y - data.points[0].y) / 2 + + // 20, + // }, + // { x: 0, y: 0 }, + // 0, + // { x: 0, y: 0 }, + // ), + // ), + // ]; + // const g = logicSectionDraw.graphicTemplate.new(); + // g.loadData(data); + // logicSectionDraw.storeGraphic(g); + // children.push(g); + // }); + // // sectionData.children = children.map((g) => g.datas.id); + // section.repaint(); + // section.buildRelation(); + // section.draggable = false; + // children.forEach((c) => c.buildRelation()); + // this.app.updateSelected(...children); + // }); + // }; + SectionEditMenu.open(e.global); + } + + onSelected(g: DisplayObject): void { + const section = g as Section; + if (section.datas.sectionType === SectionType.TurnoutPhysical) { + return; + } + if (section.datas.isCurve) { + let lep = section.getAssistantAppend( + SectionBazierCurveEditPlugin.Name, + ); + if (!lep) { + lep = new SectionBazierCurveEditPlugin(section, { + onEditPointCreate, + }); + section.addAssistantAppend(lep); + } + lep.showAll(); + } else { + let lep = section.getAssistantAppend( + SectionPolylineEditPlugin.Name, + ); + if (!lep) { + lep = new SectionPolylineEditPlugin(section, { onEditPointCreate }); + section.addAssistantAppend(lep); + } + lep.showAll(); + } + } + onUnselected(g: DisplayObject): void { + const section = g as Section; + if (section.datas.isCurve) { + const lep = section.getAssistantAppend( + SectionBazierCurveEditPlugin.Name, + ); + if (lep) { + lep.hideAll(); + } + } else { + const lep = section.getAssistantAppend( + SectionPolylineEditPlugin.Name, + ); + if (lep) { + lep.hideAll(); + } + } + } +} diff --git a/src/packages/Turnout/Turnout.ts b/src/packages/Turnout/Turnout.ts index d67f177..53454f5 100644 --- a/src/packages/Turnout/Turnout.ts +++ b/src/packages/Turnout/Turnout.ts @@ -1,8 +1,53 @@ -import { JlGraphic } from "jl-graphic"; +import { DevicePort, IRelatedRef, KilometerSystem } from 'common/common'; +import { GraphicData, JlGraphic } from 'jl-graphic'; +import { IPointData } from 'pixi.js'; + +export enum SwitchMachineType { + Unknown = 0, + ZDJ9_Single = 1, + ZDJ9_Double = 2, +} + +export interface ITurnoutData extends GraphicData { + code: string; + pointA: IPointData[]; + pointB: IPointData[]; + pointC: IPointData[]; + paRef?: IRelatedRef; + pbRef?: IRelatedRef; + pcRef?: IRelatedRef; + kilometerSystem: KilometerSystem; + paTrackSectionId?: number; + pbTrackSectionId?: number; + pcTrackSectionId?: number; + switchMachineType?: SwitchMachineType; + centralizedStations?: number[]; + clone(): ITurnoutData; + copyFrom(data: ITurnoutData): void; + eq(other: ITurnoutData): boolean; +} export class Turnout extends JlGraphic { - + static Type = 'Turnout'; doRepaint(): void { - console.log(111) + console.log(111); + } + get datas(): ITurnoutData { + return this.getDatas(); + } + + getGraphicOfPort(port: DevicePort) { + return this.relationManage + .getRelationsOfGraphic(this) + .filter( + (relation) => + relation.getRelationParam(this).getParam() === port, + ) + .map((relation) => { + return relation.getOtherGraphic(this); + }); + } + getPortPoints() { + return [this.datas.pointA, this.datas.pointB, this.datas.pointC]; } }