diff --git a/graphic-pixi b/graphic-pixi index 7e4eaed..1f30264 160000 --- a/graphic-pixi +++ b/graphic-pixi @@ -1 +1 @@ -Subproject commit 7e4eaed0cf06d68c75cb51c30329eff5fe4d1e3f +Subproject commit 1f302648b5a71a82b798b77fe238c5fc6e3081b4 diff --git a/quasar.config.js b/quasar.config.js index ee66d7a..bc1b766 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -110,6 +110,8 @@ module.exports = configure(function (/* ctx */) { // components: [], // directives: [], + autoImportComponentCase: 'combined', + // Quasar plugins plugins: ['Notify', 'Dialog', 'Dark', 'AppFullscreen', 'Loading'], }, diff --git a/src/components/draw-app/dialogs/SectionSplitDialog.vue b/src/components/draw-app/dialogs/SectionSplitDialog.vue new file mode 100644 index 0000000..2dd83ee --- /dev/null +++ b/src/components/draw-app/dialogs/SectionSplitDialog.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/src/components/draw-app/properties/SectionProperty.vue b/src/components/draw-app/properties/SectionProperty.vue index 12695da..30ee676 100644 --- a/src/components/draw-app/properties/SectionProperty.vue +++ b/src/components/draw-app/properties/SectionProperty.vue @@ -31,16 +31,6 @@ > - - - @@ -56,34 +46,6 @@ const drawStore = useDrawStore(); const sectionModel = shallowRef(new SectionData()); -const splitNum = ref(3); - -function splitSection() { - const sectionData = toRaw(sectionModel.value); - const section = toRaw(drawStore.selectedGraphic as Section); - const app = drawStore.getDrawApp(); - const points = section.getSplitPoints(splitNum.value); - const childIds: string[] = []; - - points.forEach((ps, i) => { - const data = new SectionData(); - data.points = ps.map((p) => new graphicData.Point({ x: p.x, y: p.y })); - data.id = app.drawAssistants - .find((as) => as.name === Section.name)! - .nextId(); - data.code = `${sectionData.code}-${'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.charAt( - i % 26 - )}`; - data.sectionType = SectionType.Logic; - const section = app.graphicTemplateMap.get(Section.name)!.new(); - section?.loadData(data); - app.addGraphics(section); - childIds.push(data.id); - }); - sectionData.children = childIds; - app.updateGraphicAndRecord(section, sectionData); -} - const sectionRelations = computed(() => { const section = drawStore.selectedGraphic as Section; diff --git a/src/graphics/section/Section.ts b/src/graphics/section/Section.ts index 99cb600..6ec80b6 100644 --- a/src/graphics/section/Section.ts +++ b/src/graphics/section/Section.ts @@ -17,6 +17,7 @@ import { } from '../CommonGraphics'; import { Turnout } from '../turnout/Turnout'; import { SectionData } from 'src/drawApp/graphics/SectionInteraction'; +import Vector2 from 'src/jl-graphic/math/Vector2'; export enum SectionType { Physical = 0, @@ -134,13 +135,49 @@ export class Section extends JlGraphic implements ILineGraphic { /** 获取拆分逻辑区段数据 */ getSplitPoints(count: number): IPointData[][] { if (this.datas.points.length !== 2) { - throw Error('多段分割待实现'); + // throw Error('多段分割待实现'); + let totalLen = 0; + const lengths: number[] = []; + for (let i = 1; i < this.datas.points.length; i++) { + const { x: x1, y: y1 } = this.datas.points[i - 1], + { x: x2, y: y2 } = this.datas.points[i]; + const len = new Vector2([x2 - x1, y2 - y1]).length(); + totalLen += len; + lengths.push(len); + } + const counts = lengths.map((length) => + Math.round((count * length) / totalLen) + ); + if (counts.reduce((p, c) => p + c, 0) !== count) { + const intersection = counts.reduce((p, c) => p + c, 0) - count; + let maxCountIndex = 0, + maxCount = 0; + counts.forEach((c, i) => { + if (c > maxCount) { + maxCount = c; + maxCountIndex = i; + } + }); + counts[maxCountIndex] + intersection; + } + return counts + .map((count, i) => { + return splitLineEvenly( + this.localToCanvasPoint(this.datas.points[i]), + this.localToCanvasPoint(this.datas.points[i + 1]), + count + ); + }) + .flat(); + } else { + return splitLineEvenly( + this.localToCanvasPoint(this.datas.points[0]), + this.localToCanvasPoint( + this.datas.points[this.datas.points.length - 1] + ), + count + ); } - return splitLineEvenly( - this.localToCanvasPoint(this.datas.points[0]), - this.localToCanvasPoint(this.datas.points[this.datas.points.length - 1]), - count - ); } buildRelation() { diff --git a/src/graphics/section/SectionDrawAssistant.ts b/src/graphics/section/SectionDrawAssistant.ts index c27d8da..049f3ea 100644 --- a/src/graphics/section/SectionDrawAssistant.ts +++ b/src/graphics/section/SectionDrawAssistant.ts @@ -4,6 +4,7 @@ import { GraphicApp, GraphicDrawAssistant, GraphicInteractionPlugin, + GraphicRelation, GraphicTransform, GraphicTransformEvent, JlDrawApp, @@ -15,25 +16,37 @@ import { ISectionData, Section, SectionConsts, + SectionPort, SectionTemplate, + SectionType, } from './Section'; import { DisplayObject, FederatedMouseEvent, Graphics, IHitArea, + IPointData, Point, } from 'pixi.js'; import { IEditPointOptions, ILineGraphic, PolylineEditPlugin, + addWayPoint, + clearWayPoint, + getWaypointRangeIndex, } from 'src/jl-graphic/plugins/GraphicEditPlugin'; import AbsorbablePoint, { AbsorbableLine, AbsorbablePosition, } from 'src/jl-graphic/graphic/AbsorbablePosition'; -import { Turnout } from '../turnout/Turnout'; +import { Turnout, TurnoutPort } from '../turnout/Turnout'; +import { MenuItemOptions } from 'src/jl-graphic/ui/Menu'; +import { ContextMenu } from 'src/jl-graphic/ui/ContextMenu'; +import { Dialog } from 'quasar'; +import { SectionData } from 'src/drawApp/graphics/SectionInteraction'; +import { graphicData } from 'src/protos/stationLayoutGraphics'; +import SectionSplitDialog from 'src/components/draw-app/dialogs/SectionSplitDialog.vue'; export class SectionDraw extends GraphicDrawAssistant< SectionTemplate, @@ -46,7 +59,7 @@ export class SectionDraw extends GraphicDrawAssistant< super(app, template, 'sym_o_timeline', '区段Section'); this.container.addChild(this.graphic); - SectionPointEditPlugin.init(app); + SectionPointEditPlugin.init(app, this); } onLeftDown(e: FederatedMouseEvent): void { @@ -88,20 +101,23 @@ export class SectionDraw extends GraphicDrawAssistant< prepareData(data: ISectionData): boolean { data.points = this.points; data.code = 'G000'; - data.childTransforms?.push( + console.log(data.points[1].x); + console.log(data.points[0].x); + data.childTransforms = [ new ChildTransform( 'label', new GraphicTransform( { - x: data.points[1].x - data.points[0].x, - y: data.points[1].y - data.points[0].y + 20, + 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 } ) - ) - ); + ), + ]; return true; } @@ -191,9 +207,71 @@ class SectionPolylineEditPlugin extends PolylineEditPlugin { this.updateEditedPointsPosition(); } - reset(): void { - super.reset(); - this.initLabels(); + setRelatedDrag() { + const len = this.editedPoints.length; + this.editedPoints.forEach((ep, i) => { + if (i === 0 || i === len - 1) { + let relations: GraphicRelation[]; + if (i === 0) { + relations = this.graphic.relationManage + .getRelationsOfGraphic(this.graphic) + .filter( + (relation) => + relation.getRelationParam(this.graphic).param === SectionPort.A + ); + } else { + relations = this.graphic.relationManage + .getRelationsOfGraphic(this.graphic) + .filter( + (relation) => + relation.getRelationParam(this.graphic).param === SectionPort.B + ); + } + if (!relations.length) return; + const points: IPointData[] = []; + const otherGraphics = relations.map((relation) => + relation.getOtherGraphic(this.graphic) + ); + const otherPorts = relations.map( + (relation) => relation.getOtherRelationParam(this.graphic).param + ); + otherGraphics.forEach((otherGraphic, i) => { + const otherPort = otherPorts[i]; + if (otherGraphic instanceof Turnout) { + if (otherPort === TurnoutPort.A) { + points.push( + otherGraphic.datas.pointA[otherGraphic.datas.pointA.length - 1] + ); + } else if (otherPort === TurnoutPort.B) { + points.push( + otherGraphic.datas.pointB[otherGraphic.datas.pointB.length - 1] + ); + } else if (otherPort === TurnoutPort.C) { + points.push( + otherGraphic.datas.pointC[otherGraphic.datas.pointC.length - 1] + ); + } + } else if (otherGraphic instanceof Section) { + if (otherPort === SectionPort.A) { + points.push(otherGraphic.datas.points[0]); + } else if (otherPort === SectionPort.B) { + points.push( + otherGraphic.datas.points[otherGraphic.datas.points.length - 1] + ); + } + } + }); + const transformingHandler = () => { + otherGraphics.forEach((otherGraphic, i) => { + const p = otherGraphic.canvasToLocalPoint(ep); + points[i].x = p.x; + points[i].y = p.y; + otherGraphic.repaint(); + }); + }; + ep.on('transforming', transformingHandler); + } + }); } updateEditedPointsPosition() { @@ -209,14 +287,39 @@ class SectionPolylineEditPlugin extends PolylineEditPlugin { } } +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: GraphicApp) { + constructor(app: GraphicApp, da: SectionDraw) { super(SectionPointEditPlugin.Name, app); + this.drawAssistant = da; + app.registerMenu(SectionEditMenu); } - static init(app: GraphicApp) { - return new SectionPointEditPlugin(app); + static init(app: GraphicApp, da: SectionDraw) { + return new SectionPointEditPlugin(app, da); } filter(...grahpics: JlGraphic[]): Section[] | undefined { return grahpics.filter((g) => g.type == Section.Type) as Section[]; @@ -232,14 +335,99 @@ export class SectionPointEditPlugin extends GraphicInteractionPlugin
{ 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); + const p = section.screenToLocalPoint(e.global); + + addWaypointConfig.handler = () => { + const linePoints = section.linePoints; + const { start, end } = getWaypointRangeIndex( + linePoints, + false, + p, + SectionConsts.lineWidth + ); + addWayPoint(section, false, start, end, p); + }; + clearWaypointsConfig.handler = () => { + clearWayPoint(section, false); + }; + + if ( + section.datas.children && + section.datas.sectionType === SectionType.Physical + ) { + 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: Section[] = []; + 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 SectionData(); + data.id = this.drawAssistant.nextId(); + data.code = `${sectionData.code}-${codeAppend.charAt(i % 26)}`; + data.sectionType = SectionType.Logic; + data.points = ps.map( + (p) => new graphicData.Point({ x: p.x, y: p.y }) + ); + data.id = this.drawAssistant.nextId(); + const g = this.drawAssistant.graphicTemplate.new(); + g.loadData(data); + this.drawAssistant.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); + }); + }; + } else { + splitSectionConfig.disabled = true; + } + + SectionEditMenu.open(e.global); } onSelected(g: DisplayObject): void { const section = g as Section; + if ( + section.datas.children.length > 0 && + section.datas.sectionType === SectionType.Physical + ) { + return; + } let lep = section.getAssistantAppend( SectionPolylineEditPlugin.Name ); @@ -248,6 +436,7 @@ export class SectionPointEditPlugin extends GraphicInteractionPlugin
{ section.addAssistantAppend(lep); } lep.showAll(); + lep.setRelatedDrag(); } onUnselected(g: DisplayObject): void { const section = g as Section; diff --git a/src/graphics/turnout/TurnoutDrawAssistant.ts b/src/graphics/turnout/TurnoutDrawAssistant.ts index 9c22ebb..0c50121 100644 --- a/src/graphics/turnout/TurnoutDrawAssistant.ts +++ b/src/graphics/turnout/TurnoutDrawAssistant.ts @@ -4,6 +4,7 @@ import { GraphicApp, GraphicDrawAssistant, GraphicInteractionPlugin, + GraphicRelation, GraphicTransformEvent, JlDrawApp, JlGraphic, @@ -31,7 +32,7 @@ import { GraphicEditPlugin, getWaypointRangeIndex, } from 'src/jl-graphic/plugins/GraphicEditPlugin'; -import { Section } from '../section/Section'; +import { Section, SectionPort } from '../section/Section'; import AbsorbablePoint, { AbsorbableLine, } from 'src/jl-graphic/graphic/AbsorbablePosition'; @@ -338,8 +339,9 @@ export class TurnoutPointsInteractionPlugin extends GraphicInteractionPlugin { this.removeChildren(); this.initEditPoints(); } + hideAll(): void { + super.hideAll(); + } + setRelatedDrag() { + this.editPoints.forEach((eps, i) => { + const ep = eps[eps.length - 1]; + let relations: GraphicRelation[]; + if (i === 0) { + relations = this.graphic.relationManage + .getRelationsOfGraphic(this.graphic) + .filter( + (relation) => + relation.getRelationParam(this.graphic).param === TurnoutPort.A + ); + } else if (i === 1) { + relations = this.graphic.relationManage + .getRelationsOfGraphic(this.graphic) + .filter( + (relation) => + relation.getRelationParam(this.graphic).param === TurnoutPort.B + ); + } else { + relations = this.graphic.relationManage + .getRelationsOfGraphic(this.graphic) + .filter( + (relation) => + relation.getRelationParam(this.graphic).param === TurnoutPort.C + ); + } + if (!relations.length) return; + const otherGraphics = relations.map((relation) => + relation.getOtherGraphic(this.graphic) + ); + console.log(otherGraphics); + const otherPorts = relations.map( + (relation) => relation.getOtherRelationParam(this.graphic).param + ); + + const point: IPointData[] = []; + otherGraphics.map((otherGraphic, i) => { + const otherPort = otherPorts[i]; + if (otherGraphic instanceof Turnout) { + if (otherPort === TurnoutPort.A) { + point.push( + otherGraphic.datas.pointA[otherGraphic.datas.pointA.length - 1] + ); + } else if (otherPort === TurnoutPort.B) { + point.push( + otherGraphic.datas.pointB[otherGraphic.datas.pointB.length - 1] + ); + } else if (otherPort === TurnoutPort.C) { + point.push( + otherGraphic.datas.pointC[otherGraphic.datas.pointC.length - 1] + ); + } + } else if (otherGraphic instanceof Section) { + if (otherPort === SectionPort.A) { + point.push(otherGraphic.datas.points[0]); + } else if (otherPort === SectionPort.B) { + point.push( + otherGraphic.datas.points[otherGraphic.datas.points.length - 1] + ); + } + } + }); + const transformingHandler = () => { + otherGraphics.forEach((otherGraphic, i) => { + const p = otherGraphic.canvasToLocalPoint(ep); + point[i].x = p.x; + point[i].y = p.y; + otherGraphic.repaint(); + }); + }; + ep.on('transforming', transformingHandler); + }); + } initEditPoints() { const cpA = this.graphic.localToCanvasPoints(...this.graphic.datas.pointA); diff --git a/src/jl-graphic/plugins/GraphicEditPlugin.ts b/src/jl-graphic/plugins/GraphicEditPlugin.ts index 9b1cc5b..c93631a 100644 --- a/src/jl-graphic/plugins/GraphicEditPlugin.ts +++ b/src/jl-graphic/plugins/GraphicEditPlugin.ts @@ -32,13 +32,13 @@ export abstract class GraphicEditPlugin< this.sortableChildren = true; this.graphic.on('transformstart', this.hideAll, this); this.graphic.on('transformend', this.showAll, this); - this.graphic.on('repaint', this.showAll, this); + this.graphic.on('repaint', this.updateEditedPointsPosition, this); } destroy(options?: boolean | IDestroyOptions | undefined): void { this.graphic.off('transformstart', this.hideAll, this); this.graphic.off('transformend', this.showAll, this); - this.graphic.off('repaint', this.showAll, this); + this.graphic.off('repaint', this.updateEditedPointsPosition, this); super.destroy(options); } diff --git a/src/protos/device_info.ts b/src/protos/device_info.ts index baa1e88..af0fb8c 100644 --- a/src/protos/device_info.ts +++ b/src/protos/device_info.ts @@ -188,7 +188,7 @@ export namespace state { return Section.deserialize(bytes); } } - export class Switch extends pb_1.Message { + export class Turnout extends pb_1.Message { #one_of_decls: number[][] = []; constructor(data?: any[] | { id?: string; @@ -242,8 +242,8 @@ export namespace state { code?: string; kilometerSystem?: ReturnType[]; convertKilometer?: number[]; - }): Switch { - const message = new Switch({}); + }): Turnout { + const message = new Turnout({}); if (data.id != null) { message.id = data.id; } @@ -294,8 +294,8 @@ export namespace state { if (!w) return writer.getResultBuffer(); } - static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Switch { - const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Switch(); + static deserialize(bytes: Uint8Array | pb_1.BinaryReader): Turnout { + const reader = bytes instanceof pb_1.BinaryReader ? bytes : new pb_1.BinaryReader(bytes), message = new Turnout(); while (reader.nextField()) { if (reader.isEndGroup()) break; @@ -320,8 +320,8 @@ export namespace state { serializeBinary(): Uint8Array { return this.serialize(); } - static deserializeBinary(bytes: Uint8Array): Switch { - return Switch.deserialize(bytes); + static deserializeBinary(bytes: Uint8Array): Turnout { + return Turnout.deserialize(bytes); } } } diff --git a/xian-ncc-da-message b/xian-ncc-da-message index 549aa2e..8fd000d 160000 --- a/xian-ncc-da-message +++ b/xian-ncc-da-message @@ -1 +1 @@ -Subproject commit 549aa2ec10bffe292a1a68e278ae824a8502db0b +Subproject commit 8fd000d45907f94410786c3bd6c1ab37edbe91e8