gojs
Version:
Interactive diagrams, charts, and graphs, such as trees, flowcharts, orgcharts, UML, BPMN, or business diagrams
905 lines • 105 kB
JavaScript
/*
* Copyright (C) 1998-2023 by Northwoods Software Corporation
* All Rights Reserved.
*
* FLOOR PLANNER: WALL RESHAPING TOOL
* Used to reshape walls via their endpoints in a Floorplan
*/
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../../../release/go"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.WallReshapingTool = void 0;
var go = require("../../../release/go");
var WallReshapingTool = /** @class */ (function (_super) {
__extends(WallReshapingTool, _super);
/**
* @constructor
* This tool is responsible for allowing walls in a Floorplan to be reshaped via handles on either side.
*/
function WallReshapingTool() {
var _this = _super.call(this) || this;
var h = new go.Shape();
h.figure = 'Diamond';
h.desiredSize = new go.Size(12, 12);
h.fill = 'lightblue';
h.stroke = 'dodgerblue';
h.cursor = 'move';
_this._handleArchetype = h;
_this._handle = null;
_this._adornedShape = null;
_this._reshapeObjectName = 'SHAPE';
_this._angle = 0;
_this._length = 0;
_this._isBuilding = false; // only true when a wall is first being constructed, set in WallBuildingTool's doMouseUp function
_this._isIntersecting = false;
_this._joinedWalls = new go.Set();
_this._returnPoint = null; // used if reshape is cancelled; return reshaping wall endpoint to its previous location
_this._returnData = null; // used if reshape is cancelled; return all windows/doors of a reshaped wall to their old place
_this._joinedWalls = new go.Set();
_this._wallIntersecting = null;
return _this;
}
Object.defineProperty(WallReshapingTool.prototype, "handleArchetype", {
// Get the archetype for the handle (a Shape)
get: function () { return this._handleArchetype; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "handle", {
// Get / set current handle being used to reshape the wall
get: function () { return this._handle; },
set: function (value) { this._handle = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "adornedShape", {
// Get / set adorned shape (shape of the Wall Group being reshaped)
get: function () { return this._adornedShape; },
set: function (value) { this._adornedShape = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "angle", {
// Get / set current angle
get: function () { return this._angle; },
set: function (value) { this._angle = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "length", {
// Get / set length of the wall being reshaped (used only with SHIFT + drag)
get: function () { return this._length; },
set: function (value) { this._length = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "reshapeObjectName", {
// Get / set the name of the object being reshaped
get: function () { return this._reshapeObjectName; },
set: function (value) { this._reshapeObjectName = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "isBuilding", {
// Get / set flag telling tool whether it's reshaping a new wall (isBuilding = true) or reshaping an old wall (isBuilding = false)
get: function () { return this._isBuilding; },
set: function (value) { this._isBuilding = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "returnData", {
// Get set loc data for wallParts to return to if reshape is cancelled
get: function () { return this._returnData; },
set: function (value) { this._returnData = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "returnPoint", {
// Get / set the point to return the reshaping wall endpoint to if reshape is cancelled
get: function () { return this._returnPoint; },
set: function (value) { this._returnPoint = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "isIntersecting", {
// Get / set whether the reshaping wall is intersecting at least one other wall. if so, ignore grid snap
get: function () { return this._isIntersecting; },
set: function (value) { this._isIntersecting = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "wallIntersecting", {
// Get / set the wall the reshaping endpoint is currently intersecting
get: function () { return this._wallIntersecting; },
set: function (value) { this._wallIntersecting = value; },
enumerable: false,
configurable: true
});
Object.defineProperty(WallReshapingTool.prototype, "joinedWalls", {
// Get / set the wall created during after a reshape event by combining some colinear walls
get: function () { return this._joinedWalls; },
set: function (value) { this._joinedWalls = value; },
enumerable: false,
configurable: true
});
/**
* Places reshape handles on either end of a wall node.
* @param {go.Part} part The wall to adorn
*/
WallReshapingTool.prototype.updateAdornments = function (part) {
if (part === null || part instanceof go.Link)
return;
if (part.isSelected && !this.diagram.isReadOnly) {
var seleltgo = part.findObject(this.reshapeObjectName);
if (seleltgo !== null && seleltgo.part !== null && seleltgo.part.data.category === 'WallGroup') {
var selelt = seleltgo;
var adornment = part.findAdornment(this.name);
if (adornment === null) {
adornment = this.makeAdornment(selelt);
}
if (adornment !== null && selelt.part !== null && selelt.geometry != null) {
// update the position/alignment of each handle
var geo = selelt.geometry;
var b_1 = geo.bounds;
var pb_1 = selelt.part.actualBounds;
// update the size of the adornment
var graphObj = adornment.findObject('BODY');
if (graphObj === null)
return;
graphObj.desiredSize = b_1.size;
adornment.elements.each(function (h) {
if (h.name === undefined)
return;
var x = 0;
var y = 0;
switch (h.name) {
case 'sPt': {
x = part.data.startpoint.x - pb_1.x;
y = part.data.startpoint.y - pb_1.y;
break;
}
case 'ePt': {
x = part.data.endpoint.x - pb_1.x;
y = part.data.endpoint.y - pb_1.y;
break;
}
}
var xCheck = Math.min((x - b_1.x) / b_1.width, 1);
var yCheck = Math.min((y - b_1.y) / b_1.height, 1);
if (xCheck < 0)
xCheck = 0;
if (yCheck < 0)
yCheck = 0;
if (xCheck > 1)
xCheck = 1;
if (yCheck > 1)
yCheck = 1;
if (isNaN(xCheck))
xCheck = 0;
if (isNaN(yCheck))
yCheck = 0;
h.alignment = new go.Spot(Math.max(0, xCheck), Math.max(0, yCheck));
});
part.addAdornment(this.name, adornment);
adornment.location = selelt.getDocumentPoint(go.Spot.Center);
return;
}
}
}
part.removeAdornment(this.name);
};
/**
* If the user has clicked down at a visible handle on a wall node, then the tool may start.
* @return {boolean}
*/
WallReshapingTool.prototype.canStart = function () {
if (!this.isEnabled)
return false;
var diagram = this.diagram;
if (diagram === null || diagram.isReadOnly)
return false;
if (!diagram.allowReshape)
return false;
if (!diagram.lastInput.left)
return false;
var h = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
return (h !== null || this.isBuilding);
};
/**
* Start a new transaction for the wall reshaping.
* Store pre-reshape location of reshaping wall's reshaping endpoint.
* Store pre-reshape locations of all wall's members (windows / doors).
*/
WallReshapingTool.prototype.doActivate = function () {
var diagram = this.diagram;
if (diagram === null)
return;
if (this.isBuilding) {
// this.adornedShape has already been set in WallBuildingTool's doMouseDown function
if (this.adornedShape !== null && this.adornedShape.part !== null) {
var wall = this.adornedShape.part;
this.handle = this.findToolHandleAt(wall.data.endpoint, this.name);
this.returnPoint = wall.data.startpoint;
}
}
else {
this.handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
if (this.handle === null)
return;
var adorn = this.handle.part;
var shape = adorn.adornedObject;
var wall = shape.part;
if (!shape)
return;
this.adornedShape = shape;
// store pre-reshape location of wall's reshaping endpoint
this.returnPoint = this.handle.name === 'sPt' ? wall.data.startpoint : wall.data.endpoint;
// store pre-reshape locations of all wall's members (windows / doors)
var wallParts = wall.memberParts;
if (wallParts.count !== 0) {
var locationsMap_1 = new go.Map();
wallParts.iterator.each(function (wallPart) {
locationsMap_1.add(wallPart.data.key, wallPart.location);
});
this.returnData = locationsMap_1;
}
}
// diagram.isMouseCaptured = true;
this.startTransaction(this.name);
this.isActive = true;
};
/**
* Adjust the handle's coordinates, along with the wall's points.
*/
WallReshapingTool.prototype.doMouseMove = function () {
var fp = this.diagram;
var tool = this;
if (tool.handle === null)
return;
var adorn = tool.handle.part;
var wall = adorn.adornedPart;
// the stationaryPt
var mousePt = fp.lastInput.documentPoint;
if (tool.isActive && fp !== null) {
// if user is holding shift, make sure the angle of the reshaping wall (from stationaryPt to mousePt) is a multiple of 45
if (fp.lastInput.shift) {
// what's the current angle made from stationaryPt to mousePt?
var type = tool.handle.name;
var stationaryPt = (type === 'sPt') ? wall.data.endpoint : wall.data.startpoint;
var ang = stationaryPt.directionPoint(mousePt);
var length_1 = Math.sqrt(stationaryPt.distanceSquaredPoint(mousePt));
ang = Math.round(ang / 45) * 45;
var newPoint = new go.Point(stationaryPt.x + length_1, stationaryPt.y);
// rotate the new point ang degrees
var dx = stationaryPt.x;
var dy = stationaryPt.y;
newPoint = newPoint.offset(-dx, -dy); // move point to origin
newPoint = newPoint.rotate(ang); // rotate ang degrees around origin
newPoint = newPoint.offset(dx, dy); // add back offset
mousePt = newPoint;
}
// if the mousePt is close to some wall's endpoint, snap the mousePt to that endpoint
var walls = fp.findNodesByExample({ category: 'WallGroup' });
walls.iterator.each(function (w) {
if (w.data.key !== wall.data.key) {
var spt = w.data.startpoint;
var ept = w.data.endpoint;
// if the mousePt is inside the geometry of another wall, project the point onto that wall
if (fp.isPointInWall(w, mousePt)) {
mousePt = mousePt.projectOntoLineSegmentPoint(w.data.startpoint, w.data.endpoint);
tool.isIntersecting = true; // yes, the current reshaping wall is intersecting another wall
}
// if the mousePt is close to some wall's endpoint, snap the mousePt to that endpoint
if (Math.sqrt(spt.distanceSquaredPoint(mousePt)) < 10) {
mousePt = spt;
}
else if (Math.sqrt(ept.distanceSquaredPoint(mousePt)) < 10) {
mousePt = ept;
}
}
});
// if the resulting segment between stationary pt and mousePt would intersect other wall(s), project mousePt onto the first wall it would intersect
var iw = tool.getClosestIntersectingWall(mousePt);
// if we are or just were intersecting some wall, miter it
if (iw === null || tool.wallIntersecting !== null) {
if (tool.wallIntersecting !== null && tool.wallIntersecting !== undefined && tool.wallIntersecting.data !== null) {
tool.performMiteringOnWall(tool.wallIntersecting);
}
}
if (iw != null) {
tool.isIntersecting = true; // yes, the current reshaping wall is intersecting another wall
tool.wallIntersecting = iw;
mousePt = mousePt.projectOntoLineSegmentPoint(iw.data.startpoint, iw.data.endpoint);
// if the mousePt is really close to an endpoint of its intersecting wall, make it that endpoint
var distToSpt = Math.sqrt(mousePt.distanceSquaredPoint(iw.data.startpoint));
var distToEpt = Math.sqrt(mousePt.distanceSquaredPoint(iw.data.endpoint));
if (distToSpt < 25) {
mousePt = iw.data.startpoint;
}
else if (distToEpt < 10) {
mousePt = iw.data.endpoint;
}
}
else {
tool.isIntersecting = false;
// if the wall we were previously intersecting is not touching the reshaping wall, forget it
if (tool.wallIntersecting !== null && tool.wallIntersecting !== undefined &&
tool.wallIntersecting.data !== null && fp.getWallsIntersection(wall, tool.wallIntersecting) === null) {
tool.wallIntersecting = null;
}
}
tool.calcAngleAndLengthFromHandle(mousePt); // sets this.angle and this.length (useful for when SHIFT is held)
tool.reshape(mousePt);
}
tool.performMiteringOnWall(wall);
fp.updateWallDimensions();
fp.updateWallAngles();
};
/**
* Get the closest wall the reshaping wall intersects with.
* Returns null if reshaping wall does not intersect with any other wall.
* @param {go.Point} proposedPt The proposed point for the reshaping wall's moving pt
* @return {go.Group | null} The closest wall the reshaping wall's reshaping endpoint intersects with
*/
WallReshapingTool.prototype.getClosestIntersectingWall = function (proposedPt) {
var tool = this;
if (tool.handle === null)
return null;
var adorn = tool.handle.part;
var wall = adorn.adornedPart;
var type = tool.handle.name;
var stationaryPt = (type === 'sPt') ? wall.data.endpoint : wall.data.startpoint;
// dummy wall is used for intersection checks, since the reshaping wall has not had its data yet set
var dummyWallData = {
key: 'wall', category: 'WallGroup', caption: 'Wall', type: 'Wall', startpoint: stationaryPt,
smpt1: stationaryPt, smpt2: stationaryPt, endpoint: proposedPt, empt1: proposedPt, empt2: proposedPt,
thickness: parseFloat(tool.diagram.model.modelData.wallThickness), isGroup: true, notes: ''
};
tool.diagram.model.addNodeData(dummyWallData);
var dummyWall = tool.diagram.findPartForKey(dummyWallData.key);
var fp = tool.diagram;
var walls = tool.diagram.findNodesByExample({ category: 'WallGroup' });
var closestWall = null;
var closestDistance = Number.MAX_VALUE;
walls.iterator.each(function (w) {
if (w.data.key !== wall.data.key && w.data.key !== dummyWall.data.key) {
// check if wall and w intersect, and if so, where
var intersectPoint = fp.getWallsIntersection(dummyWall, w);
// also, don't project onto a wall the stationaryPt is already along (this would make two walls on top of each other)
var isStationaryPtOnW = false;
var ab = parseFloat(Math.sqrt(w.data.startpoint.distanceSquaredPoint(stationaryPt)).toFixed(2));
var bc = parseFloat(Math.sqrt(stationaryPt.distanceSquaredPoint(w.data.endpoint)).toFixed(2));
var ac = parseFloat(Math.sqrt(w.data.startpoint.distanceSquaredPoint(w.data.endpoint)).toFixed(2));
if (Math.abs((ab + bc) - ac) <= .1) {
isStationaryPtOnW = true;
}
if (intersectPoint !== null && !isStationaryPtOnW) {
// calc distance from stationaryPoint to proposed intersection point
var dist = Math.sqrt(stationaryPt.distanceSquaredPoint(intersectPoint));
if (dist < closestDistance) {
closestDistance = dist;
closestWall = w;
}
}
}
});
// remove the dummy wall
fp.remove(dummyWall);
return closestWall;
};
/**
* Returns whether or not 2 points are "close enough" to each other.
* "Close enough" is, by default, defined as a point whose x and y values are within .05
* document units of another point's x and y values.
* @param {go.Point} p1
* @param {go.Point} p2
* @return {boolean}
*/
WallReshapingTool.prototype.pointsApproximatelyEqual = function (p1, p2) {
var x1 = p1.x;
var x2 = p2.x;
var y1 = p1.y;
var y2 = p2.y;
var diff1 = Math.abs(x2 - x1);
var diff2 = Math.abs(y2 - y1);
if (diff2 < .05 && diff1 < .05) {
return true;
}
return false;
};
/**
* Sets the counterclockwise mitering point for wallA / clockwise mitering point for wallB.
* This algorithm based on https://math.stackexchange.com/questions/1849784/calculate-miter-points-of-stroked-vectors-in-cartesian-plane.
* @param {go.Group} wa wallA
* @param {go.Group} wb wallB
*/
WallReshapingTool.prototype.performMitering = function (wa, wb) {
var tool = this;
var diagram = this.diagram;
// wall endpoints, thicknesses, lengths
var as = wa.data.startpoint;
var ae = wa.data.endpoint;
var bs = wb.data.startpoint;
var be = wb.data.endpoint;
var wat = wa.data.thickness;
var wbt = wb.data.thickness;
var wal = Math.sqrt(as.distanceSquaredPoint(ae));
var wbl = Math.sqrt(bs.distanceSquaredPoint(be));
// points
var B = diagram.getWallsIntersection(wa, wb); // intersection point
if (B === null) {
return;
}
var A = (tool.pointsApproximatelyEqual(as, B)) ? ae : as; // wallA non-intersection point
var C = (tool.pointsApproximatelyEqual(bs, B)) ? be : bs; // wallB non-intersection point
// edge case: non-endpoint intersection
// must know which wall is outer wall (the one who has no endpoint in the intersection)
// and which wall is inner wall (the one with an endpoint in the intersection)
var ow = null;
var iw = null;
if (!tool.pointsApproximatelyEqual(as, B) && !tool.pointsApproximatelyEqual(ae, B)) {
ow = wa;
iw = wb;
}
else if (!tool.pointsApproximatelyEqual(bs, B) && !tool.pointsApproximatelyEqual(be, B)) {
ow = wb;
iw = wa;
}
// if wall A is the inner wall, use the endpoint of wall B that counterclockwise from point A for point C
if (ow !== null && iw !== null && wa.data.key === iw.data.key) {
if (tool.isClockwise(A, B, ow.data.startpoint)) {
C = ow.data.startpoint;
}
else {
C = ow.data.endpoint;
}
}
// if wall B is the inner wall, use endpoint of wall A that's clockwise from point C for point A
if (ow !== null && iw !== null && wb.data.key === iw.data.key) {
if (tool.isClockwise(B, C, ow.data.startpoint)) {
A = ow.data.startpoint;
}
else {
A = ow.data.endpoint;
}
}
// angle between wallA and wallB, clockwise, in degrees
var a1 = B.directionPoint(A);
var a2 = B.directionPoint(C);
var ang = Math.abs(a1 - a2 + 360) % 360;
if (Math.abs(ang - 180) < .1) {
return;
}
ang = ang * (Math.PI / 180); // radians
// create a parallelogram with altitudes wat/2 and wbt/2, s.t. u and v are the lengths from B to reach D (counterclockwise mitering point)
var u = Math.abs(wbt / (2 * (Math.sin(ang))));
var v = Math.abs(wat / (2 * (Math.sin(ang))));
// get u and v vectors
var ab = Math.sqrt(A.distanceSquaredPoint(B));
var bc = Math.sqrt(B.distanceSquaredPoint(C));
var ux = ((A.x - B.x) / ab) * u;
var uy = ((A.y - B.y) / ab) * u;
// only for endpoint-endpoint?
var vx = ((C.x - B.x) / bc) * v;
var vy = ((C.y - B.y) / bc) * v;
// these are the mitering points
var D = new go.Point(B.x + ux + vx, B.y + uy + vy);
var E = new go.Point(B.x - ux - vx, B.y - uy - vy);
// miter limit TODO???
var minLength = Math.min(wal, wbl);
if (Math.sqrt(D.distanceSquaredPoint(B)) > minLength) {
return;
}
// mitering point / other mitering point
var mpt = tool.isClockwise(B, A, D) ? E : D;
if (isNaN(mpt.x) || isNaN(mpt.y)) {
return;
}
// now figure out which mitering point of wallA's data to modify
// only modify a mitering point in data if B is one of wallA's endpoints
if (tool.pointsApproximatelyEqual(as, B) || tool.pointsApproximatelyEqual(ae, B)) {
var prop = null;
// wall A's direction to point B is from startpoint to endpoint
if (tool.pointsApproximatelyEqual(A, as)) {
// if ang2 is clockwise of ang1, update empt1
if (tool.isClockwise(A, B, mpt)) {
prop = 'empt1';
}
else {
prop = 'empt2';
}
}
else if (tool.pointsApproximatelyEqual(A, ae)) {
// wall A's direction to point B is from endpoint to startpoint
if (tool.isClockwise(A, B, mpt)) {
prop = 'smpt2';
}
else {
prop = 'smpt1';
}
}
if (prop !== null) {
diagram.model.setDataProperty(wa.data, prop, mpt);
diagram.updateWall(wa);
}
}
// same, but for wall B
if (tool.pointsApproximatelyEqual(bs, B) || tool.pointsApproximatelyEqual(be, B)) {
var prop = null;
// wall A's direction to point B is from startpoint to endpoint
if (tool.pointsApproximatelyEqual(C, bs)) {
// if ang2 < ang1, update empt1
if (tool.isClockwise(C, B, mpt)) {
prop = 'empt1';
}
else {
prop = 'empt2';
}
}
else if (tool.pointsApproximatelyEqual(C, be)) {
// wall A's direction to point B is from endpoint to startpoint
if (tool.isClockwise(C, B, mpt)) {
prop = 'smpt2';
}
else {
prop = 'smpt1';
}
}
if (prop !== null) {
diagram.model.setDataProperty(wb.data, prop, mpt);
diagram.updateWall(wb);
}
}
};
/**
* Returns a set of all the wall intersections in the entire floorplan.
* Each entry is a stringified points (i.e. "0 0").
* @return {go.Set<string>}
*/
WallReshapingTool.prototype.getAllWallIntersectionPoints = function () {
var tool = this;
var diagram = tool.diagram;
// get all walls
var walls = diagram.findNodesByExample({ category: 'WallGroup' });
var intersectionPoints = new go.Set(); // set of Points where walls intersect
walls.iterator.each(function (w) {
// for each wall, go through all other walls; if this wall intersects another wall, mark it as an intersection point
var otherWalls = diagram.findNodesByExample({ category: 'WallGroup' });
otherWalls.iterator.each(function (ow) {
if (ow.data.key === w.data.key)
return; // do not check for intersection with self
var ip = diagram.getWallsIntersection(w, ow);
var doAdd = true;
if (ip !== null) {
// make sure there is not already an intersection point in the set that's really close to this one
intersectionPoints.iterator.each(function (ips) {
var ip2 = go.Point.parse(ips);
if (tool.pointsApproximatelyEqual(ip2, ip)) {
doAdd = false;
}
});
if (doAdd) {
intersectionPoints.add(go.Point.stringify(ip));
}
}
});
});
return intersectionPoints;
};
/**
* Get all the walls with an endpoint at a given Point.
* Returns a List of all walls involved in that intersection.
* @param {go.Point | null} intersectionPoint
* @param {boolean} includeDividers Whether or not to also include Room Dividers with endpoints at intersectionPoint. Default is true.
* @return {go.List<go.Group>}
*/
WallReshapingTool.prototype.getAllWallsAtIntersection = function (intersectionPoint, includeDividers) {
if (includeDividers === undefined || includeDividers === null) {
includeDividers = true;
}
var tool = this;
var diagram = tool.diagram;
var wallsInvolved = new go.List(); // list of walls, which will be sorted clockwise
if (intersectionPoint === null) {
return wallsInvolved;
}
diagram.findObjectsNear(intersectionPoint, 1, function (x) {
if (x.part !== null) {
return x.part;
}
return null;
}, function (p) {
if (!(p instanceof go.Group && p.category === 'WallGroup' && (includeDividers || !p.data.isDivider) && !wallsInvolved.contains(p)))
return false;
// make sure the wall's segment includes ip
var s = p.data.startpoint;
var e = p.data.endpoint;
return tool.isPointOnSegment(s, e, intersectionPoint);
}, true, wallsInvolved);
return wallsInvolved;
};
/**
* Returns whether or not 2 walls share at least one endpoint
* @param {go.Group} wa
* @param {go.Group} wb
* @return {boolean}
*/
WallReshapingTool.prototype.doWallsShareAnEndpoint = function (wa, wb) {
var tool = this;
var as = wa.data.startpoint;
var ae = wa.data.endpoint;
var bs = wb.data.startpoint;
var be = wb.data.endpoint;
if (tool.pointsApproximatelyEqual(as, bs) || tool.pointsApproximatelyEqual(as, be)
|| tool.pointsApproximatelyEqual(ae, bs) || tool.pointsApproximatelyEqual(ae, be)) {
return true;
}
return false;
};
/**
* This function, called on {@link doMouseUp} event, checks if the reshaping wall's reshaping endpoint is now intersecting a wall.
* If so, that intersected wall is split into 2 walls at the intersection point. All walls at the intersection point are then mitered.
* Next, it checks if the reshapingWall has become a new, big wall (via {@link joinColinearWalls}).
* If so, we must split the new wall at any points it intersects with others.
* Room boundary data that depended on the split wall is then updated to reflect the split.
*/
WallReshapingTool.prototype.maybeSplitWall = function () {
var tool = this;
if (tool.handle === null)
return;
var adorn = tool.handle.part;
var reshapingWall = adorn.adornedPart;
var movingProp = tool.handle.name;
var movingPt = movingProp === 'sPt' ? reshapingWall.data.startpoint : reshapingWall.data.endpoint;
var jw = tool.joinedWalls;
var wallsAtEndpoint = tool.getAllWallsAtIntersection(movingPt);
// exclude the reshapingWall from wallsAtEndpoint
wallsAtEndpoint.remove(reshapingWall);
jw.iterator.each(function (ww) {
wallsAtEndpoint.remove(ww);
});
if (wallsAtEndpoint.count === 1) {
var wallToSplit = wallsAtEndpoint.first();
if (wallToSplit !== null) {
// make sure this is not an endpoint to endpoint connection
if (!tool.doWallsShareAnEndpoint(reshapingWall, wallToSplit)) {
tool.maybePerformWallSplit(wallToSplit, movingPt);
}
}
}
// if we're building a wall, it's possible we need to split at the stationary pt too
if (tool.isBuilding) {
var stationaryPt = movingPt === reshapingWall.data.startpoint ? reshapingWall.data.endpoint : reshapingWall.data.startpoint;
var wallsAtStationaryPt = tool.getAllWallsAtIntersection(stationaryPt);
wallsAtStationaryPt.remove(reshapingWall);
jw.iterator.each(function (ww) {
wallsAtEndpoint.remove(ww);
});
if (wallsAtStationaryPt.count === 1) {
var wallToSplit = wallsAtStationaryPt.first();
if (wallToSplit !== null) {
// make sure this is not an endpoint to endpoint connection
if (!tool.doWallsShareAnEndpoint(reshapingWall, wallToSplit)) {
tool.maybePerformWallSplit(wallToSplit, stationaryPt);
}
}
}
}
// if this reshape event has created a big joined wall, the joined wall may need to be split
// find out if either endpoint of the original reshaping wall is NOT one of the endpoints of joinedWall
// if so, split the joinedWall at that endpoint
if (jw !== null) {
jw.iterator.each(function (ww) {
// find all points along the joined wall where it intersects with other walls and split along them
tool.splitNewWall(ww);
});
}
};
/**
* Finds all points along a new wall (created via {@link joinColinearWalls}) and splits / miters at each.
* @param w The newly joined wall
*/
WallReshapingTool.prototype.splitNewWall = function (w) {
var tool = this;
var fp = this.diagram;
// find all walls that intersect this wall
var walls = fp.findNodesByExample({ category: 'WallGroup' });
var ips = new go.Set();
walls.iterator.each(function (ww) {
var ip = fp.getWallsIntersection(w, ww);
if (ip !== null) {
ips.add(ip);
}
});
ips.iterator.each(function (ip) {
var wi = tool.getAllWallsAtIntersection(ip);
wi.iterator.each(function (ww) {
var s = ww.data.startpoint;
var e = ww.data.endpoint;
if (!tool.pointsApproximatelyEqual(s, ip) && !tool.pointsApproximatelyEqual(e, ip)) {
tool.maybePerformWallSplit(ww, ip);
}
});
});
};
/**
* Split a given wall into 2 at a given intersection point, if the given point is on the wall and not on one of the wall's endpoints.
* The resultant two walls are then mitered.
* Room boundary data that depended on the split wall is then updated to reflect the split.
* @param {go.Group} w wall to split
* @param {go.Point} ip intersection point where the split should occur
*/
WallReshapingTool.prototype.maybePerformWallSplit = function (w, ip) {
var tool = this;
var fp = tool.diagram;
var s = w.data.startpoint;
var e = w.data.endpoint;
var type = w.data.isDivider ? 'Divider' : 'Wall';
// this wall has neither endpoint in the intersection -- it must be split into 2 walls
var data1 = {
key: 'wall', category: 'WallGroup', caption: type, type: type, color: w.data.color,
startpoint: s, endpoint: ip, smpt1: s, smpt2: s, empt1: ip, empt2: ip,
thickness: w.data.thickness, isGroup: true, notes: '',
isDivider: w.data.isDivider
};
var data2 = {
key: 'wall', category: 'WallGroup', caption: type, type: type, color: w.data.color,
startpoint: ip, endpoint: e, smpt1: ip, smpt2: ip, empt1: e, empt2: e,
thickness: w.data.thickness, isGroup: true, notes: '',
isDivider: w.data.isDivider
};
// only actually split the wall if the 2 new walls would both have at least length 1
// and if there are no walls with endpoints very close to these proposed ones
var l1 = Math.sqrt(data1.startpoint.distanceSquaredPoint(data1.endpoint));
var l2 = Math.sqrt(data2.startpoint.distanceSquaredPoint(data2.endpoint));
var walls = fp.findNodesByExample({ category: 'WallGroup' });
var alreadyExists = false;
walls.iterator.each(function (wc) {
var ws = wc.data.startpoint;
var we = wc.data.endpoint;
if ((tool.pointsApproximatelyEqual(s, ws) && tool.pointsApproximatelyEqual(ip, we)) ||
(tool.pointsApproximatelyEqual(s, we) && tool.pointsApproximatelyEqual(ip, ws))) {
alreadyExists = true;
}
if ((tool.pointsApproximatelyEqual(ip, ws) && tool.pointsApproximatelyEqual(e, we)) ||
(tool.pointsApproximatelyEqual(ip, we) && tool.pointsApproximatelyEqual(e, ws))) {
alreadyExists = true;
}
});
if (l1 > 1 && l2 > 1 && !alreadyExists) {
fp.model.addNodeData(data1);
fp.model.addNodeData(data2);
var w1_1 = fp.findNodeForData(data1);
var w2_1 = fp.findNodeForData(data2);
// Before removing the original wall from the Floorplan, update relevant room boundarywalls data
// iff this method is being called as a result of a user-prompted wall reshape action
// needed so proper mitering side of replacement entry walls can be determined
tool.premiterWall(w1_1);
tool.premiterWall(w2_1);
tool.performMiteringAtPoint(ip, false);
if (tool.handle !== null) {
var rooms = fp.findNodesByExample({ category: 'RoomNode' });
var adorn = tool.handle.part;
var rw_1 = adorn.adornedPart; // reshaping wall
// go through rooms, find any whose boundary walls contain w (the wall that was split, soon to be removed)
rooms.iterator.each(function (r) {
var bw = r.data.boundaryWalls;
var _loop_1 = function (i) {
var entry = bw[i];
var wk = entry[0];
if (wk === w.data.key) {
// then, find out if the reshaping wall, at the non-ip endpoint, is connected to another wall in that room's boundary walls
var isConnectedToBounds_1 = false;
var nonIpEndpoint = (tool.pointsApproximatelyEqual(rw_1.data.startpoint, ip)) ? rw_1.data.endpoint : rw_1.data.startpoint;
var iw = tool.getAllWallsAtIntersection(nonIpEndpoint);
iw.iterator.each(function (ww) {
// if boundary walls contains ww and ww is not the reshaping wall, reshaping wall is connected to room boundary walls at non ip endpoint
for (var j = 0; j < bw.length; j++) {
var ee = bw[j];
var wk2 = ee[0];
if (ww.data.key === wk2 && ww.data.key !== rw_1.data.key) {
isConnectedToBounds_1 = true;
}
}
});
// if yes, replace the w entry in boundary walls with just one new entry, using the split wall that is connected to some other wall in bounds
if (isConnectedToBounds_1) {
// find out whether w1 or w2 is connected to another wall in boundary walls
var isW1ConnectedToBounds_1 = false;
var w1NonIpEndpoint_1 = (tool.pointsApproximatelyEqual(w1_1.data.startpoint, ip)) ? w1_1.data.endpoint : w1_1.data.startpoint;
var iw2 = tool.getAllWallsAtIntersection(w1NonIpEndpoint_1);
iw2.remove(w); // do not include the wall soon to be destroyed
// go through all walls at w1's non-ip endpoint and find out if one of those is in r's boundary walls
iw2.iterator.each(function (ww) {
var _loop_2 = function (j) {
var entry2 = bw[j];
var wk2 = entry2[0];
if (ww.data.key === wk2 && w1_1.data.key !== ww.data.key) {
// additional followup -- make sure ww2 is still connected to r's boundary walls at other endpoint (not connected to w1NonIpEndpoint)
var ww2_1 = fp.findNodeForKey(wk2);
var ww2OtherEndpoint = (tool.pointsApproximatelyEqual(ww2_1.data.startpoint, w1NonIpEndpoint_1)) ? ww2_1.data.endpoint : ww2_1.data.startpoint;
var iw3 = tool.getAllWallsAtIntersection(ww2OtherEndpoint);
iw3.iterator.each(function (ww3) {
for (var k = 0; k < bw.length; k++) {
var entry3 = bw[k];
var wk3 = entry3[0];
if (wk3 === ww3.data.key && wk3 !== ww2_1.data.key) {
isW1ConnectedToBounds_1 = true;
}
}
});
}
};
for (var j = 0; j < bw.length; j++) {
_loop_2(j);
}
});
// replace this entry of r's boundary walls with the replacementWall
var replacementWall = (isW1ConnectedToBounds_1) ? w1_1 : w2_1;
var replacementEntry = tool.getUpdatedEntry(entry, replacementWall);
fp.startTransaction();
var newBounds = bw.slice();
newBounds[i] = replacementEntry;
fp.model.setDataProperty(r.data, 'boundaryWalls', newBounds);
fp.commitTransaction();
}
else {
// if no, replace the w entry with both split walls. Order those 2 entries CC, relative to reshaping wall
// get a List of walls involved (reshaping wall, w1, and w2)
var wi = new go.List();
wi.add(rw_1);
wi.add(w1_1);
wi.add(w2_1);
wi = fp.sortWallsClockwiseWithSetStartWall(wi, rw_1);
// get replacement entries for the entry with w
var replacementEntry2 = tool.getUpdatedEntry(entry, wi.toArray()[1]);
var replacementEntry1 = tool.getUpdatedEntry(entry, wi.toArray()[2]);
// insert these replacement entries into the bw at index i, remove
fp.startTransaction();
var newBounds = bw.slice();
newBounds.splice(i, 1, replacementEntry1);
newBounds.splice(i + 1, 0, replacementEntry2);
fp.model.setDataProperty(r.data, 'boundaryWalls', newBounds);
fp.commitTransaction();
}
}
};
for (var i = 0; i < bw.length; i++) {
_loop_1(i);
}
}); // end rooms iteration
}
// Maintain wall parts that were on the big wall -- give them new locations on the most appropriate of the split walls, if possible
var wallParts = fp.findNodesByExample({ group: w.data.key });
var wallsSet = new go.Set();
wallsSet.add(w1_1);
wallsSet.add(w2_1);
tool.maintainWallParts(wallParts, wallsSet);
// remove original wall
fp.remove(w);
// perform mitering
tool.premiterWall(w1_1);
tool.premiterWall(w2_1);
var w1op = tool.pointsApproximatelyEqual(w1_1.data.startpoint, ip) ? w1_1.data.endpoint : w1_1.data.startpoint;
var w2op = tool.pointsApproximatelyEqual(w2_1.data.startpoint, ip) ? w2_1.data.endpoint : w2_1.da