区段拆分 && 区段道岔联动拖动

This commit is contained in:
Yuan 2023-07-03 18:45:18 +08:00
parent a3c079ba48
commit 2c219da3a2
10 changed files with 380 additions and 70 deletions

@ -1 +1 @@
Subproject commit 7e4eaed0cf06d68c75cb51c30329eff5fe4d1e3f
Subproject commit 1f302648b5a71a82b798b77fe238c5fc6e3081b4

View File

@ -110,6 +110,8 @@ module.exports = configure(function (/* ctx */) {
// components: [],
// directives: [],
autoImportComponentCase: 'combined',
// Quasar plugins
plugins: ['Notify', 'Dialog', 'Dark', 'AppFullscreen', 'Loading'],
},

View File

@ -0,0 +1,42 @@
<script setup lang="ts">
import { useDialogPluginComponent } from 'quasar';
import { ref } from 'vue';
const num = ref(3);
const dir = ref('ltr');
const dirOptions = [
{ label: '从左至右', value: 'ltr' },
{ label: '从右至左', value: 'rtl' },
];
defineEmits([...useDialogPluginComponent.emits]);
const { dialogRef, onDialogOK, onDialogCancel } = useDialogPluginComponent();
</script>
<template>
<QDialog ref="dialogRef">
<QCard>
<QCardSection> <div class="text-h6">区段拆分</div> </QCardSection>
<QCardSection> <div>请选择要拆分的数量和方向</div> </QCardSection>
<QCardSection class="q-pt-none">
<QInput type="number" dense outlined v-model="num" :min="2" :max="20" />
</QCardSection>
<QCardSection>
<QSelect v-model="dir" dense :options="dirOptions" emitValue mapOptions>
</QSelect>
</QCardSection>
<QCardActions align="right" class="text-primary">
<QBtn flat label="取消" @click="onDialogCancel" v-close-popup />
<QBtn
flat
label="确认"
@click="onDialogOK({ num: Number(num), dir })"
v-close-popup
/>
</QCardActions>
</QCard>
</QDialog>
</template>
<style scoped></style>

View File

@ -31,16 +31,6 @@
>
</template>
</q-field>
<q-input class="q-mt-lg" outlined v-model="splitNum" type="number">
<template #after>
<q-btn
:disable="sectionModel.data.sectionType !== SectionType.Physical"
color="primary"
@click="splitSection"
>拆分为逻辑区段</q-btn
>
</template>
</q-input>
</q-form>
</template>
@ -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;

View File

@ -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() {

View File

@ -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<Section> {
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<Section> {
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>(
SectionPolylineEditPlugin.Name
);
@ -248,6 +436,7 @@ export class SectionPointEditPlugin extends GraphicInteractionPlugin<Section> {
section.addAssistantAppend(lep);
}
lep.showAll();
lep.setRelatedDrag();
}
onUnselected(g: DisplayObject): void {
const section = g as Section;

View File

@ -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<Tur
tep = new TurnoutEditPlugin(turnout, { onEditPointCreate });
turnout.addAssistantAppend(tep);
}
tep.reset();
// tep.reset();
tep.showAll();
tep.setRelatedDrag();
}
onUnSelected(g: DisplayObject) {
@ -380,6 +382,82 @@ export class TurnoutEditPlugin extends GraphicEditPlugin<Turnout> {
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);

View File

@ -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);
}

View File

@ -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<typeof dependency_1.graphicData.KilometerSystem.prototype.toObject>[];
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);
}
}
}

@ -1 +1 @@
Subproject commit 549aa2ec10bffe292a1a68e278ae824a8502db0b
Subproject commit 8fd000d45907f94410786c3bd6c1ab37edbe91e8