diff --git a/src/graphics/iscs-fan/IscsFanDrawAssistant.ts b/src/graphics/iscs-fan/IscsFanDrawAssistant.ts index 5853991..fab5997 100644 --- a/src/graphics/iscs-fan/IscsFanDrawAssistant.ts +++ b/src/graphics/iscs-fan/IscsFanDrawAssistant.ts @@ -1,7 +1,10 @@ import { FederatedMouseEvent, Point } from 'pixi.js'; import { + AbsorbableLine, + AbsorbablePosition, GraphicDrawAssistant, GraphicInteractionPlugin, + GraphicTransformEvent, JlDrawApp, JlGraphic, } from 'src/jlgraphic'; @@ -68,6 +71,16 @@ export class IscsFanInteraction extends GraphicInteractionPlugin { g.cursor = 'pointer'; // g.scalable = true; g.rotatable = true; + g.on('drag-start', () => { + console.log('风机拖拽'); + }); + g.on('transformstart', (e: GraphicTransformEvent) => { + if (e.isShift()) { + g.getGraphicApp().setOptions({ + absorbablePositions: buildAbsorbablePositions(g), + }); + } + }); } unbind(g: IscsFan): void { g.eventMode = 'none'; @@ -75,3 +88,23 @@ export class IscsFanInteraction extends GraphicInteractionPlugin { g.rotatable = false; } } + +function buildAbsorbablePositions( + g: IscsFan +): AbsorbablePosition[] | undefined { + const app = g.getGraphicApp(); + const canvas = app.canvas; + const store = app.queryStore; + const aps: AbsorbablePosition[] = []; + store.queryByType(IscsFan.Type).forEach((fan) => { + if (fan.id === g.id) return; + const p = fan.position; + aps.push( + new AbsorbableLine(new Point(0, p.y), new Point(canvas.width, p.y)) + ); + aps.push( + new AbsorbableLine(new Point(p.x, 0), new Point(p.x, canvas.height)) + ); + }); + return aps; +} diff --git a/src/jlgraphic/app/JlGraphicApp.ts b/src/jlgraphic/app/JlGraphicApp.ts index 529f67d..7b23002 100644 --- a/src/jlgraphic/app/JlGraphicApp.ts +++ b/src/jlgraphic/app/JlGraphicApp.ts @@ -288,6 +288,7 @@ export interface GraphicAppEvents extends GlobalMixins.GraphicAppEvents { drag_op_end: [event: AppDragEvent]; 'pre-menu-handle': [menu: MenuItemOptions]; 'post-menu-handle': [menu: MenuItemOptions]; + 'websocket-state-change': [app: GraphicApp, connected: boolean]; destroy: [app: GraphicApp]; } @@ -888,10 +889,10 @@ export class GraphicApp extends EventEmitter { this.emit('destroy', this); if (this.wsMsgBroker) { this.wsMsgBroker.close(); - if (!StompCli.hasAppMsgBroker()) { - // 如果没有其他消息代理,关闭websocket Stomp客户端 - StompCli.close(); - } + // if (!StompCli.hasAppMsgBroker()) { + // // 如果没有其他消息代理,关闭websocket Stomp客户端 + // StompCli.close(); + // } } this.interactionPluginMap.forEach((plugin) => { plugin.pause(); diff --git a/src/jlgraphic/graphic/AbsorbablePosition.ts b/src/jlgraphic/graphic/AbsorbablePosition.ts index ec379a1..0d5405f 100644 --- a/src/jlgraphic/graphic/AbsorbablePosition.ts +++ b/src/jlgraphic/graphic/AbsorbablePosition.ts @@ -11,6 +11,8 @@ import { calculateFootPointFromPointToLine, calculateIntersectionPointOfCircleAndPoint, distance, + distance2, + isLineContainOther, linePoint, } from '../utils'; import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic'; @@ -19,12 +21,25 @@ import { VectorGraphic, VectorGraphicUtil } from './VectorGraphic'; * 抽象可吸附位置 */ export interface AbsorbablePosition extends Container { + /** + * 是否与另一个可吸附位置重叠(相似,但可能范围不同) + * @param other + */ + isOverlapping(other: AbsorbablePosition): boolean; + + /** + * 与另一个相似的吸附位置比较范围大小 + * @param other + * @returns >0此吸附范围大,<0另一个吸附范围大,=0两个吸附范围一样大 + */ + compareTo(other: AbsorbablePosition): number; + /** * 尝试吸附图形对象 * @param objs 图形对象列表 * @returns 如果吸附成功,返回true,否则false */ - tryAbsorb(...objs: DisplayObject[]): boolean; + tryAbsorb(...objs: DisplayObject[]): void; } /** @@ -65,7 +80,22 @@ export default class AbsorbablePoint this.interactive; VectorGraphicUtil.handle(this); } - tryAbsorb(...objs: DisplayObject[]): boolean { + compareTo(other: AbsorbablePosition): number { + if (other instanceof AbsorbablePoint) { + return this.absorbRange - other.absorbRange; + } + throw new Error('非可吸附点'); + } + isOverlapping(other: AbsorbablePosition): boolean { + if (other instanceof AbsorbablePoint) { + return ( + this._point.equals(other._point) && + this.absorbRange === other.absorbRange + ); + } + return false; + } + tryAbsorb(...objs: DisplayObject[]): void { for (let i = 0; i < objs.length; i++) { const obj = objs[i]; if ( @@ -73,10 +103,8 @@ export default class AbsorbablePoint this.absorbRange ) { obj.position.copyFrom(this._point); - return true; } } - return false; } updateOnScaled() { const scaled = this.getAllParentScaled(); @@ -101,6 +129,22 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition { this.absorbRange = absorbRange; this.redraw(); } + isOverlapping(other: AbsorbablePosition): boolean { + if (other instanceof AbsorbableLine) { + const contain = isLineContainOther( + { p1: this.p1, p2: this.p2 }, + { p1: other.p1, p2: other.p2 } + ); + return contain; + } + return false; + } + compareTo(other: AbsorbablePosition): number { + if (other instanceof AbsorbableLine) { + return distance2(this.p1, this.p2) - distance2(other.p1, other.p2); + } + throw new Error('非可吸附线'); + } redraw() { this.clear(); this.lineStyle(1, new Color(this._color)); @@ -108,17 +152,15 @@ export class AbsorbableLine extends Graphics implements AbsorbablePosition { this.lineTo(this.p2.x, this.p2.y); } - tryAbsorb(...objs: DisplayObject[]): boolean { + tryAbsorb(...objs: DisplayObject[]): void { for (let i = 0; i < objs.length; i++) { const obj = objs[i]; const p = obj.position.clone(); if (linePoint(this.p1, this.p2, p, this.absorbRange, true)) { const fp = calculateFootPointFromPointToLine(this.p1, this.p2, p); obj.position.copyFrom(fp); - return true; } } - return false; } } @@ -138,6 +180,18 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition { this.absorbRange = absorbRange; this.redraw(); } + isOverlapping(other: AbsorbablePosition): boolean { + if (other instanceof AbsorbableCircle) { + return this.p0.equals(other.p0) && this.radius === other.radius; + } + return false; + } + compareTo(other: AbsorbablePosition): number { + if (other instanceof AbsorbableCircle) { + return this.absorbRange - other.absorbRange; + } + throw new Error('非可吸附圆'); + } redraw() { this.clear(); @@ -145,7 +199,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition { this.drawCircle(this.p0.x, this.p0.y, this.radius); } - tryAbsorb(...objs: DisplayObject[]): boolean { + tryAbsorb(...objs: DisplayObject[]): void { for (let i = 0; i < objs.length; i++) { const obj = objs[i]; const len = distance( @@ -165,9 +219,7 @@ export class AbsorbableCircle extends Graphics implements AbsorbablePosition { obj.position ); obj.position.copyFrom(p); - return true; } } - return false; } } diff --git a/src/jlgraphic/message/WsMsgBroker.ts b/src/jlgraphic/message/WsMsgBroker.ts index 8b8cd53..940b11a 100644 --- a/src/jlgraphic/message/WsMsgBroker.ts +++ b/src/jlgraphic/message/WsMsgBroker.ts @@ -22,7 +22,7 @@ export interface StompCliOption { * @returns */ onAuthenticationFailed?: () => void; - reconnectDelay?: number; // 重连延时,默认3秒 + reconnectDelay?: number; // 重连延时,默认3秒,设置为0不重连. heartbeatIncoming?: number; // 服务端过来的心跳间隔,默认30秒 heartbeatOutgoing?: number; // 到服务端的心跳间隔,默认30秒 } @@ -36,18 +36,22 @@ const DefaultStompOption: StompCliOption = { }; export class StompCli { - private static enabled = false; - private static options: StompCliOption; private static client: StompClient; + private static options: StompCliOption; private static appMsgBroker: AppWsMsgBroker[] = []; + /** + * key-订阅路径 + */ + subscriptions: Map = new Map< + string, + AppStateSubscription + >(); private static connected = false; static new(options: StompCliOption) { - if (StompCli.enabled) { - // 已经启用 + if (StompCli.client) { + // 已经创建 return; - // throw new Error('websocket 已连接,若确实需要重新连接,请先断开StompCli.close再重新StompCli.new') } - StompCli.enabled = true; StompCli.options = Object.assign({}, DefaultStompOption, options); StompCli.client = new StompClient({ brokerURL: StompCli.options.wsUrl, @@ -62,6 +66,7 @@ export class StompCli { StompCli.client.onConnect = () => { // console.log('websocket连接(重连),重新订阅', StompCli.appMsgBroker.length) StompCli.connected = true; + StompCli.emitConnectStateChangeEvent(); StompCli.appMsgBroker.forEach((broker) => { broker.resubscribe(); }); @@ -83,10 +88,12 @@ export class StompCli { StompCli.client.onDisconnect = (frame: Frame) => { console.log('Stomp 断开连接', frame); StompCli.connected = false; + StompCli.emitConnectStateChangeEvent(); }; StompCli.client.onWebSocketClose = (evt: CloseEvent) => { console.log('websocket 关闭', evt); StompCli.connected = false; + StompCli.emitConnectStateChangeEvent(); }; // websocket错误处理 StompCli.client.onWebSocketError = (err: Event) => { @@ -96,8 +103,10 @@ export class StompCli { StompCli.client.activate(); } - static isEnabled(): boolean { - return StompCli.enabled; + static emitConnectStateChangeEvent() { + StompCli.appMsgBroker.forEach((broker) => { + broker.app.emit('websocket-state-change', broker.app, StompCli.connected); + }); } static isConnected(): boolean { @@ -135,7 +144,6 @@ export class StompCli { * 关闭websocket连接 */ static close() { - StompCli.enabled = false; StompCli.connected = false; if (StompCli.client) { StompCli.client.deactivate(); diff --git a/src/jlgraphic/plugins/CommonMousePlugin.ts b/src/jlgraphic/plugins/CommonMousePlugin.ts index bdd8747..b7a33f1 100644 --- a/src/jlgraphic/plugins/CommonMousePlugin.ts +++ b/src/jlgraphic/plugins/CommonMousePlugin.ts @@ -194,7 +194,7 @@ export class CommonMouseTool extends AppInteractionPlugin { const graphic = this.leftDownTarget.getGraphic(); if (graphic) { const app = this.app; - console.log(this.leftDownTarget.isGraphic()); + // console.log(this.leftDownTarget.isGraphic()); // 图形选中 if (!e.ctrlKey && !graphic.selected && graphic.selectable) { app.updateSelected(graphic); diff --git a/src/jlgraphic/plugins/GraphicTransformPlugin.ts b/src/jlgraphic/plugins/GraphicTransformPlugin.ts index 369d55b..07fa18e 100644 --- a/src/jlgraphic/plugins/GraphicTransformPlugin.ts +++ b/src/jlgraphic/plugins/GraphicTransformPlugin.ts @@ -171,11 +171,40 @@ export class GraphicTransformPlugin extends InteractionPluginBase { this.app.canvas.addAssistantAppend(this.apContainer); app.on('options-update', (options) => { if (options.absorbablePositions) { - this.absorbablePositions = options.absorbablePositions; + this.absorbablePositions = this.filterAbsorbablePositions( + options.absorbablePositions + ); } }); } + /** + * 过滤重复的吸附位置 + * @param positions + * @returns + */ + filterAbsorbablePositions( + positions: AbsorbablePosition[] + ): AbsorbablePosition[] { + const aps: AbsorbablePosition[] = []; + for (let i = 0; i < positions.length; i++) { + const ap1 = positions[i]; + let ap: AbsorbablePosition | null = ap1; + for (let j = positions.length - 1; j > i; j--) { + const ap2 = positions[j]; + if (ap.isOverlapping(ap2) && ap.compareTo(ap2) <= 0) { + ap = null; + break; + } + } + if (ap != null) { + aps.push(ap); + } + } + // console.log(positions, aps); + return aps; + } + static new(app: GraphicApp) { return new GraphicTransformPlugin(app); } @@ -266,9 +295,7 @@ export class GraphicTransformPlugin extends InteractionPluginBase { if (this.absorbablePositions) { for (let i = 0; i < this.absorbablePositions.length; i++) { const ap = this.absorbablePositions[i]; - if (ap.tryAbsorb(...targets)) { - break; - } + ap.tryAbsorb(...targets); } } // 事件发布 diff --git a/src/jlgraphic/utils/GraphicUtils.ts b/src/jlgraphic/utils/GraphicUtils.ts index 254e4bf..a710fde 100644 --- a/src/jlgraphic/utils/GraphicUtils.ts +++ b/src/jlgraphic/utils/GraphicUtils.ts @@ -607,3 +607,71 @@ export function getIntersectionPoint(line1: number[], line2: number[]) { -denominator; return new Point(x, y); } + +/** + * 是否平行线 + * @param p1 + * @param p2 + * @param pa + * @param pb + * @returns + */ +export function isParallelLines( + p1: IPointData, + p2: IPointData, + pa: IPointData, + pb: IPointData +): boolean { + const vle1 = Vector2.direction(Vector2.from(p1), Vector2.from(p2)); + const vle2 = Vector2.direction(Vector2.from(pa), Vector2.from(pb)); + if (vle2.equals(vle1)) { + return true; + } + return vle1.equals(Vector2.direction(Vector2.from(pb), Vector2.from(pa))); +} + +/** + * 点是否在线段上 + * @param p1 + * @param p2 + * @param p + * @returns + */ +export function isPointOnLine( + p1: IPointData, + p2: IPointData, + p: IPointData +): boolean { + const vp1 = Vector2.from(p1); + const vp2 = Vector2.from(p2); + const vp = Vector2.from(p); + if (vp1.equals(vp) || vp2.equals(vp)) { + return true; + } + const vle = Vector2.direction(vp1, Vector2.from(p2)); + const vpe = Vector2.direction(vp1, vp); + if (vle.equals(vpe)) { + return ( + Vector2.difference(vp1, vp2).squaredLength() >= + Vector2.difference(vp1, vp).squaredLength() + ); + } + return false; +} +/** + * 两条线段是否存在包含关系 + * @param line1 + * @param line2 + * @returns + */ +export function isLineContainOther( + line1: { p1: IPointData; p2: IPointData }, + line2: { p1: IPointData; p2: IPointData } +): boolean { + return ( + (isPointOnLine(line1.p1, line1.p2, line2.p1) && + isPointOnLine(line1.p1, line1.p2, line2.p2)) || + (isPointOnLine(line2.p1, line2.p2, line1.p1) && + isPointOnLine(line2.p1, line2.p2, line1.p2)) + ); +} diff --git a/src/layouts/DrawLayout.vue b/src/layouts/DrawLayout.vue index b6bc4a4..0c3dccc 100644 --- a/src/layouts/DrawLayout.vue +++ b/src/layouts/DrawLayout.vue @@ -134,6 +134,12 @@ onMounted(() => { const dom = document.getElementById('draw-app-container'); if (dom) { const drawApp = drawStore.initDrawApp(dom); + drawApp.on('websocket-connected', (app) => { + console.log('应用websocket成功连接'); + }); + drawApp.on('websocket-disconnected', (app) => { + console.log('应用websocket断开连接'); + }); loadDrawDatas(drawApp); onResize(); drawApp.enableWsStomp({