feat: 后端全量更新 - 含所有本次需求
- Contract.php: 返回合约账户余额(balance_contract) - My.php: 地址管理增加BTC/ETH - AppContract.php: 一键平仓(closeall) - AppProxy.php: 代理专属注册链接 + 分级权限(L1/L2) - site.php: 手续费减半(0.018→0.009) - agent_permission_setup.sql: 代理权限SQL - crypto_news_crawler.py: 新闻自动采集脚本
This commit is contained in:
+198
@@ -0,0 +1,198 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - FURNITURE
|
||||
* GraphObject templates for interactional furniture nodes (and their dependecies) used in the Floor Planner sample
|
||||
* Includes Default Node (Furniture), MultiPurpose Node
|
||||
*/
|
||||
|
||||
/*
|
||||
* Furniture Node Dependencies:
|
||||
* Node Tool Tip, Furniture Resize Adornment Template, Furniture Rotate Adornment Template, Invert Color
|
||||
*/
|
||||
|
||||
// Node Tool Tip
|
||||
function makeNodeToolTip() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", function (text, obj) {
|
||||
var data = obj.part.adornedObject.data;
|
||||
var name = (obj.part.adornedObject.category === "MultiPurposeNode") ? data.text : data.caption;
|
||||
return "Name: " + name + "\nNotes: " + data.notes;
|
||||
}).ofObject())
|
||||
)
|
||||
}
|
||||
|
||||
// Furniture Resize Adornment
|
||||
function makeFurnitureResizeAdornmentTemplate() {
|
||||
var $ = go.GraphObject.make;
|
||||
function makeHandle(alignment, cursor) {
|
||||
return $(go.Shape, { alignment: alignment, cursor: cursor, figure: "Rectangle", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" },
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "stroke"));
|
||||
}
|
||||
|
||||
return $(go.Adornment, "Spot",
|
||||
$(go.Placeholder),
|
||||
makeHandle(go.Spot.Top, "n-resize"),
|
||||
makeHandle(go.Spot.TopRight, "n-resize"),
|
||||
makeHandle(go.Spot.BottomRight, "se-resize"),
|
||||
makeHandle(go.Spot.Right, "e-resize"),
|
||||
makeHandle(go.Spot.Bottom, "s-resize"),
|
||||
makeHandle(go.Spot.BottomLeft, "sw-resize"),
|
||||
makeHandle(go.Spot.Left, "w-resize"),
|
||||
makeHandle(go.Spot.TopLeft, "nw-resize")
|
||||
);
|
||||
}
|
||||
|
||||
// Furniture Rotate Adornment
|
||||
function makeFurnitureRotateAdornmentTemplate() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment,
|
||||
$(go.Shape, "Circle", { cursor: "pointer", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" },
|
||||
new go.Binding("fill", "", function (obj) { return (obj.adornedPart === null) ? "#ffffff" : obj.adornedPart.data.color; }).ofObject(),
|
||||
new go.Binding("stroke", "", function (obj) { return (obj.adornedPart === null) ? "#000000" : obj.adornedPart.data.stroke; }).ofObject())
|
||||
);
|
||||
}
|
||||
|
||||
// Return inverted color (in hex) of a given hex code color; used to determine furniture node stroke color
|
||||
function invertColor(hexnum) {
|
||||
if (hexnum.includes('#')) hexnum = hexnum.substring(1);
|
||||
if (hexnum.length != 6) {
|
||||
console.error("Hex color must be six hex numbers in length.");
|
||||
return false;
|
||||
}
|
||||
|
||||
hexnum = hexnum.toUpperCase();
|
||||
var splitnum = hexnum.split("");
|
||||
var resultnum = "";
|
||||
var simplenum = "FEDCBA9876".split("");
|
||||
var complexnum = new Array();
|
||||
complexnum.A = "5";
|
||||
complexnum.B = "4";
|
||||
complexnum.C = "3";
|
||||
complexnum.D = "2";
|
||||
complexnum.E = "1";
|
||||
complexnum.F = "0";
|
||||
|
||||
for (i = 0; i < 6; i++) {
|
||||
if (!isNaN(splitnum[i])) {
|
||||
resultnum += simplenum[splitnum[i]];
|
||||
} else if (complexnum[splitnum[i]]) {
|
||||
resultnum += complexnum[splitnum[i]];
|
||||
} else {
|
||||
console.error("Hex colors must only include hex numbers 0-9, and A-F");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return '#' + resultnum;
|
||||
}
|
||||
|
||||
/*
|
||||
* Furniture Node Templates:
|
||||
* Default Node, MultiPurpose Node
|
||||
*/
|
||||
|
||||
// Default Node
|
||||
function makeDefaultNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
resizable: true,
|
||||
rotatable: true,
|
||||
toolTip: makeNodeToolTip(),
|
||||
resizeAdornmentTemplate: makeFurnitureResizeAdornmentTemplate(),
|
||||
rotateAdornmentTemplate: makeFurnitureRotateAdornmentTemplate(),
|
||||
contextMenu: makeContextMenu(),
|
||||
locationObjectName: "SHAPE",
|
||||
resizeObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
minSize: new go.Size(5, 5),
|
||||
locationSpot: go.Spot.Center,
|
||||
selectionAdorned: false, // use a Binding on the Shape.stroke to show selection
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow")
|
||||
}
|
||||
},
|
||||
// remember Node location
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// move selected Node to Foreground layer so it's not obscuerd by non-selected Parts
|
||||
new go.Binding("layerName", "isSelected", function (s) {
|
||||
return s ? "Foreground" : "";
|
||||
}).ofObject(),
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "SHAPE", stroke: "#000000",
|
||||
fill: "rgba(128, 128, 128, 0.5)"
|
||||
},
|
||||
new go.Binding("figure", "shape"),
|
||||
new go.Binding("geometryString", "geo"),
|
||||
new go.Binding("width").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
return s ? go.Brush.lightenBy(obj.stroke, .5) : invertColor(obj.part.data.color);
|
||||
}).ofObject())
|
||||
)
|
||||
}
|
||||
|
||||
// MultiPurpose Node
|
||||
function makeMultiPurposeNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
toolTip: makeNodeToolTip(),
|
||||
locationSpot: go.Spot.Center,
|
||||
resizeAdornmentTemplate: makeFurnitureResizeAdornmentTemplate(),
|
||||
rotateAdornmentTemplate: makeFurnitureRotateAdornmentTemplate(),
|
||||
locationObjectName: "SHAPE",
|
||||
resizable: true,
|
||||
rotatable: true,
|
||||
resizeObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
minSize: new go.Size(5, 5),
|
||||
selectionAdorned: false,
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow")
|
||||
}
|
||||
},
|
||||
// remember location, angle, height, and width of the node
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
// move a selected part into the Foreground layer so it's not obscuerd by non-selected Parts
|
||||
new go.Binding("layerName", "isSelected", function (s) { return s ? "Foreground" : ""; }).ofObject(),
|
||||
$(go.Shape,
|
||||
{ strokeWidth: 1, name: "SHAPE", fill: "rgba(128, 128, 128, 0.5)", },
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
new go.Binding("width").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
return s ? go.Brush.lightenBy(obj.stroke, .5) : invertColor(obj.part.data.color);
|
||||
}).ofObject()
|
||||
),
|
||||
$(go.TextBlock,
|
||||
{
|
||||
margin: 5,
|
||||
wrap: go.TextBlock.WrapFit,
|
||||
textAlign: "center",
|
||||
editable: true,
|
||||
isMultiline: false,
|
||||
stroke: '#454545',
|
||||
font: "10pt sans-serif"
|
||||
},
|
||||
new go.Binding("text").makeTwoWay(),
|
||||
new go.Binding("angle", "angle").makeTwoWay(),
|
||||
new go.Binding("font", "height", function (height) {
|
||||
if (height > 25) return "10pt sans-serif";
|
||||
if (height < 25 && height > 15) return "8pt sans-serif";
|
||||
else return "6pt sans-serif";
|
||||
}),
|
||||
new go.Binding("stroke", "color", function (color) { return invertColor(color); })
|
||||
)
|
||||
)
|
||||
}
|
||||
+388
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - GENERAL
|
||||
* General GraphObject templates used in the Floor Planner sample
|
||||
* Includes Context Menu, Diagram, Default Group, AngleNode, DimensionLink, PointNode
|
||||
*/
|
||||
|
||||
/*
|
||||
* Dependencies for Context Menu:
|
||||
* Make Selection Group, Ungroup Selection, Clear Empty Groups
|
||||
*/
|
||||
|
||||
// Make the selection a group
|
||||
function makeSelectionGroup(floorplan) {
|
||||
floorplan.startTransaction("group selection");
|
||||
// ungroup all selected nodes; then group them; if one of the selected nodes is a group, ungroup all its nodes
|
||||
var sel = floorplan.selection; var nodes = [];
|
||||
sel.iterator.each(function (n) {
|
||||
if (n instanceof go.Group) n.memberParts.iterator.each(function (part) { nodes.push(part); })
|
||||
else nodes.push(n);
|
||||
});
|
||||
for (var i = 0; i < nodes.length; i++) nodes[i].isSelected = true;
|
||||
ungroupSelection(floorplan);
|
||||
floorplan.commandHandler.groupSelection();
|
||||
var group = floorplan.selection.first(); // after grouping, the new group will be the only thing selected
|
||||
floorplan.model.setDataProperty(group.data, "caption", "Group");
|
||||
floorplan.model.setDataProperty(group.data, "notes", "");
|
||||
clearEmptyGroups(floorplan);
|
||||
// unselect / reselect group so data appears properly in Selection Info Window
|
||||
floorplan.clearSelection();
|
||||
floorplan.select(group);
|
||||
floorplan.commitTransaction("group selection");
|
||||
}
|
||||
|
||||
// Ungroup selected nodes; if the selection is a group, ungroup all it's memberParts
|
||||
function ungroupSelection(floorplan) {
|
||||
floorplan.startTransaction('ungroup selection');
|
||||
// helper function to ungroup nodes
|
||||
function ungroupNode(node) {
|
||||
var group = node.containingGroup;
|
||||
node.containingGroup = null;
|
||||
if (group != null) {
|
||||
if (group.memberParts.count === 0) floorplan.remove(group);
|
||||
else if (group.memberParts.count === 1) group.memberParts.first().containingGroup = null;
|
||||
}
|
||||
}
|
||||
// ungroup any selected nodes; remember groups that are selected
|
||||
var sel = floorplan.selection; var groups = [];
|
||||
sel.iterator.each(function (n) {
|
||||
if (!(n instanceof go.Group)) ungroupNode(n);
|
||||
else groups.push(n);
|
||||
});
|
||||
// go through selected groups, and ungroup their memberparts too
|
||||
var nodes = [];
|
||||
for (var i = 0; i < groups.length; i++) groups[i].memberParts.iterator.each(function (n) { nodes.push(n); });
|
||||
for (var i = 0; i < nodes.length; i++) ungroupNode(nodes[i]);
|
||||
clearEmptyGroups(floorplan);
|
||||
floorplan.commitTransaction('ungroup selection');
|
||||
}
|
||||
|
||||
// Clear all the groups that have no nodes
|
||||
function clearEmptyGroups(floorplan) {
|
||||
var nodes = floorplan.nodes; var arr = [];
|
||||
nodes.iterator.each(function (node) { if (node instanceof go.Group && node.memberParts.count === 0 && node.category !== "WallGroup") { arr.push(node); } });
|
||||
for (i = 0; i < arr.length; i++) { floorplan.remove(arr[i]); }
|
||||
}
|
||||
|
||||
/*
|
||||
* General Group Dependencies:
|
||||
* Group Tool Tip
|
||||
*/
|
||||
|
||||
// Group Tool Tip
|
||||
function makeGroupToolTip() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4 },
|
||||
new go.Binding("text", "", function (text, obj) {
|
||||
var data = obj.part.adornedObject.data;
|
||||
var name = (obj.part.adornedObject.category === "MultiPurposeNode") ? data.text : data.caption;
|
||||
return "Name: " + name + "\nNotes: " + data.notes + '\nMembers: ' + obj.part.adornedObject.memberParts.count;
|
||||
}).ofObject())
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* General Templates:
|
||||
* Context Menu, Default Group
|
||||
*/
|
||||
|
||||
// Context Menu -- referenced by Node, Diagram and Group Templates
|
||||
function makeContextMenu() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Adornment, "Vertical",
|
||||
// Make Selection Group Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Make Group"),
|
||||
{ click: function (e, obj) { makeSelectionGroup(obj.part.diagram); } },
|
||||
new go.Binding("visible", "visible", function (v, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan.selection.count <= 1) return false;
|
||||
var flag = true;
|
||||
floorplan.selection.iterator.each(function (node) {
|
||||
if (node.category === "WallGroup" || node.category === "WindowNode" || node.category === "DoorNode") flag = false;
|
||||
});
|
||||
return flag;
|
||||
}).ofObject()
|
||||
),
|
||||
// Ungroup Selection Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Ungroup"),
|
||||
{ click: function (e, obj) { ungroupSelection(obj.part.diagram); } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan !== null) {
|
||||
var node = floorplan.selection.first();
|
||||
return ((node instanceof go.Node && node.containingGroup != null && node.containingGroup.category != 'WallGroup') ||
|
||||
(node instanceof go.Group && node.category === ''));
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Copy Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Copy"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.copySelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Cut Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Cut"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.cutSelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Delete Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Delete"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.deleteSelection() } },
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Paste Button
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Paste"),
|
||||
{ click: function (e, obj) { obj.part.diagram.commandHandler.pasteSelection(obj.part.diagram.toolManager.contextMenuTool.mouseDownPoint) } }
|
||||
),
|
||||
// Show Selection Info Button (only available when selection count > 0)
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Show Selection Info"),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
if (e.diagram.floorplanUI) {
|
||||
var selectionInfoWindow = document.getElementById(e.diagram.floorplanUI.state.windows.selectionInfoWindow.id);
|
||||
if (selectionInfoWindow.style.visibility !== 'visible') e.diagram.floorplanUI.hideShow('selectionInfoWindow');
|
||||
}
|
||||
}
|
||||
},
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
return obj.part.diagram.selection.count > 0;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
),
|
||||
// Flip Dimension Side Button (only available when selection contains Wall Group(s))
|
||||
$("ContextMenuButton",
|
||||
$(go.TextBlock, "Flip Dimension Side"),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
if (floorplan !== null) {
|
||||
floorplan.startTransaction("flip dimension link side");
|
||||
var walls = [];
|
||||
floorplan.selection.iterator.each(function (part) {
|
||||
if (part.category === "WallGroup") walls.push(part);
|
||||
});
|
||||
for (var i = 0; i < walls.length; i++) {
|
||||
var wall = walls[i];
|
||||
var sPt = wall.data.startpoint.copy();
|
||||
var ePt = wall.data.endpoint.copy();
|
||||
floorplan.model.setDataProperty(wall.data, "startpoint", ePt);
|
||||
floorplan.model.setDataProperty(wall.data, "endpoint", sPt);
|
||||
floorplan.updateWall(wall);
|
||||
}
|
||||
floorplan.commitTransaction("flip dimension link side");
|
||||
}
|
||||
}
|
||||
},
|
||||
new go.Binding("visible", "", function (v, obj) {
|
||||
if (obj.part.diagram !== null) {
|
||||
var sel = obj.part.diagram.selection;
|
||||
if (sel.count === 0) return false;
|
||||
var flag = false;
|
||||
sel.iterator.each(function (part) {
|
||||
if (part.category === "WallGroup") flag = true;
|
||||
});
|
||||
return flag;
|
||||
} return false;
|
||||
}).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Default Group
|
||||
function makeDefaultGroup() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Group, "Vertical",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
doubleClick: function (e) {
|
||||
if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow");
|
||||
},
|
||||
toolTip: makeGroupToolTip()
|
||||
},
|
||||
new go.Binding("location", "loc"),
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, "RoundedRectangle", { fill: "rgba(128,128,128,0.15)", stroke: 'rgba(128, 128, 128, .05)', name: 'SHAPE', strokeCap: 'square' },
|
||||
new go.Binding("fill", "isSelected", function (s, obj) {
|
||||
return s ? "rgba(128, 128, 128, .15)" : "rgba(128, 128, 128, 0.10)";
|
||||
}).ofObject()
|
||||
),
|
||||
$(go.Placeholder, { padding: 5 }) // extra padding around group members
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/*
|
||||
* Dependencies for Angle Nodes:
|
||||
* Make Arc
|
||||
*/
|
||||
|
||||
// Return arc geometry for Angle Nodes
|
||||
function makeArc(node) {
|
||||
var ang = node.data.angle;
|
||||
var sweep = node.data.sweep;
|
||||
var rad = Math.min(30, node.data.maxRadius);
|
||||
if (typeof sweep === "number" && sweep > 0) {
|
||||
var start = new go.Point(rad, 0).rotate(ang);
|
||||
// this is much more efficient than calling go.GraphObject.make:
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(start.x + rad, start.y + rad) // start point
|
||||
.add(new go.PathSegment(go.PathSegment.Arc,
|
||||
ang, sweep, // angles
|
||||
rad, rad, // center
|
||||
rad, rad) // radius
|
||||
))
|
||||
.add(new go.PathFigure(0, 0))
|
||||
.add(new go.PathFigure(2 * rad, 2 * rad));
|
||||
} else { // make sure this arc always occupies the same circular area of RAD radius
|
||||
return new go.Geometry()
|
||||
.add(new go.PathFigure(0, 0))
|
||||
.add(new go.PathFigure(2 * rad, 2 * rad));
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Dependencies for Dimension Links
|
||||
* Make Point Node
|
||||
*/
|
||||
|
||||
// Return a Point Node (used for Dimension Links)
|
||||
function makePointNode() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Node, "Position", new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify));
|
||||
}
|
||||
|
||||
/*
|
||||
* Dynamically appearing parts:
|
||||
* Angle Node, Dimension Link
|
||||
*/
|
||||
|
||||
// Return an Angle Node (for each angle ndeeded in the diagram, one angle node is made)
|
||||
function makeAngleNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{ locationSpot: go.Spot.Center, locationObjectName: "SHAPE", selectionAdorned: false },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, "Circle", // placed where walls intersect, is invisible
|
||||
{ name: "SHAPE", height: 0, width: 0 }),
|
||||
$(go.Shape, // arc
|
||||
{ strokeWidth: 1.5, fill: null },
|
||||
new go.Binding("geometry", "", makeArc).ofObject(),
|
||||
new go.Binding("stroke", "sweep", function (sweep) {
|
||||
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
|
||||
})),
|
||||
// Arc label panel
|
||||
$(go.Panel, "Auto",
|
||||
{ name: "ARCLABEL" },
|
||||
// position the label in the center of the arc
|
||||
new go.Binding("alignment", "sweep", function (sweep, panel) {
|
||||
var rad = Math.min(30, panel.part.data.maxRadius);
|
||||
var angle = panel.part.data.angle;
|
||||
var cntr = new go.Point(rad, 0).rotate(angle + sweep / 2);
|
||||
return new go.Spot(0.5, 0.5, cntr.x, cntr.y);
|
||||
}),
|
||||
// rectangle containing angle text
|
||||
$(go.Shape,
|
||||
{ fill: "white" },
|
||||
new go.Binding("stroke", "sweep", function (sweep) {
|
||||
return (sweep % 45 < 1 || sweep % 45 > 44) ? "dodgerblue" : "lightblue";
|
||||
})),
|
||||
// angle text
|
||||
$(go.TextBlock,
|
||||
{ font: "7pt sans-serif", margin: 2 },
|
||||
new go.Binding("text", "sweep", function (sweep) {
|
||||
return sweep.toFixed(2) + String.fromCharCode(176);
|
||||
}))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Returns a Dimension Link
|
||||
function makeDimensionLink() {
|
||||
var $ = go.GraphObject.make
|
||||
return $(go.Link,
|
||||
// link itself
|
||||
$(go.Shape,
|
||||
{ stroke: "gray", strokeWidth: 2, name: 'SHAPE' }),
|
||||
// to arrow shape
|
||||
$(go.Shape,
|
||||
{ toArrow: "OpenTriangle", stroke: "gray", strokeWidth: 2 }),
|
||||
$(go.Shape,
|
||||
// from arrow shape
|
||||
{ fromArrow: "BackwardOpenTriangle", stroke: "gray", strokeWidth: 2 }),
|
||||
// dimension link text
|
||||
$(go.TextBlock,
|
||||
{ text: 'sometext', segmentOffset: new go.Point(0, -10), font: "13px sans-serif" },
|
||||
new go.Binding("text", "", function (link) {
|
||||
var floorplan = link.diagram;
|
||||
if (floorplan) {
|
||||
var fromPtNode = null; var toPtNode = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === link.data.from) fromPtNode = node;
|
||||
if (node.data.key === link.data.to) toPtNode = node;
|
||||
});
|
||||
if (fromPtNode !== null) {
|
||||
var fromPt = fromPtNode.location;
|
||||
var toPt = toPtNode.location;
|
||||
return floorplan.convertPixelsToUnits(Math.sqrt(fromPt.distanceSquaredPoint(toPt))).toFixed(2) + floorplan.model.modelData.unitsAbbreviation;
|
||||
} return null;
|
||||
} return null;
|
||||
}).ofObject(),
|
||||
// bind angle of textblock to angle of link -- always make text rightside up and readable
|
||||
new go.Binding("angle", "angle", function (angle, link) {
|
||||
if (angle > 90 && angle < 270) return (angle + 180) % 360;
|
||||
return angle;
|
||||
}),
|
||||
// default poisiton text above / below dimension link based on angle
|
||||
new go.Binding("segmentOffset", "angle", function (angle, textblock) {
|
||||
var floorplan = textblock.part.diagram;
|
||||
if (floorplan) {
|
||||
var wall = floorplan.findPartForKey(textblock.part.data.wall);
|
||||
if (wall.rotateObject.angle > 135 && wall.rotateObject.angle < 315) return new go.Point(0, 10);
|
||||
return new go.Point(0, -10);
|
||||
} return new go.Point(0, 0);
|
||||
}).ofObject(),
|
||||
// scale font size according to the length of the link
|
||||
new go.Binding("font", "", function (link) {
|
||||
var floorplan = link.diagram;
|
||||
var fromPtNode = null; var toPtNode = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === link.data.from) fromPtNode = node;
|
||||
if (node.data.key === link.data.to) toPtNode = node;
|
||||
});
|
||||
if (fromPtNode !== null) {
|
||||
var fromPt = fromPtNode.location;
|
||||
var toPt = toPtNode.location;
|
||||
var distance = Math.sqrt(fromPt.distanceSquaredPoint(toPt));
|
||||
if (distance > 40) return "13px sans-serif";
|
||||
if (distance <= 40 && distance >= 20) return "11px sans-serif";
|
||||
else return "9px sans-serif";
|
||||
} return "13px sans-serif";
|
||||
}).ofObject()
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,499 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER CODE: TEMPLATES - WALLS
|
||||
* GraphObject templates for Wall Groups, Wall Part Nodes (and their dependecies) used in the Floor Planner sample
|
||||
* Includes Wall Group, Palette Wall Node, Window Node, Door Node
|
||||
*/
|
||||
|
||||
/*
|
||||
* Wall Group Dependencies:
|
||||
* Snap Walls, Find Closest Loc on Wall, Add Wall Part, Wall Part Drag Over, Wall Part Drag Away
|
||||
*/
|
||||
|
||||
/*
|
||||
* Drag computation function to snap walls to the grid properly while dragging
|
||||
* @param {Node} part A reference to dragged Part
|
||||
* @param {Point} pt The Point describing the proposed location
|
||||
* @param {Point} gridPt Snapped location
|
||||
*/
|
||||
var snapWalls = function (part, pt, gridPt) {
|
||||
var floorplan = part.diagram;
|
||||
floorplan.updateWallDimensions();
|
||||
floorplan.updateWallAngles();
|
||||
floorplan.updateWall(part);
|
||||
var grid = part.diagram.grid;
|
||||
var sPt = part.data.startpoint.copy();
|
||||
var ePt = part.data.endpoint.copy();
|
||||
var dx = pt.x - part.location.x;
|
||||
var dy = pt.y - part.location.y;
|
||||
var newSpt = sPt.offset(dx, dy);
|
||||
var newEpt = ePt.offset(dx, dy);
|
||||
if (floorplan.toolManager.draggingTool.isGridSnapEnabled) {
|
||||
newSpt = newSpt.snapToGridPoint(grid.gridOrigin, grid.gridCellSize);
|
||||
newEpt = newEpt.snapToGridPoint(grid.gridOrigin, grid.gridCellSize);
|
||||
}
|
||||
floorplan.model.setDataProperty(part.data, "startpoint", newSpt);
|
||||
floorplan.model.setDataProperty(part.data, "endpoint", newEpt);
|
||||
return new go.Point((newSpt.x + newEpt.x) / 2, (newSpt.y + newEpt.y) / 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* Find closest loc (to mouse point) on wall a wallPart can be dropped onto without extending beyond wall endpoints or intruding into another wallPart
|
||||
* @param {Group} wall A reference to a Wall Group
|
||||
* @param {Node} part A reference to a Wall Part Node -- i.e. Door Node, Window Node
|
||||
*/
|
||||
function findClosestLocOnWall(wall, part) {
|
||||
var orderedConstrainingPts = []; // wall endpoints and wallPart endpoints
|
||||
var startpoint = wall.data.startpoint.copy();
|
||||
var endpoint = wall.data.endpoint.copy();
|
||||
// store all possible constraining endpoints (wall endpoints and wallPart endpoints) in the order in which they appear (left/top to right/bottom)
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var wallPartEndpoints = [];
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
wallPartEndpoints.push(endpoints[0]);
|
||||
wallPartEndpoints.push(endpoints[1]);
|
||||
});
|
||||
// sort all wallPartEndpoints by x coordinate left to right
|
||||
wallPartEndpoints.sort(function (a, b) {
|
||||
if ((a.x + a.y) > (b.x + b.y)) return 1;
|
||||
if ((a.x + a.y) < (b.x + b.y)) return -1;
|
||||
else return 0;
|
||||
});
|
||||
orderedConstrainingPts.push(firstWallPt);
|
||||
orderedConstrainingPts = orderedConstrainingPts.concat(wallPartEndpoints);
|
||||
orderedConstrainingPts.push(lastWallPt);
|
||||
|
||||
// go through all constraining points; if there's a free stretch along the wall "part" could fit in, remember it
|
||||
var possibleStretches = [];
|
||||
for (var i = 0; i < orderedConstrainingPts.length; i += 2) {
|
||||
var point1 = orderedConstrainingPts[i];
|
||||
var point2 = orderedConstrainingPts[i + 1];
|
||||
var distanceBetween = Math.sqrt(point1.distanceSquaredPoint(point2));
|
||||
if (distanceBetween >= part.data.length) possibleStretches.push({ pt1: point1, pt2: point2 });
|
||||
}
|
||||
|
||||
// go through all possible stretches along the wall the part *could* fit in; find the one closest to the part's current location
|
||||
var closestDist = Number.MAX_VALUE; var closestStretch = null;
|
||||
for (var i = 0; i < possibleStretches.length; i++) {
|
||||
var testStretch = possibleStretches[i];
|
||||
var testPoint1 = testStretch.pt1;
|
||||
var testPoint2 = testStretch.pt2;
|
||||
var testDistance1 = Math.sqrt(testPoint1.distanceSquaredPoint(part.location));
|
||||
var testDistance2 = Math.sqrt(testPoint2.distanceSquaredPoint(part.location));
|
||||
if (testDistance1 < closestDist) {
|
||||
closestDist = testDistance1;
|
||||
closestStretch = testStretch;
|
||||
}
|
||||
if (testDistance2 < closestDist) {
|
||||
closestDist = testDistance2;
|
||||
closestStretch = testStretch;
|
||||
}
|
||||
}
|
||||
|
||||
// Edge Case: If there's no space for the wallPart, return null
|
||||
if (closestStretch === null) return null;
|
||||
|
||||
// using the closest free stretch along the wall, calculate endpoints that make the stretch's line segment, then project part.location onto the segment
|
||||
var closestStretchLength = Math.sqrt(closestStretch.pt1.distanceSquaredPoint(closestStretch.pt2));
|
||||
var offset = part.data.length / 2;
|
||||
var point1 = new go.Point(closestStretch.pt1.x + ((offset / closestStretchLength) * (closestStretch.pt2.x - closestStretch.pt1.x)),
|
||||
closestStretch.pt1.y + ((offset / closestStretchLength) * (closestStretch.pt2.y - closestStretch.pt1.y)));
|
||||
var point2 = new go.Point(closestStretch.pt2.x + ((offset / closestStretchLength) * (closestStretch.pt1.x - closestStretch.pt2.x)),
|
||||
closestStretch.pt2.y + ((offset / closestStretchLength) * (closestStretch.pt1.y - closestStretch.pt2.y)));
|
||||
var newLoc = part.location.copy().projectOntoLineSegmentPoint(point1, point2);
|
||||
return newLoc;
|
||||
}
|
||||
|
||||
// MouseDrop event for wall groups; if a door or window is dropped on a wall, add it to the wall group
|
||||
// Do not allow dropping wallParts that would extend beyond wall endpoints or intrude into another wallPart
|
||||
var addWallPart = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
var wallPart = floorplan.selection.first();
|
||||
if ((wallPart && (wallPart.category === "WindowNode" || wallPart.category === "DoorNode") && wallPart.containingGroup === null)) {
|
||||
var newLoc = findClosestLocOnWall(wall, wallPart);
|
||||
if (newLoc !== null) {
|
||||
wall.findObject("SHAPE").stroke = makeBrush(wall.data);
|
||||
floorplan.model.setDataProperty(wallPart.data, "group", wall.data.key);
|
||||
wallPart.location = newLoc.projectOntoLineSegmentPoint(wall.data.startpoint, wall.data.endpoint);
|
||||
wallPart.angle = wall.rotateObject.angle;
|
||||
if (wallPart.category === "WindowNode") floorplan.model.setDataProperty(wallPart.data, "height", wall.data.thickness);
|
||||
if (wallPart.category === "DoorNode") floorplan.model.setDataProperty(wallPart.data, "doorOpeningHeight", wall.data.thickness);
|
||||
} else {
|
||||
floorplan.remove(wallPart);
|
||||
alert("There's not enough room on the wall!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(floorplan.selection.first(), floorplan);
|
||||
floorplan.updateWallDimensions();
|
||||
}
|
||||
|
||||
// MouseDragEnter event for walls; if a door or window is dragged over a wall, highlight the wall and change its angle
|
||||
var wallPartDragOver = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
var parts = floorplan.toolManager.draggingTool.draggingParts;
|
||||
parts.iterator.each(function (part) {
|
||||
if ((part.category === "WindowNode" || part.category === "DoorNode") && part.containingGroup === null) {
|
||||
wall.findObject("SHAPE").stroke = "lightblue";
|
||||
part.angle = wall.rotateObject.angle;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// MouseDragLeave event for walls; if a wall part is dragged past a wall, unhighlight the wall and change back the wall part's angle to 0
|
||||
var wallPartDragAway = function (e, wall) {
|
||||
var floorplan = e.diagram;
|
||||
wall.findObject("SHAPE").stroke = makeBrush(wall.data);
|
||||
var parts = floorplan.toolManager.draggingTool.draggingParts;
|
||||
parts.iterator.each(function (part) {
|
||||
if ((part.category === "WindowNode" || part.category === "DoorNode") && part.containingGroup === null) part.angle = 0
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Group Template
|
||||
*/
|
||||
|
||||
// Wall Group
|
||||
function makeWallGroup() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Group, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
toolTip: makeGroupToolTip(),
|
||||
selectionObjectName: "SHAPE",
|
||||
rotateObjectName: "SHAPE",
|
||||
locationSpot: go.Spot.Center,
|
||||
reshapable: true,
|
||||
minSize: new go.Size(1, 1),
|
||||
dragComputation: snapWalls,
|
||||
selectionAdorned: false,
|
||||
mouseDrop: addWallPart,
|
||||
mouseDragEnter: wallPartDragOver,
|
||||
mouseDragLeave: wallPartDragAway,
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); }
|
||||
},
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE" },
|
||||
new go.Binding("strokeWidth", "thickness"),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) {
|
||||
if (obj.part.containingGroup != null) {
|
||||
var group = obj.part.containingGroup;
|
||||
if (s) { group.data.isSelected = true; }
|
||||
}
|
||||
return s ? "dodgerblue" : makeBrush(obj.part.data);
|
||||
}).ofObject()
|
||||
))
|
||||
}
|
||||
|
||||
function makeBrush(data) {
|
||||
return "black";
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Part Node Dependencies:
|
||||
* Get Wall Part Endpoints, Get Wall Part Stretch, Drag Wall Parts (Drag Computation Function),
|
||||
* Wall Part Resize Adornment, Door Selection Adornment (Door Nodes only)
|
||||
*/
|
||||
|
||||
/*
|
||||
* Find and return an array of the endpoints of a given wallpart (window or door)
|
||||
* @param {Node} wallPart A Wall Part Node -- i.e. Door Node, Window Node
|
||||
*/
|
||||
function getWallPartEndpoints(wallPart) {
|
||||
var loc = wallPart.location;
|
||||
var partLength = wallPart.data.length;
|
||||
if (wallPart.containingGroup !== null) var angle = wallPart.containingGroup.rotateObject.angle;
|
||||
else var angle = 180;
|
||||
var point1 = new go.Point((loc.x + (partLength / 2)), loc.y);
|
||||
var point2 = new go.Point((loc.x - (partLength / 2)), loc.y);
|
||||
point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
var arr = []; arr.push(point1); arr.push(point2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns a "stretch" (2 Points) that constrains a wallPart (door or window), comprised of "part"'s containing wall endpoints or other wallPart endpoints
|
||||
* @param {Node} part A Wall Part Node -- i.e. Door Node, Window Node, that is attached to a wall
|
||||
*/
|
||||
function getWallPartStretch(part) {
|
||||
var wall = part.containingGroup;
|
||||
var startpoint = wall.data.startpoint.copy();
|
||||
var endpoint = wall.data.endpoint.copy();
|
||||
|
||||
// sort all possible endpoints into either left/above or right/below
|
||||
var leftOrAbove = new go.Set(/*go.Point*/); var rightOrBelow = new go.Set(/*go.Point*/);
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
if (wallPart.data.key !== part.data.key) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
if (endpoints[i].x < part.location.x || (endpoints[i].y > part.location.y && endpoints[i].x === part.location.x)) leftOrAbove.add(endpoints[i]);
|
||||
else rightOrBelow.add(endpoints[i]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// do the same with the startpoint and endpoint of the dragging part's wall
|
||||
if (parseFloat(startpoint.x.toFixed(2)) < parseFloat(part.location.x.toFixed(2)) || (startpoint.y > part.location.y && parseFloat(startpoint.x.toFixed(2)) === parseFloat(part.location.x.toFixed(2)))) leftOrAbove.add(startpoint);
|
||||
else rightOrBelow.add(startpoint);
|
||||
if (parseFloat(endpoint.x.toFixed(2)) < parseFloat(part.location.x.toFixed(2)) || (endpoint.y > part.location.y && parseFloat(endpoint.x.toFixed(2)) === parseFloat(part.location.x.toFixed(2)))) leftOrAbove.add(endpoint);
|
||||
else rightOrBelow.add(endpoint);
|
||||
|
||||
// of each set, find the closest point to the dragging part
|
||||
var leftOrAbovePt; var closestDistLeftOrAbove = Number.MAX_VALUE;
|
||||
leftOrAbove.iterator.each(function (point) {
|
||||
var distance = Math.sqrt(point.distanceSquaredPoint(part.location));
|
||||
if (distance < closestDistLeftOrAbove) {
|
||||
closestDistLeftOrAbove = distance;
|
||||
leftOrAbovePt = point;
|
||||
}
|
||||
});
|
||||
var rightOrBelowPt; var closestDistRightOrBelow = Number.MAX_VALUE;
|
||||
rightOrBelow.iterator.each(function (point) {
|
||||
var distance = Math.sqrt(point.distanceSquaredPoint(part.location));
|
||||
if (distance < closestDistRightOrBelow) {
|
||||
closestDistRightOrBelow = distance;
|
||||
rightOrBelowPt = point;
|
||||
}
|
||||
});
|
||||
|
||||
var stretch = { point1: leftOrAbovePt, point2: rightOrBelowPt };
|
||||
return stretch;
|
||||
}
|
||||
|
||||
/*
|
||||
* Drag computation function for WindowNodes and DoorNodes; ensure wall parts stay in walls when dragged
|
||||
* @param {Node} part A reference to dragged Part
|
||||
* @param {Point} pt The Point describing the proposed location
|
||||
* @param {Point} gridPt Snapped location
|
||||
*/
|
||||
var dragWallParts = function (part, pt, gridPt) {
|
||||
if (part.containingGroup !== null && part.containingGroup.category === 'WallGroup') {
|
||||
var floorplan = part.diagram;
|
||||
// Edge Case: if part is not on its wall (due to incorrect load) snap part.loc onto its wall immediately; ideally this is never called
|
||||
var wall = part.containingGroup;
|
||||
var wStart = wall.data.startpoint;
|
||||
var wEnd = wall.data.endpoint;
|
||||
var dist1 = Math.sqrt(wStart.distanceSquaredPoint(part.location));
|
||||
var dist2 = Math.sqrt(part.location.distanceSquaredPoint(wEnd));
|
||||
var totalDist = Math.sqrt(wStart.distanceSquaredPoint(wEnd));
|
||||
if (dist1 + dist2 !== totalDist) part.location = part.location.copy().projectOntoLineSegmentPoint(wStart, wEnd);
|
||||
|
||||
// main behavior
|
||||
var stretch = getWallPartStretch(part);
|
||||
var leftOrAbovePt = stretch.point1;
|
||||
var rightOrBelowPt = stretch.point2;
|
||||
|
||||
// calc points along line created by the endpoints that are half the width of the moving window/door
|
||||
var totalLength = Math.sqrt(leftOrAbovePt.distanceSquaredPoint(rightOrBelowPt));
|
||||
var distance = (part.data.length / 2);
|
||||
var point1 = new go.Point(leftOrAbovePt.x + ((distance / totalLength) * (rightOrBelowPt.x - leftOrAbovePt.x)),
|
||||
leftOrAbovePt.y + ((distance / totalLength) * (rightOrBelowPt.y - leftOrAbovePt.y)));
|
||||
var point2 = new go.Point(rightOrBelowPt.x + ((distance / totalLength) * (leftOrAbovePt.x - rightOrBelowPt.x)),
|
||||
rightOrBelowPt.y + ((distance / totalLength) * (leftOrAbovePt.y - rightOrBelowPt.y)));
|
||||
|
||||
// calc distance from pt to line (part's wall) - use point to 2pt line segment distance formula
|
||||
var distFromWall = Math.abs(((wEnd.y - wStart.y) * pt.x) - ((wEnd.x - wStart.x) * pt.y) + (wEnd.x * wStart.y) - (wEnd.y * wStart.x)) /
|
||||
Math.sqrt(Math.pow((wEnd.y - wStart.y), 2) + Math.pow((wEnd.x - wStart.x), 2));
|
||||
var tolerance = (20 * wall.data.thickness < 100) ? (20 * wall.data.thickness) : 100;
|
||||
|
||||
// if distance from pt to line > some tolerance, detach the wallPart from the wall
|
||||
if (distFromWall > tolerance) {
|
||||
part.containingGroup = null;
|
||||
delete part.data.group;
|
||||
part.angle = 0;
|
||||
floorplan.pointNodes.iterator.each(function (node) { floorplan.remove(node) });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { floorplan.remove(link) });
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.updateWallDimensions();
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(part);
|
||||
}
|
||||
|
||||
// project the proposed location onto the line segment created by the new points (ensures wall parts are constrained properly when dragged)
|
||||
pt = pt.copy().projectOntoLineSegmentPoint(point1, point2);
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("set loc");
|
||||
floorplan.model.setDataProperty(part.data, "loc", go.Point.stringify(pt));
|
||||
floorplan.commitTransaction("set loc");
|
||||
floorplan.skipsUndoManager = false;
|
||||
|
||||
floorplan.updateWallDimensions(); // update the dimension links created by having this wall part selected
|
||||
} return pt;
|
||||
}
|
||||
|
||||
// Resize Adornment for Wall Part Nodes
|
||||
function makeWallPartResizeAdornment() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Spot",
|
||||
{ name: "WallPartResizeAdornment" },
|
||||
$(go.Placeholder),
|
||||
$(go.Shape, { alignment: go.Spot.Left, cursor: "w-resize", figure: "Diamond", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" }),
|
||||
$(go.Shape, { alignment: go.Spot.Right, cursor: "e-resize", figure: "Diamond", desiredSize: new go.Size(7, 7), fill: "#ffffff", stroke: "#808080" })
|
||||
);
|
||||
}
|
||||
|
||||
// Selection Adornment for Door Nodes
|
||||
function makeDoorSelectionAdornment() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Adornment, "Vertical",
|
||||
{ name: "DoorSelectionAdornment" },
|
||||
$(go.Panel, "Auto",
|
||||
$(go.Shape, { fill: null, stroke: null }),
|
||||
$(go.Placeholder)),
|
||||
$(go.Panel, "Horizontal", { defaultStretch: go.GraphObject.Vertical },
|
||||
$("Button",
|
||||
$(go.Picture, { source: "icons/flipDoorOpeningLeft.png", column: 0, desiredSize: new go.Size(12, 12) },
|
||||
new go.Binding("source", "", function (obj) {
|
||||
if (obj.adornedPart === null) return "icons/flipDoorOpeningRight.png";
|
||||
else if (obj.adornedPart.data.swing === "left") return "icons/flipDoorOpeningRight.png";
|
||||
else return "icons/flipDoorOpeningLeft.png";
|
||||
}).ofObject()
|
||||
),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
floorplan.startTransaction("flip door");
|
||||
var door = obj.part.adornedPart;
|
||||
if (door.data.swing === "left") floorplan.model.setDataProperty(door.data, "swing", "right");
|
||||
else floorplan.model.setDataProperty(door.data, "swing", "left");
|
||||
floorplan.commitTransaction("flip door");
|
||||
},
|
||||
toolTip: $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4, text: "Flip Door Opening" }
|
||||
))
|
||||
},
|
||||
new go.Binding("visible", "", function (obj) { return (obj.adornedPart === null) ? false : (obj.adornedPart.containingGroup !== null); }).ofObject()
|
||||
),
|
||||
$("Button",
|
||||
$(go.Picture, { source: "icons/flipDoorSide.png", column: 0, desiredSize: new go.Size(12, 12) }),
|
||||
{
|
||||
click: function (e, obj) {
|
||||
var floorplan = obj.part.diagram;
|
||||
floorplan.startTransaction("rotate door");
|
||||
var door = obj.part.adornedPart;
|
||||
door.angle = (door.angle + 180) % 360;
|
||||
floorplan.commitTransaction("rotate door");
|
||||
},
|
||||
toolTip: $(go.Adornment, "Auto",
|
||||
$(go.Shape, { fill: "#FFFFCC" }),
|
||||
$(go.TextBlock, { margin: 4, text: "Flip Door Side" }
|
||||
))
|
||||
}
|
||||
),
|
||||
new go.Binding("visible", "", function (obj) { return (obj.adornedPart === null) ? false : (obj.adornedPart.containingGroup !== null); }).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wall Part Nodes:
|
||||
* Window Node, Door Node, Palette Wall Node
|
||||
*/
|
||||
|
||||
// Window Node
|
||||
function makeWindowNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
selectionObjectName: "SHAPE",
|
||||
selectionAdorned: false,
|
||||
locationSpot: go.Spot.Center,
|
||||
toolTip: makeNodeToolTip(),
|
||||
minSize: new go.Size(5, 5),
|
||||
resizable: true,
|
||||
resizeAdornmentTemplate: makeWallPartResizeAdornment(),
|
||||
resizeObjectName: "SHAPE",
|
||||
rotatable: false,
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); },
|
||||
dragComputation: dragWallParts,
|
||||
layerName: 'Foreground' // make sure windows are always in front of walls
|
||||
},
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE", fill: "white", strokeWidth: 0 },
|
||||
new go.Binding("width", "length").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "lightgray" : "white"; }).ofObject()
|
||||
),
|
||||
$(go.Shape,
|
||||
{ name: "LINESHAPE", fill: "darkgray", strokeWidth: 0, height: 10 },
|
||||
new go.Binding("width", "length", function (width, obj) { return width - 10; }), // 5px padding each side
|
||||
new go.Binding("height", "height", function (height, obj) { return (height / 5); }),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Door Node
|
||||
function makeDoorNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{
|
||||
contextMenu: makeContextMenu(),
|
||||
selectionObjectName: "SHAPE",
|
||||
selectionAdornmentTemplate: makeDoorSelectionAdornment(),
|
||||
locationSpot: go.Spot.BottomCenter,
|
||||
resizable: true,
|
||||
resizeObjectName: "OPENING_SHAPE",
|
||||
toolTip: makeNodeToolTip(),
|
||||
minSize: new go.Size(10, 10),
|
||||
doubleClick: function (e) { if (e.diagram.floorplanUI) e.diagram.floorplanUI.hideShow("selectionInfoWindow"); },
|
||||
dragComputation: dragWallParts,
|
||||
resizeAdornmentTemplate: makeWallPartResizeAdornment(),
|
||||
layerName: 'Foreground' // make sure windows are always in front of walls
|
||||
},
|
||||
// remember location of the Node
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
new go.Binding("angle").makeTwoWay(),
|
||||
// the door's locationSpot is affected by it's openingHeight, which is affected by the thickness of its containing wall
|
||||
new go.Binding("locationSpot", "doorOpeningHeight", function (doh, obj) { return new go.Spot(0.5, 1, 0, -(doh / 2)); }),
|
||||
// this is the shape that reprents the door itself and its swing
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE" },
|
||||
new go.Binding("width", "length"),
|
||||
new go.Binding("height", "length").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "color"),
|
||||
new go.Binding("geometryString", "swing", function (swing) {
|
||||
if (swing === "left") return "F1 M0,0 v-150 a150,150 0 0,1 150,150 ";
|
||||
else return "F1 M275,175 v-150 a150,150 0 0,0 -150,150 ";
|
||||
})
|
||||
),
|
||||
// door opening shape
|
||||
$(go.Shape,
|
||||
{
|
||||
name: "OPENING_SHAPE", fill: "white",
|
||||
strokeWidth: 0, height: 5, width: 40,
|
||||
alignment: go.Spot.BottomCenter, alignmentFocus: go.Spot.Center
|
||||
},
|
||||
new go.Binding("height", "doorOpeningHeight").makeTwoWay(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "lightgray" : "white"; }).ofObject(),
|
||||
new go.Binding("width", "length").makeTwoWay()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Palette Wall Node (becomes WallGroup when dropped from Palette onto diagram)
|
||||
function makePaletteWallNode() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot",
|
||||
{ selectionAdorned: false },
|
||||
$(go.Shape,
|
||||
{ name: "SHAPE", fill: "black", strokeWidth: 0, height: 10, figure: "Rectangle" },
|
||||
new go.Binding("width", "length").makeTwoWay(),
|
||||
new go.Binding("height").makeTwoWay(),
|
||||
new go.Binding("fill", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject(),
|
||||
new go.Binding("stroke", "isSelected", function (s, obj) { return s ? "dodgerblue" : "black"; }).ofObject())
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER - WALL BUILDING TOOL
|
||||
* Used to construct new Walls in a Floorplan with mouse clicking / mouse point
|
||||
* Depends on functionality in Floorplan.js
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
function WallBuildingTool() {
|
||||
go.Tool.call(this);
|
||||
this.name = "WallBuilding";
|
||||
this._startPoint = null;
|
||||
this._endPoint = null;
|
||||
this._wallReshapingTool = null;
|
||||
} go.Diagram.inherit(WallBuildingTool, go.Tool);
|
||||
|
||||
// Get / set the current startPoint
|
||||
Object.defineProperty(WallBuildingTool.prototype, "startPoint", {
|
||||
get: function () { return this._startPoint; },
|
||||
set: function (val) { this._startPoint = val; }
|
||||
});
|
||||
|
||||
// Get / set the current endPoint
|
||||
Object.defineProperty(WallBuildingTool.prototype, "endPoint", {
|
||||
get: function () { return this._endPoint; },
|
||||
set: function (val) { this._endPoint = val; }
|
||||
});
|
||||
|
||||
// Get / set the diagram's wallReshapingTool
|
||||
Object.defineProperty(WallBuildingTool.prototype, "wallReshapingTool", {
|
||||
get: function () { return this._wallReshapingTool; },
|
||||
set: function (val) { this._wallReshapingTool = val; }
|
||||
});
|
||||
|
||||
// Tool can start iff diagram exists, is editable, and tool is enabled
|
||||
WallBuildingTool.prototype.canStart = function () {
|
||||
var diagram = this.diagram;
|
||||
if (diagram !== null && !diagram.isReadOnly && this.isEnabled) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Start transaction, capture the mouse, use a crosshair cursor
|
||||
WallBuildingTool.prototype.doActivate = function () {
|
||||
this.endPoint = null;
|
||||
this.startTransaction(this.name);
|
||||
this.isMouseCaptured = true;
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
|
||||
// update wallThickness, based on the current value of the HTML input element
|
||||
// pre-condition: diagram.floorplanUI exists
|
||||
if (diagram.floorplanUI) {
|
||||
var el = document.getElementById(diagram.floorplanUI.state.wallThicknessInputId);
|
||||
if (isNaN(el.value) || el.value === null || el.value === '' || el.value === undefined) el.value = diagram.convertPixelsToUnits(5);
|
||||
diagram.model.setDataProperty(diagram.model.modelData, "wallThickness", diagram.convertUnitsToPixels(parseFloat(el.value)));
|
||||
}
|
||||
else diagram.model.setDataProperty(diagram.model.modelData, "wallThickness", diagram.convertUnitsToPixels(parseFloat(5)));
|
||||
|
||||
// assign startpoint based on grid
|
||||
var point1 = tool.diagram.lastInput.documentPoint;
|
||||
var gs = diagram.model.modelData.gridSize;
|
||||
if (!(tool.diagram.toolManager.draggingTool.isGridSnapEnabled)) gs = 1;
|
||||
var newx = gs * Math.round(point1.x / gs);
|
||||
var newy = gs * Math.round(point1.y / gs);
|
||||
var newPoint1 = new go.Point(newx, newy);
|
||||
this.startPoint = newPoint1;
|
||||
|
||||
this.wallReshapingTool = tool.diagram.toolManager.mouseDownTools.elt(3);
|
||||
// Default functionality:
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
// When user clicks, add wall model data to model and initialize a Wall Reshaping Tool to handle the shaping of its construction
|
||||
WallBuildingTool.prototype.doMouseDown = function () {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
tool.diagram.currentCursor = 'crosshair';
|
||||
var data = { key: "wall", category: "WallGroup", caption: "Wall", type: "Wall", startpoint: tool.startPoint, endpoint: tool.startPoint, thickness: parseFloat(diagram.model.modelData.wallThickness), isGroup: true, notes: "" };
|
||||
this.diagram.model.addNodeData(data);
|
||||
var wall = diagram.findPartForKey(data.key);
|
||||
diagram.updateWall(wall);
|
||||
var part = diagram.findPartForData(data);
|
||||
// set the TransactionResult before raising event, in case it changes the result or cancels the tool
|
||||
tool.transactionResult = tool.name;
|
||||
diagram.raiseDiagramEvent('PartCreated', part);
|
||||
|
||||
// start the wallReshapingTool, tell it what wall it's reshaping (more accurately, the shape that will have the reshape handle)
|
||||
tool.wallReshapingTool.isEnabled = true;
|
||||
diagram.select(part);
|
||||
tool.wallReshapingTool.isBuilding = true;
|
||||
tool.wallReshapingTool.adornedShape = part.findObject("SHAPE");
|
||||
tool.wallReshapingTool.doActivate();
|
||||
}
|
||||
|
||||
// If user presses Esc key, cancel the wall building
|
||||
WallBuildingTool.prototype.doKeyDown = function () {
|
||||
var diagram = this.diagram;
|
||||
var e = diagram.lastInput;
|
||||
if (e.key === "Esc") {
|
||||
var wall = diagram.selection.first();
|
||||
diagram.remove(wall);
|
||||
diagram.pointNodes.iterator.each(function (node) { diagram.remove(node); });
|
||||
diagram.dimensionLinks.iterator.each(function (link) { diagram.remove(link); });
|
||||
diagram.pointNodes.clear();
|
||||
diagram.dimensionLinks.clear();
|
||||
this.doDeactivate();
|
||||
}
|
||||
go.Tool.prototype.doKeyDown.call(this);
|
||||
}
|
||||
|
||||
// When the mouse moves, reshape the wall
|
||||
WallBuildingTool.prototype.doMouseMove = function () {
|
||||
this.wallReshapingTool.doMouseMove();
|
||||
}
|
||||
|
||||
// End transaction
|
||||
WallBuildingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
this.diagram.currentCursor = "";
|
||||
this.diagram.isMouseCaptured = false;
|
||||
|
||||
this.wallReshapingTool.isEnabled = false;
|
||||
this.wallReshapingTool.adornedShape = null;
|
||||
this.wallReshapingTool.doDeactivate();
|
||||
this.wallReshapingTool.isBuilding = false;
|
||||
|
||||
diagram.updateWallDimensions();
|
||||
|
||||
this.stopTransaction(this.name);
|
||||
|
||||
this.isActive = false; // Default functionality
|
||||
}
|
||||
+595
@@ -0,0 +1,595 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANNER: WALL RESHAPING TOOL
|
||||
* Used to reshape walls via their endpoints in a Floorplan
|
||||
* Depends on functionality in Floorplan.js
|
||||
*/
|
||||
|
||||
// Constructor
|
||||
function WallReshapingTool() {
|
||||
go.Tool.call(this);
|
||||
this.name = "WallReshaping";
|
||||
|
||||
var h = new go.Shape();
|
||||
h.figure = "Diamond";
|
||||
h.desiredSize = new go.Size(7, 7);
|
||||
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;
|
||||
this._isBuilding = false; // only true when a wall is first being constructed, set in WallBuildingTool's doMouseUp function
|
||||
|
||||
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
|
||||
} go.Diagram.inherit(WallReshapingTool, go.Tool);
|
||||
|
||||
// Get the archetype for the handle (a Shape)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "handleArchetype", {
|
||||
get: function () { return this._handleArchetype; }
|
||||
});
|
||||
|
||||
// Get / set current handle being used to reshape the wall
|
||||
Object.defineProperty(WallReshapingTool.prototype, "handle", {
|
||||
get: function () { return this._handle; },
|
||||
set: function (val) { this._handle = val; }
|
||||
});
|
||||
|
||||
// Get / set adorned shape (shape of the Wall Group being reshaped)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "adornedShape", {
|
||||
get: function () { return this._adornedShape; },
|
||||
set: function (val) { this._adornedShape = val; }
|
||||
});
|
||||
|
||||
// Get / set current angle
|
||||
Object.defineProperty(WallReshapingTool.prototype, "angle", {
|
||||
get: function () { return this._angle; },
|
||||
set: function (val) { this._angle = val; }
|
||||
});
|
||||
|
||||
// Get / set length of the wall being reshaped (used only with SHIFT + drag)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "length", {
|
||||
get: function () { return this._length; },
|
||||
set: function (val) { this._length = val; }
|
||||
});
|
||||
|
||||
// Get / set the name of the object being reshaped
|
||||
Object.defineProperty(WallReshapingTool.prototype, "reshapeObjectName", {
|
||||
get: function () { return this._reshapeObjectName; },
|
||||
set: function (val) { this._reshapeObjectName = val; }
|
||||
});
|
||||
|
||||
// Get / set flag telling tool whether it's reshaping a new wall (isBuilding = true) or reshaping an old wall (isBuilding = false)
|
||||
Object.defineProperty(WallReshapingTool.prototype, "isBuilding", {
|
||||
get: function () { return this._isBuilding; },
|
||||
set: function (val) { this._isBuilding = val; }
|
||||
});
|
||||
|
||||
// Get set loc data for wallParts to return to if reshape is cancelled
|
||||
Object.defineProperty(WallReshapingTool.prototype, "returnData", {
|
||||
get: function () { return this._returnData; },
|
||||
set: function (val) { this._returnData = val; }
|
||||
});
|
||||
|
||||
// Get / set the point to return the reshaping wall endpoint to if reshape is cancelled
|
||||
Object.defineProperty(WallReshapingTool.prototype, "returnPoint", {
|
||||
get: function () { return this._returnPoint; },
|
||||
set: function (val) { this._returnPoint = val; }
|
||||
});
|
||||
|
||||
/*
|
||||
* Places reshape handles on either end of a wall node
|
||||
* @param {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 selelt = part.findObject(this.reshapeObjectName);
|
||||
if (selelt.part.data.category === "WallGroup") {
|
||||
var adornment = part.findAdornment(this.name);
|
||||
if (adornment === null) {
|
||||
adornment = this.makeAdornment(selelt);
|
||||
}
|
||||
if (adornment !== null && selelt.geometry != null) {
|
||||
// update the position/alignment of each handle
|
||||
var geo = selelt.geometry;
|
||||
var b = geo.bounds;
|
||||
// update the size of the adornment
|
||||
adornment.findObject("BODY").desiredSize = b.size;
|
||||
adornment.elements.each(function (h) {
|
||||
if (h.name === undefined) return;
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
switch (h.name) {
|
||||
case 'sPt': x = geo.startX; y = geo.startY; break;
|
||||
case 'ePt': x = geo.endX; y = geo.endY; break;
|
||||
}
|
||||
var xCheck = Math.min((x - b.x) / b.width, 1);
|
||||
var yCheck = Math.min((y - b.y) / b.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);
|
||||
adornment.angle = selelt.getDocumentAngle();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
part.removeAdornment(this.name);
|
||||
}
|
||||
|
||||
// If the user has clicked down at a visible handle on a wall node, then the tool may start
|
||||
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
|
||||
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
|
||||
var wall = this.adornedShape.part;
|
||||
this.handle = this.findToolHandleAt(wall.data.endpoint, this.name);
|
||||
} else {
|
||||
this.handle = this.findToolHandleAt(diagram.firstInput.documentPoint, this.name);
|
||||
if (this.handle === null) return;
|
||||
var shape = this.handle.part.adornedObject;
|
||||
var wall = shape.part;
|
||||
if (!shape) return;
|
||||
this.adornedShape = shape;
|
||||
|
||||
// store pre-reshape location of wall's reshaping endpoint
|
||||
this.returnPoint = this.snapPointToGrid(diagram.firstInput.documentPoint);
|
||||
|
||||
// store pre-reshape locations of all wall's members (windows / doors)
|
||||
var wallParts = wall.memberParts;
|
||||
if (wallParts.count != 0) {
|
||||
var locationsMap = new go.Map(/*"string", go.Point*/);
|
||||
wallParts.iterator.each(function (wallPart) {
|
||||
locationsMap.add(wallPart.data.key, wallPart.location);
|
||||
});
|
||||
this.returnData = locationsMap;
|
||||
}
|
||||
}
|
||||
|
||||
this.calcAngleAndLengthFromHandle(diagram.firstInput.documentPoint);
|
||||
this.startTransaction(this.name);
|
||||
this.isActive = true;
|
||||
}
|
||||
|
||||
// Adjust the handle's coordinates, along with the wall's points
|
||||
WallReshapingTool.prototype.doMouseMove = function () {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
var wall = this.handle.part.adornedPart;
|
||||
|
||||
if (this.isActive && diagram !== null) {
|
||||
var mousePt = diagram.lastInput.documentPoint;
|
||||
tool.calcAngleAndLengthFromHandle(mousePt); // sets this.angle and this.length (useful for when SHIFT is held)
|
||||
var newpt = diagram.lastInput.documentPoint;
|
||||
if (diagram.floorplanUI) diagram.floorplanUI.setSelectionInfo(this.adornedShape.part, diagram); // update selection info window
|
||||
this.reshape(newpt);
|
||||
}
|
||||
diagram.updateWallAngles();
|
||||
}
|
||||
|
||||
// Does one final reshape, commits the transaction, then stops the tool
|
||||
WallReshapingTool.prototype.doMouseUp = function () {
|
||||
var diagram = this.diagram;
|
||||
if (this.isActive && diagram !== null) {
|
||||
var newpt = diagram.lastInput.documentPoint;
|
||||
this.reshape(newpt);
|
||||
this.transactionResult = this.name; // success
|
||||
}
|
||||
this.stopTool();
|
||||
}
|
||||
|
||||
// End the wall reshaping transaction
|
||||
WallReshapingTool.prototype.doDeactivate = function () {
|
||||
var diagram = this.diagram;
|
||||
var returnData = this.returnData;
|
||||
// if a wall reshaped to length < 1 px, remove it
|
||||
var wall = this.handle.part.adornedPart;
|
||||
var sPt = wall.data.startpoint;
|
||||
var ePt = wall.data.endpoint;
|
||||
var length = Math.sqrt(sPt.distanceSquared(ePt.x, ePt.y));
|
||||
if (length < 1) {
|
||||
diagram.remove(wall); // remove wall
|
||||
wall.memberParts.iterator.each(function (member) { diagram.remove(member); }) // remove wall's parts
|
||||
var wallDimensionLinkPointNodes = [];
|
||||
diagram.pointNodes.iterator.each(function (node) { if (node.data.key.indexOf(wall.data.key) !== -1) wallDimensionLinkPointNodes.push(node); });
|
||||
diagram.remove(wallDimensionLinkPointNodes[0]);
|
||||
diagram.remove(wallDimensionLinkPointNodes[1]);
|
||||
}
|
||||
|
||||
// remove wall's dimension links if tool cancelled via esc key
|
||||
if (diagram.lastInput.key === "Esc" && !this.isBuilding) {
|
||||
diagram.skipsUndoManager = true;
|
||||
diagram.startTransaction("reset to old data");
|
||||
if (this.handle.name === "sPt") wall.data.startpoint = this.returnPoint;
|
||||
else wall.data.endpoint = this.returnPoint;
|
||||
|
||||
diagram.updateWall(wall);
|
||||
|
||||
if (this.returnData) {
|
||||
this.returnData.iterator.each(function (kvp) {
|
||||
var key = kvp.key;
|
||||
var loc = kvp.value;
|
||||
var wallPart = diagram.findPartForKey(key);
|
||||
wallPart.location = loc;
|
||||
wallPart.rotateObject.angle = wall.rotateObject.angle;
|
||||
});
|
||||
}
|
||||
diagram.commitTransaction("reset to old data");
|
||||
diagram.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// remove guide line points
|
||||
var glPoints = this.diagram.findNodesByExample({ category: 'GLPointNode' });
|
||||
diagram.removeParts(glPoints, true);
|
||||
|
||||
diagram.updateWallDimensions();
|
||||
// commit transaction, deactivate tool
|
||||
diagram.commitTransaction(this.name);
|
||||
this.isActive = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Creates an adornment with 2 handles
|
||||
* @param {Shape} The adorned wall's Shape element
|
||||
*/
|
||||
WallReshapingTool.prototype.makeAdornment = function (selelt) {
|
||||
var adornment = new go.Adornment;
|
||||
adornment.type = go.Panel.Spot;
|
||||
adornment.locationObjectName = "BODY";
|
||||
adornment.locationSpot = go.Spot.Center;
|
||||
var h = new go.Shape();
|
||||
h.name = "BODY"
|
||||
h.fill = null;
|
||||
h.stroke = null;
|
||||
h.strokeWidth = 0;
|
||||
adornment.add(h);
|
||||
|
||||
h = this.makeHandle();
|
||||
h.name = 'sPt';
|
||||
adornment.add(h);
|
||||
h = this.makeHandle();
|
||||
h.name = 'ePt';
|
||||
adornment.add(h);
|
||||
|
||||
adornment.category = this.name;
|
||||
adornment.adornedObject = selelt;
|
||||
return adornment;
|
||||
}
|
||||
|
||||
// Creates a basic handle archetype
|
||||
WallReshapingTool.prototype.makeHandle = function () {
|
||||
var h = this.handleArchetype;
|
||||
return h.copy();
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the angle and length made from the mousepoint and the non-moving handle; used to reshape wall when holding SHIFT
|
||||
* @param {Point} mousePt The mouse cursors coordinate position
|
||||
*/
|
||||
WallReshapingTool.prototype.calcAngleAndLengthFromHandle = function (mousePt) {
|
||||
var tool = this;
|
||||
var diagram = this.diagram;
|
||||
var h = this.handle;
|
||||
var otherH;
|
||||
var node = this.adornedShape.part;
|
||||
var adornments = node.adornments.iterator;
|
||||
var adornment;
|
||||
adornments.each(function (a) { if (a.category === tool.name) adornment = a; })
|
||||
adornment.elements.each(function (e) {
|
||||
if (e.name != undefined && e.name != h.name) otherH = e;
|
||||
});
|
||||
|
||||
// calc angle from otherH against the horizontal
|
||||
var otherHandlePt = otherH.getDocumentPoint(go.Spot.Center);
|
||||
|
||||
deltaY = mousePt.y - otherHandlePt.y
|
||||
deltaX = mousePt.x - otherHandlePt.x
|
||||
var angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
||||
// because atan2 goes from -180 to +180 and we want it to be 0-360
|
||||
// so -90 becomes 270, etc.
|
||||
if (angle < 0) angle += 360;
|
||||
tool.angle = angle;
|
||||
|
||||
var distanceBetween = Math.sqrt(mousePt.distanceSquared(otherHandlePt.x, otherHandlePt.y));
|
||||
tool.length = distanceBetween;
|
||||
}
|
||||
|
||||
/*
|
||||
* Takes a point -- returns a new point that is closest to the original point that conforms to the grid snap
|
||||
* @param {Point} point The point to snap to grid
|
||||
*/
|
||||
WallReshapingTool.prototype.snapPointToGrid = function (point) {
|
||||
var diagram = this.diagram;
|
||||
var newx = diagram.model.modelData.gridSize * Math.round(point.x / diagram.model.modelData.gridSize);
|
||||
var newy = diagram.model.modelData.gridSize * Math.round(point.y / diagram.model.modelData.gridSize);
|
||||
var newPt = new go.Point(newx, newy);
|
||||
return newPt;
|
||||
}
|
||||
|
||||
/*
|
||||
* Reshapes the shape's geometry, updates model data
|
||||
* @param {Point} newPoint The point to move the reshaping wall's reshaping endpoint to
|
||||
*/
|
||||
WallReshapingTool.prototype.reshape = function (newPoint) {
|
||||
var diagram = this.diagram;
|
||||
var tool = this;
|
||||
var shape = this.adornedShape;
|
||||
var node = shape.part;
|
||||
|
||||
// if user holds SHIFT, make angle between startPoint / endPoint and the horizontal line a multiple of 45
|
||||
if (this.diagram.lastInput.shift) {
|
||||
|
||||
var sPt; // the stationary point -- the point at the handle that is not being adjusted
|
||||
if (tool.handle.name === 'sPt') sPt = node.data.endpoint;
|
||||
else sPt = node.data.startpoint;
|
||||
|
||||
var oldGridSize = diagram.model.modelData.gridSize;
|
||||
var gridSize = diagram.model.modelData.gridSize;
|
||||
//if gridSnapping is disabled, just set 'gridSize' var to 1 so it doesn't affect endPoint calculations
|
||||
if (!(this.diagram.toolManager.draggingTool.isGridSnapEnabled)) gridSize = 1;
|
||||
|
||||
// these are set in mouseMove's call to calcAngleAndLengthFromHandle()
|
||||
var angle = tool.angle;
|
||||
var length = tool.length;
|
||||
|
||||
// snap to 90 degrees
|
||||
if (angle > 67.5 && angle < 112.5) {
|
||||
var newy = sPt.y + length;
|
||||
newy = gridSize * Math.round(newy / gridSize);
|
||||
newPoint = new go.Point(sPt.x, newy);
|
||||
}
|
||||
// snap to 180 degrees
|
||||
if (angle > 112.5 && angle < 202.5) {
|
||||
var newx = sPt.x - length;
|
||||
newx = gridSize * Math.round(newx / gridSize);
|
||||
newPoint = new go.Point(newx, sPt.y);
|
||||
}
|
||||
// snap to 270 degrees
|
||||
if (angle > 247.5 && angle < 292.5) {
|
||||
var newy = sPt.y - length;
|
||||
newy = gridSize * Math.round(newy / gridSize);
|
||||
newPoint = new go.Point(sPt.x, newy);
|
||||
}
|
||||
// snap to 360 degrees
|
||||
if (angle > 337.5 || angle < 22.5) {
|
||||
var newx = sPt.x + length;
|
||||
newx = gridSize * Math.round(newx / gridSize);
|
||||
newPoint = new go.Point(newx, sPt.y);
|
||||
}
|
||||
// snap to 45 degrees
|
||||
if (angle > 22.5 && angle < 67.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = gridSize * Math.round(newx / gridSize) + sPt.x;
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = gridSize * Math.round(newy / gridSize) + sPt.y;
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 135 degrees
|
||||
if (angle > 112.5 && angle < 157.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x - (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = gridSize * Math.round(newy / gridSize) + sPt.y;
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 225 degrees
|
||||
if (angle > 202.5 && angle < 247.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x - (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = sPt.y - (gridSize * Math.round(newy / gridSize));
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
// snap to 315 degrees
|
||||
if (angle > 292.5 && angle < 337.5) {
|
||||
var newx = (Math.sin(.785) * length);
|
||||
newx = sPt.x + (gridSize * Math.round(newx / gridSize));
|
||||
var newy = (Math.cos(.785) * length);
|
||||
newy = sPt.y - (gridSize * Math.round(newy / gridSize));
|
||||
newPoint = new go.Point(newx, newy);
|
||||
}
|
||||
gridSize = oldGridSize; //set gridSize back to what it used to be in case gridSnap is enabled again
|
||||
}
|
||||
if (this.diagram.toolManager.draggingTool.isGridSnapEnabled) newPoint = this.snapPointToGrid(newPoint);
|
||||
else newPoint = new go.Point(newPoint.x, newPoint.y);
|
||||
|
||||
var type = this.handle.name;
|
||||
if (type === undefined) return;
|
||||
// set the appropriate point in the node's data to the newPoint value
|
||||
switch (type) {
|
||||
case 'sPt':
|
||||
reshapeWall(node, node.data.endpoint, node.data.startpoint, newPoint, diagram, tool);
|
||||
break;
|
||||
case 'ePt':
|
||||
reshapeWall(node, node.data.startpoint, node.data.endpoint, newPoint, diagram, tool);
|
||||
break;
|
||||
}
|
||||
this.updateAdornments(shape.part);
|
||||
this.showMatches();
|
||||
diagram.updateWallDimensions();
|
||||
}
|
||||
|
||||
/*Maintain position of all wallParts as best as possible when a wall is being reshaped
|
||||
* Position is relative to the distance a wallPart's location is from the stationaryPoint of the wall
|
||||
* This is called during WallReshapingTool's reshape function
|
||||
* @param {Group} wall The wall being reshaped
|
||||
* @param {Point} stationaryPoint The endpoint of the wall not being reshaped
|
||||
* @param {Point} movingPoint The endpoint of the wall being reshaped
|
||||
* @param {Point} newPoint The point that movingPoint is going to
|
||||
* @param {Diagram} diagram The diagram belonging WallReshapingTool belongs to
|
||||
* @param {WallReshapingTool} tool
|
||||
*/
|
||||
function reshapeWall(wall, stationaryPoint, movingPoint, newPoint, diagram, tool) {
|
||||
var wallParts = wall.memberParts;
|
||||
var arr = [];
|
||||
var oldAngle = wall.rotateObject.angle;
|
||||
wallParts.iterator.each(function (part) { arr.push(part); });
|
||||
// remember the distance each wall part's location was from the stationary point; store these in a Map
|
||||
var distancesMap = new go.Map(/*"string", "number"*/);
|
||||
var closestPart = null; var closestDistance = Number.MAX_VALUE;
|
||||
for (var i = 0; i < arr.length; i++) {
|
||||
var part = arr[i];
|
||||
var distanceToStationaryPt = Math.sqrt(part.location.distanceSquaredPoint(stationaryPoint));
|
||||
distancesMap.add(part.data.key, distanceToStationaryPt);
|
||||
// distanceToMovingPt is determined by whichever endpoint of the wallpart is closest to movingPoint
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
var distanceToMovingPt = Math.min(Math.sqrt(endpoints[0].distanceSquaredPoint(movingPoint)),
|
||||
Math.sqrt(endpoints[1].distanceSquaredPoint(movingPoint)));
|
||||
// find and store the closest wallPart to the movingPt
|
||||
if (distanceToMovingPt < closestDistance) {
|
||||
closestDistance = distanceToMovingPt;
|
||||
closestPart = part;
|
||||
}
|
||||
}
|
||||
// if the proposed newPoint would make it so the wall would reshape past closestPart, set newPoint to the edge point of closest part
|
||||
if (closestPart !== null) {
|
||||
var loc = closestPart.location;
|
||||
var partLength = closestPart.data.length;
|
||||
var angle = oldAngle;
|
||||
var point1 = new go.Point((loc.x + (partLength / 2)), loc.y);
|
||||
var point2 = new go.Point((loc.x - (partLength / 2)), loc.y);
|
||||
point1.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
point2.offset(-loc.x, -loc.y).rotate(angle).offset(loc.x, loc.y);
|
||||
var distance1 = Math.sqrt(stationaryPoint.distanceSquaredPoint(point1));
|
||||
var distance2 = Math.sqrt(stationaryPoint.distanceSquaredPoint(point2));
|
||||
|
||||
var minLength; var newLoc;
|
||||
if (distance1 > distance2) {
|
||||
minLength = distance1;
|
||||
newLoc = point1;
|
||||
} else {
|
||||
minLength = distance2;
|
||||
newLoc = point2;
|
||||
}
|
||||
|
||||
var testDistance = Math.sqrt(stationaryPoint.distanceSquaredPoint(newPoint));
|
||||
if (testDistance < minLength) newPoint = newLoc;
|
||||
}
|
||||
// reshape the wall
|
||||
if (movingPoint === wall.data.endpoint) diagram.model.setDataProperty(wall.data, "endpoint", newPoint);
|
||||
else diagram.model.setDataProperty(wall.data, "startpoint", newPoint);
|
||||
diagram.updateWall(wall);
|
||||
// calculate the new angle offset
|
||||
var newAngle = wall.rotateObject.angle;
|
||||
var angleOffset = newAngle - oldAngle;
|
||||
// for each wallPart, maintain relative distance from the stationaryPoint
|
||||
distancesMap.iterator.each(function (kvp) {
|
||||
var wallPart = diagram.findPartForKey(kvp.key);
|
||||
var distance = kvp.value;
|
||||
var wallLength = Math.sqrt(stationaryPoint.distanceSquaredPoint(movingPoint));
|
||||
var newLoc = new go.Point(stationaryPoint.x + ((distance / wallLength) * (movingPoint.x - stationaryPoint.x)),
|
||||
stationaryPoint.y + ((distance / wallLength) * (movingPoint.y - stationaryPoint.y)));
|
||||
wallPart.location = newLoc;
|
||||
wallPart.angle = (wallPart.angle + angleOffset) % 360;
|
||||
});
|
||||
}
|
||||
|
||||
// Show if the wall (at the adjustment handle being moved) lines up with other wall edges
|
||||
WallReshapingTool.prototype.showMatches = function () {
|
||||
//if (!(document.getElementById('wallGuidelinesCheckbox').checked)) return;
|
||||
var diagram = this.diagram;
|
||||
if (!diagram.model.modelData.preferences.showWallGuidelines) return;
|
||||
var tool = this;
|
||||
var wall = this.adornedShape.part;
|
||||
var comparePt;
|
||||
if (this.handle.name === 'sPt') comparePt = wall.data.startpoint;
|
||||
else comparePt = wall.data.endpoint;
|
||||
|
||||
// the wall attached to the handle being manipulated
|
||||
var hWall = this.adornedShape.part;
|
||||
|
||||
// delete any old guideline points (these are used to show guidelines, must be cleared before a new guideline can be shown)
|
||||
var glPoints = diagram.findNodesByExample({ category: 'GLPointNode' });
|
||||
diagram.removeParts(glPoints, true);
|
||||
|
||||
var walls = this.diagram.findNodesByExample({ category: 'WallGroup' });
|
||||
walls.iterator.each(function (w) {
|
||||
if (w.data.key != hWall.data.key) {
|
||||
var shape = w.findObject('SHAPE');
|
||||
var geo = shape.geometry;
|
||||
|
||||
var pt1 = w.data.startpoint;
|
||||
var pt2 = w.data.endpoint;
|
||||
|
||||
tool.checkPtLinedUp(pt1, comparePt.x, pt1.x, comparePt);
|
||||
tool.checkPtLinedUp(pt1, comparePt.y, pt1.y, comparePt);
|
||||
tool.checkPtLinedUp(pt2, comparePt.x, pt2.x, comparePt);
|
||||
tool.checkPtLinedUp(pt2, comparePt.y, pt2.y, comparePt);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* Static function -- checks if there exists a horiontal or vertical line (decided by 'coord' parameter) between pt and compare pt
|
||||
* if so, draws a link between the two, letting the user know the wall they're reshaping lines up with another's edge
|
||||
* @param {Point} pt
|
||||
* @param {Number} comparePtCoord
|
||||
* @param {Number} ptCoord
|
||||
* @param {Point} comparePt
|
||||
*/
|
||||
WallReshapingTool.prototype.checkPtLinedUp = function (pt, comparePtCoord, ptCoord, comparePt) {
|
||||
function makeGuideLinePoint() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Node, "Spot", { locationSpot: go.Spot.TopLeft, locationObjectName: "SHAPE", desiredSize: new go.Size(1, 1), },
|
||||
new go.Binding("location", "loc", go.Point.parse).makeTwoWay(go.Point.stringify),
|
||||
$(go.Shape, { stroke: null, strokeWidth: 1, name: "SHAPE", fill: "black", })
|
||||
);
|
||||
}
|
||||
|
||||
function makeGuideLineLink() {
|
||||
var $ = go.GraphObject.make;
|
||||
return $(go.Link,
|
||||
$(go.Shape, { stroke: "black", strokeWidth: 2, name: 'SHAPE', },
|
||||
new go.Binding("strokeWidth", "width"),
|
||||
new go.Binding('stroke', 'stroke')
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var diagram = this.diagram;
|
||||
var errorMargin = Math.abs(comparePtCoord - ptCoord);
|
||||
if (errorMargin < 2) {
|
||||
|
||||
var data = { category: "GLPointNode", loc: go.Point.stringify(pt) };
|
||||
var data2 = { key: 'movingPt', category: "GLPointNode", loc: go.Point.stringify(comparePt) };
|
||||
var data3 = { key: 'guideline', category: 'guideLine', from: 'movingPt', to: data.key, stroke: 'blue' };
|
||||
var GLPoint1 = makeGuideLinePoint();
|
||||
var GLPoint2 = makeGuideLinePoint();
|
||||
var GLLink = makeGuideLineLink();
|
||||
diagram.add(GLPoint1);
|
||||
diagram.add(GLPoint2);
|
||||
diagram.add(GLLink);
|
||||
|
||||
GLPoint1.data = data;
|
||||
GLPoint2.data = data2;
|
||||
GLLink.data = data3;
|
||||
GLLink.fromNode = GLPoint1;
|
||||
GLLink.toNode = GLPoint2;
|
||||
}
|
||||
}
|
||||
+262
@@ -0,0 +1,262 @@
|
||||
/********************************************** MAIN CONTENT STYLING ***********************************************/
|
||||
body {font-family: Arial; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none;}
|
||||
canvas:focus {outline:0;} /*don't outline palette and diagram when they are focused*/
|
||||
p, h1,h2,h3,h4,h5,h6{cursor: default}
|
||||
/*Style placeholder text to be black on all browsers*/
|
||||
::-webkit-input-placeholder { color: black; }
|
||||
::-moz-placeholder { color: black; }
|
||||
:-ms-input-placeholder { color: black; }
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input{
|
||||
padding: 0px;
|
||||
border: 1px solid gray;
|
||||
}
|
||||
.unitsBox{ /*A special input that displays units*/
|
||||
text-align: right;
|
||||
width: 20px;
|
||||
border: 1px solid gray;
|
||||
border-left: 0px;
|
||||
}
|
||||
|
||||
#notesTextarea {
|
||||
resize: none;
|
||||
font-family: Arial;
|
||||
}
|
||||
|
||||
label{ font-size: 9pt; color: #757575;}
|
||||
|
||||
.paletteLabel {
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
}
|
||||
#furnitureSearchBar{ width: 100%; border: none; }
|
||||
#furnitureSearchBar::-webkit-input-placeholder { color: gray; }
|
||||
#furnitureSearchBar::-moz-placeholder { color: gray; }
|
||||
#furnitureSearchBar:-ms-input-placeholder { color: gray; }
|
||||
#furniturePaletteDiv {width: inherit; height: 300px; background: #e2e2e2;}
|
||||
#wallPartsPaletteDiv {width: inherit; height: 150px; background: #e2e2e2;}
|
||||
|
||||
#myPaletteWindow{
|
||||
height: inherit;
|
||||
top:12%;
|
||||
left:.5%;
|
||||
}
|
||||
|
||||
#palettes {
|
||||
height: 100%;
|
||||
width: 300px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#myFloorplanDiv {
|
||||
width: 100%;
|
||||
background-color: #DAE4E4;
|
||||
border: 1px solid gray;
|
||||
height: 85vh;
|
||||
}
|
||||
|
||||
#currentFile {
|
||||
background-color: #4b545f;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#diagramHelpDiv {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border: 1px solid gray;
|
||||
background-color: #4b545f;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/****************************************************************** GENERAL DRAGGABLE WINDOWS STYLING ********************************************************/
|
||||
|
||||
.draggable {
|
||||
border: 1px solid gray;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
height: 200px;
|
||||
z-index: 10;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#optionsWindow {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.windowButtons{
|
||||
float: right;
|
||||
border: none;
|
||||
font: bold 12px sans-serif;
|
||||
}
|
||||
|
||||
.handle {
|
||||
background-color: #4b545f;
|
||||
text-align: center;
|
||||
font: bold 12px sans-serif;
|
||||
color: white;
|
||||
cursor: move;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mySavedFiles {width:75%;}
|
||||
|
||||
/*Selection Info Window specific styling*/
|
||||
|
||||
#selectionInfoTextDiv p {margin: 0; padding: 0;} /*used only when no node is selected*/
|
||||
#selectionInfoTextDiv #name {margin: 3px;}
|
||||
#selectionInfoTextDiv .nameNotesInput {
|
||||
width: 80%;
|
||||
}
|
||||
#selectionInfoTextDiv .dimensionsInput {width: 85%; border-right: 0px;}
|
||||
.data {font-size: 10pt;}
|
||||
.clickable {cursor: pointer;}
|
||||
.selectedKey {color: dodgerblue;}
|
||||
|
||||
/*-- set border box on all elements inside the grid*/
|
||||
.grid-container * {box-sizing: border-box;}
|
||||
|
||||
.row:before, .row::after {
|
||||
content: "";
|
||||
display: table;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
[class*='col-'] {
|
||||
float: left;
|
||||
min-height: 1px;
|
||||
width: 16.66%;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.col-1 {width: 100%;}
|
||||
.col-2 {width: 50%;}
|
||||
.col-3 {width: 33.33%;}
|
||||
.col-4 {width: 25%;}
|
||||
.col-5 {width: 20%;}
|
||||
.col-6 {width: 16.66%;}
|
||||
/************************************************************* NAV BAR STYLING *************************************************/
|
||||
|
||||
nav {background: linear-gradient(#efefef ,#bbbbbb );}
|
||||
|
||||
nav ul { /*file menus and tool menus*/
|
||||
list-style: none;
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: 0; padding: 0;
|
||||
}
|
||||
|
||||
nav ul li { /*menu tabs*/
|
||||
float: left;
|
||||
transition-property: background;
|
||||
transition-duration: 0.3s;
|
||||
}
|
||||
nav ul li:hover {
|
||||
transition-delay: 0.1s, 0.1s;
|
||||
background: #4f5964;
|
||||
}
|
||||
|
||||
nav ul li a { /*menu tabs text*/
|
||||
display: block;
|
||||
padding-left: 25px; padding-right: 25px; padding-top: 2px;
|
||||
color: #757575; text-decoration: none;
|
||||
transition-property: background, color;
|
||||
transition-duration: 0.4s, 0.4s;
|
||||
}
|
||||
nav ul li:hover a {color: #fff;}
|
||||
|
||||
nav ul ul { /*drop down menus, general*/
|
||||
display: block;
|
||||
z-index: 20; /*in front of everything, even .draggables*/
|
||||
visibility: hidden; opacity: 0;
|
||||
transition-property: opacity;
|
||||
transition-duration: 0.3s;
|
||||
background: #5f6975; padding: 0;
|
||||
position: absolute; top: 100%;
|
||||
/* width: 220%; /*band aid fix for menu width -- TODO*/
|
||||
}
|
||||
nav ul li:hover > ul { /*drop down menus, visible*/
|
||||
visibility: visible; opacity: 1;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul li { /*drop down menu items*/
|
||||
border-top: 1px solid #6b727c;
|
||||
border-bottom: 1px solid #575f6a;
|
||||
position: relative; float: none;
|
||||
}
|
||||
nav ul ul li a { /*drown down menu items text*/
|
||||
padding: 5px;
|
||||
transition-property: background;
|
||||
transition-duration: 0.1s;
|
||||
}
|
||||
nav ul ul li a:hover {
|
||||
background: #4b545f;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
nav ul ul ul { /*drop down submenu (units menu)*/
|
||||
left: 100%; top:0; width: 100px;
|
||||
}
|
||||
|
||||
nav p { /* grid input area, wall width area, and 'Tools' all wrapped in <p> tag*/
|
||||
float: left;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 3px;
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
color: #4b545f;
|
||||
}
|
||||
nav p.shortcut {
|
||||
font-size: 9pt;
|
||||
float: right;
|
||||
color: #efefef;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scaleItems{ /*special styling for the 'Scale' item of the 'View' menu*/
|
||||
float: left;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
width: 12.5%;
|
||||
}
|
||||
#scaleDisplay{width: 75%;}
|
||||
|
||||
#wallThicknessInput, #gridSizeInput {
|
||||
width: 50px;
|
||||
float: left;
|
||||
border-right: 0px;
|
||||
}
|
||||
#gridSizeInput { margin-left: 10%; }
|
||||
#wallThicknesshUnitsInput, #gridSizeUnitsInput{float: left;}
|
||||
|
||||
#wallThicknessInputLabel{ float: left; }
|
||||
#setGridButton { padding: 0; }
|
||||
|
||||
|
||||
/* Scaling for small screens, TODO*/
|
||||
@media screen and (max-device-width: 480px) {
|
||||
nav ul li ul li{ font-size: 9pt; }
|
||||
}
|
||||
|
||||
/*Icons*/
|
||||
|
||||
#wallBuildingButton {background: url(icons/wallBuildingTool.png);}
|
||||
#draggingButton {background: url(icons/selectionTool.png);}
|
||||
#wallWidthBox { visibility: hidden; display: none;} /*box for setting wall width; invisible when Wall Tool not active*/
|
||||
|
||||
/* jQuery UI specific stylings */
|
||||
|
||||
.ui-accordion .ui-accordion-content, .ui-accordion .ui-accordion-icons { padding: 0px; }
|
||||
.ui-widget * { outline: none; }
|
||||
.ui-widget-content { border: none; }
|
||||
+279
@@ -0,0 +1,279 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta charset="utf-8" />
|
||||
<title>Floor Planner</title>
|
||||
<!-- Copyright 1998-2020 by Northwoods Software Corporation -->
|
||||
<script src="../../release/go.js"></script>
|
||||
<script src="FloorPlanner-WallBuildingTool.js"></script>
|
||||
<script src="FloorPlanner-WallReshapingTool.js"></script>
|
||||
<script src="FloorPlanner-Templates-General.js"></script>
|
||||
<script src="FloorPlanner-Templates-Furniture.js"></script>
|
||||
<script src="FloorPlanner-Templates-Walls.js"></script>
|
||||
<script src="Floorplan.js"></script>
|
||||
<script src="FloorplanFilesystem.js"></script>
|
||||
<script src="FloorplanUI.js"></script>
|
||||
<script src="Floorplanner-Constants.js"></script>
|
||||
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/jquery-ui.min.js"></script>
|
||||
|
||||
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.11.3/themes/smoothness/jquery-ui.css" />
|
||||
<link rel="stylesheet" type="text/css" href="FloorPlanner.css" />
|
||||
</head>
|
||||
<body id="body" onload="init()">
|
||||
<div id="currentFile">(Unsaved File)</div>
|
||||
<!-- File menus-->
|
||||
<!-- The Nav Bar / Windows are specific to a certain floorplan and its classes -- unlike most of the Floorplanner code, which is generic -->
|
||||
<nav>
|
||||
<ul id="fileMenus">
|
||||
<li>
|
||||
<a href="#">File</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="filesystem.newFloorplan()">New <p class="shortcut">(Ctrl + D)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.showOpenWindow()">Open... <p class="shortcut">(Ctrl + O)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.saveFloorplan()">Save <p class="shortcut">(Ctrl + S)</p></a></li>
|
||||
<li><a href="#" onclick="filesystem.saveFloorplanAs()">Save As...</a></li>
|
||||
<li><a href="#" onclick="filesystem.showRemoveWindow()">Remove... <p class="shortcut">(Ctrl + R)</p></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">Edit</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.undo()">Undo <p class="shortcut">(Ctrl + Z)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.redo()">Redo <p class="shortcut">(Ctrl + Y)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.copySelection()">Copy <p class="shortcut">(Ctrl + C)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.cutSelection()">Cut <p class="shortcut">(Ctrl + X)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.pasteSelection()">Paste <p class="shortcut">(Ctrl + V)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.deleteSelection()">Delete <p class="shortcut">(Del)</p></a></li>
|
||||
<li><a href="#" onclick="myFloorplan.commandHandler.selectAll()">Select All <p class="shortcut">(Ctrl + A)</p></a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#">View</a>
|
||||
<ul>
|
||||
<li><a href="#" onclick="ui.hideShow('diagramHelpDiv')" id="diagramHelpDivButton">Hide Diagram Help <p class="shortcut"> (Ctrl + H)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('selectionInfoWindow')" id="selectionInfoWindowButton">Show Selection Help <p class="shortcut"> (Ctrl + I)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('myPaletteWindow')" id="myPaletteWindowButton">Hide Palettes <p class="shortcut"> (Ctrl + P)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('myOverviewWindow')" id="myOverviewWindowButton">Show Overview <p class="shortcut"> (Ctrl + E)</p></a></li>
|
||||
<li><a href="#" onclick="ui.hideShow('statisticsWindow')" id="statisticsWindowButton">Show Statistics <p class="shortcut"> (Ctrl + G)</p></a></li>
|
||||
<li>
|
||||
<a href="#" id="optionsWindowButton" onclick="ui.hideShow('optionsWindow')">Show Options <p class="shortcut"> (Ctrl + B)</p> </a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="scaleItems" onclick="ui.adjustScale('-')">-</a>
|
||||
<a href="#" class="scaleItems" id="scaleDisplay">Scale: 100%</a>
|
||||
<a href="#" class="scaleItems" onclick="ui.adjustScale('+')">+</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><button class="setBehavior toolIcons" id="wallBuildingButton" title="Wall Building Tool (Ctrl + 1)" onclick="ui.setBehavior('wallBuilding')"> </button></li>
|
||||
<li><button class="setBehavior toolIcons" id="draggingButton" title="Select/Move Tool (Ctrl + 2)" onclick="ui.setBehavior('dragging')"> </button></li>
|
||||
<p id="wallThicknessBox">
|
||||
<label for="wallThicknessInput" id="wallThicknessInputLabel">Wall Thickness:</label>
|
||||
<input id="wallThicknessInput" class="unitsInput" placeholder="width" />
|
||||
<input id="wallThicknessUnitsInput" class="unitsBox" value="cm" disabled="disabled" />
|
||||
</p>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<!-- Floorplan / Help bar -->
|
||||
<div id="myFloorplanDiv"></div>
|
||||
<div id="diagramHelpDiv" style="visibility: visible">
|
||||
<div id="diagramHelpTextDiv">
|
||||
<p>Drag a node to the Diagram or select the Wall Drawing Tool (Ctrl + 1) to begin</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating windows-->
|
||||
<div id="myPaletteWindow" style="visibility: visible" class="draggable ui-draggable">
|
||||
<div id="myPaletteWindowHandle" class="handle ui-draggable-handle">Palettes<button id="myPaletteClose" class="windowButtons clickable" onclick="ui.hideShow('myPaletteWindow')">X</button></div>
|
||||
<div id="palettes">
|
||||
<!-- jQuery accordion -->
|
||||
<h3 class="paletteLabel">Furniture</h3>
|
||||
<div>
|
||||
<input id="furnitureSearchBar" placeholder="Search Furniture" oninput="ui.searchFurniture()" />
|
||||
<div id="furniturePaletteDiv" class="paletteClass"></div>
|
||||
</div>
|
||||
<h3 class="paletteLabel">Wall Parts</h3>
|
||||
<div id="wallPartsPaletteDiv" class="paletteClass"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="openDocument" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="openDocumentHandle" class="handle ui-draggable-handle">Open File<button id="openDocumentClose" class="windowButtons clickable" onclick="ui.hideShow('openDocument')">X</button></div>
|
||||
<div id="openText" class="elementText">Choose file to open...</div>
|
||||
<select id="filesToOpen" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="openBtn" class="elementBtn" type="button" onclick="filesystem.loadFloorplan()">Open</button>
|
||||
</div>
|
||||
|
||||
<div id="removeDocument" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="removeDocumentHandle" class="handle">Delete File <button id="removeDocumentClose" class="windowButtons clickable" onclick="ui.hideShow('removeDocument')">X</button></div>
|
||||
<div id="removeText" class="elementText">Choose file to remove...</div>
|
||||
<select id="filesToRemove" class="mySavedFiles"></select>
|
||||
<br />
|
||||
<button id="removeBtn" class="elementBtn" type="button" onclick="filesystem.removeFloorplan()" style="margin-left:70px">Remove</button>
|
||||
</div>
|
||||
|
||||
<div id="myOverviewWindow" style="visibility: hidden;" class="draggable ui-draggable">
|
||||
<div id="myOverviewWindowHandle" class="handle ui-draggable-handle">Overview<button id="myOverviewClose" title="Close" class="windowButtons clickable" onclick="ui.hideShow('myOverviewWindow')">X</button></div>
|
||||
<div id="myOverviewDiv" style="height:187px; width: 300px;"></div>
|
||||
</div>
|
||||
|
||||
<div id="selectionInfoWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="selectionInfoWindowHandle" class="handle ui-draggable-handle">Selection Info <button id="selectionInfoClose" class="windowButtons clickable" onclick="ui.hideShow('selectionInfoWindow')">X</button></div>
|
||||
<div id="selectionInfoTextDiv" class="grid-container">Nothing selected</div>
|
||||
</div>
|
||||
|
||||
<div id="optionsWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="optionsWindowHandle" class="handle ui-draggable-handle">Options <button id="optionsWindowClose" class="windowButtons clickable" onclick="ui.hideShow('optionsWindow')">X</button></div>
|
||||
Units
|
||||
<div id="unitsRow" class="row data">
|
||||
<form id="unitsForm" onchange="ui.changeUnits()">
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="centimeters" checked />cm
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="meters" /> m
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="inches" />in
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<input type="radio" name="units" id="feet" />ft
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
Grid
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-2">
|
||||
<input id="gridSizeInput" placeholder="grid size" class="unitsInput" onchange="ui.changeGridSize()" />
|
||||
<input id="gridSizeUnitsInput" class="unitsBox" value="cm" disabled />
|
||||
<!--<button id="setGridButton" onclick="ui.changeGridSize()">Set Grid</button>-->
|
||||
</div>
|
||||
<div class="col-2">
|
||||
<input type="checkbox" id="showGridCheckbox" onchange="ui.checkboxChanged('showGridCheckbox', myFloorplan)" checked />Show Grid
|
||||
</div>
|
||||
</div>
|
||||
<div id="gridRow" class="row">
|
||||
<div class="col-1">
|
||||
<label for="unitsConversionFactorInput">Units/1px (at scale 100%)</label>
|
||||
<input id="unitsConversionFactorInput" placeholder="2" onchange="ui.changeUnitsConversionFactor()" />
|
||||
</div>
|
||||
</div>
|
||||
Preferences
|
||||
<div id="miscRow" class="row data">
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="gridSnapCheckbox" onchange="ui.checkboxChanged('gridSnapCheckbox', myFloorplan)" checked />Grid Snap
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallGuidelinesCheckbox" onchange="ui.checkboxChanged('wallGuidelinesCheckbox', myFloorplan)" checked /> Show Wall Guidelines
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallLengthsCheckbox" onchange="ui.checkboxChanged('wallLengthsCheckbox', myFloorplan)" checked /> Show Wall Lengths
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="wallAnglesCheckbox" onchange="ui.checkboxChanged('wallAnglesCheckbox', myFloorplan)" checked /> Show Wall Angles
|
||||
</div>
|
||||
<div class="col-1">
|
||||
<input type="checkbox" id="smallWallAnglesCheckbox" onchange="ui.checkboxChanged('smallWallAnglesCheckbox', myFloorplan)" unchecked /> Show Only Small Wall Angles
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statisticsWindow" style="visibility: hidden" class="draggable ui-draggable">
|
||||
<div id="statisticsWindowHandle" class="handle ui-draggable-handle">Floor Plan Statistics <button id="statisticsWindowClose" class="windowButtons clickable" onclick="ui.hideShow('statisticsWindow')">X</button></div>
|
||||
Stats
|
||||
<div id="statisticsWindowTextDiv" class="grid-container"></div>
|
||||
</div>
|
||||
|
||||
<p>This Floorplanner samples makes use of multiple classes to allow for users to build, edit, save, and load feature-rich Floorplans. To start, build walls with the Wall Building Tool (Ctrl + 1), or drag furniture from Palettes onto the Floorplan area. The help at the bottom of the Floorplan area is context-specific and should aid in providing tips on how to better use this software.</p>
|
||||
<p>This sample uses the following files.</p>
|
||||
<ul>
|
||||
<li><a href="Floorplan.js">Floorplan.js</a> - A special kind of <a href="https://gojs.net/latest/api/symbols/Diagram.html">Diagram</a> with rules and listeners that help with floorplanning</li>
|
||||
<li><a href="FloorplanFilesystem.js">FloorplanFilesystem.js</a> - A class to handle saving and loading floorplans (through localstorage). Linked to a Floorplan instance</li>
|
||||
<li><a href="Floorplanner-WallBuildingTool.js">WallBuildingTool.js</a> - For constructing new walls</li>
|
||||
<li><a href="Floorplanner-WallReshapingTool.js">WallReshapingTool.js</a> - For reshaping walls from their endpoints</li>
|
||||
<li><a href="FloorplanUI.js">FloorplanUI.js</a> - A class to handle GUI interaction as events on the Floorplan take place. Linked to a Floorplan instance</li>
|
||||
</ul>
|
||||
<p> In addition, three files are used to store Flooplanner-specific <a href="https://gojs.net/latest/api/symbols/GraphObject.html">Graph Object</a> templates.</p>
|
||||
<ul>
|
||||
<li><a href="Floorplanner-Templates-General.js">Floorplanner-Templates-General.js</a> - Contains templates for Context Menu, Diagram, Default Group, AngleNode, DimensionLink, PointNode, all all their dependencies</li>
|
||||
<li><a href="Floorplanner-Templates-Furniture.js">Floorplanner-Templates-Furniture.js</a> - Contains templates for Default Node (Furniture), MultiPurpose Node, and all their dependencies</li>
|
||||
<li><a href="Floorplanner-Templates-Walls.js">Floorplanner-Templates-Walls.js</a> - Contains templates for Wall Group, Palette Wall Node, Window Node, Door Node, and all their dependencies</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
|
||||
// enables draggable windows (jQuery), defining their handles and behavior (most recently dragged window stacks over other windows)
|
||||
$(function () {
|
||||
$("#palettes").accordion({
|
||||
activate: function (event, ui) {
|
||||
furniturePalette.requestUpdate();
|
||||
wallPartsPalette.requestUpdate();
|
||||
}
|
||||
});
|
||||
$("#openDocument").draggable({ handle: "#openDocumentHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$('#optionsWindow').draggable({ handle: "#optionsWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#removeDocument").draggable({ handle: "#removeDocumentHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myOverviewWindow").draggable({ handle: "#myOverviewWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$('#statisticsWindow').draggable({ handle: "#statisticsWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#selectionInfoWindow").draggable({ handle: "#selectionInfoWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myPaletteWindow").draggable({ handle: "#myPaletteWindowHandle", stack: ".draggable", containment: 'window', scroll: false });
|
||||
$("#myPaletteWindow").resize(function () {
|
||||
furniturePalette.requestUpdate();
|
||||
wallPartsPalette.requestUpdate();
|
||||
});
|
||||
});
|
||||
|
||||
function init() {
|
||||
// Floorplan
|
||||
myFloorplan = new Floorplan("myFloorplanDiv");
|
||||
|
||||
// 'FILESYSTEM_UI_STATE', 'GUI_STATE' defined in Floorplanner-Constants.js
|
||||
filesystem = new FloorplanFilesystem(myFloorplan, FILESYSTEM_UI_STATE);
|
||||
ui = new FloorplanUI(myFloorplan, "ui", "myFloorplan", GUI_STATE);
|
||||
|
||||
// Overview
|
||||
var $ = go.GraphObject.make;
|
||||
myOverview = $(go.Overview, "myOverviewDiv", { observed: myFloorplan, maxScale: 0.5 });
|
||||
|
||||
furniturePalette = $(go.Palette, "furniturePaletteDiv");
|
||||
furniturePalette.nodeTemplateMap = myFloorplan.nodeTemplateMap;
|
||||
furniturePalette.model = new go.GraphLinksModel(FURNITURE_NODE_DATA_ARRAY);
|
||||
wallPartsPalette = $(go.Palette, "wallPartsPaletteDiv");
|
||||
wallPartsPalette.nodeTemplateMap = myFloorplan.nodeTemplateMap;
|
||||
wallPartsPalette.model = new go.GraphLinksModel(WALLPARTS_NODE_DATA_ARRAY);
|
||||
|
||||
// enable hotkeys
|
||||
var body = document.getElementById('body');
|
||||
body.addEventListener("keydown", function (e) {
|
||||
var keynum = e.which;
|
||||
if (e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
switch (keynum) {
|
||||
case 83: filesystem.saveFloorplan(); break; // ctrl + s
|
||||
case 79: filesystem.showOpenWindow(); break; // ctrl + o
|
||||
case 68: e.preventDefault(); filesystem.newFloorplan(); break; // ctrl + d
|
||||
case 82: filesystem.showRemoveWindow(); break; // ctrl + r
|
||||
case 49: ui.setBehavior('wallBuilding', myFloorplan); break; // ctrl + 1
|
||||
case 50: ui.setBehavior('dragging', myFloorplan); break; // ctrl + 2
|
||||
case 72: ui.hideShow('diagramHelpDiv'); break; // ctrl + h
|
||||
case 73: ui.hideShow('selectionInfoWindow'); break; // ctrl + i
|
||||
case 80: ui.hideShow('myPaletteWindow'); break; // ctrl + p
|
||||
case 69: ui.hideShow('myOverviewWindow'); break; // ctrl + e
|
||||
case 66: ui.hideShow('optionsWindow'); break; // ctrl + b
|
||||
case 71: ui.hideShow('statisticsWindow'); break; // ctrl + g
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// default model data stored in Floorplanner-Constants.js
|
||||
myFloorplan.floorplanFilesystem.loadFloorplanFromModel(DEFAULT_MODEL_DATA);
|
||||
ui.setBehavior("dragging");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
+813
@@ -0,0 +1,813 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplan Class
|
||||
* A Floorplan is a Diagram with special rules
|
||||
* Dependencies: Floorplanner-Templates-General.js, Floorplanner-Templates-Furniture.js, Floorplanner-Templates-Walls.js
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan Constructor
|
||||
* @param {HTMLDivElement|string} div A reference to a div or its ID as a string
|
||||
*/
|
||||
function Floorplan(div) {
|
||||
|
||||
/*
|
||||
* Floor Plan Setup:
|
||||
* Initialize Floor Plan, Floor Plan Listeners, Floor Plan Overview
|
||||
*/
|
||||
|
||||
go.Diagram.call(this, div);
|
||||
// By default there is no filesystem / UI control for a floorplan, though they can be added
|
||||
this._floorplanFilesystem = null;
|
||||
this._floorplanUI = null;
|
||||
|
||||
// When a FloorplanPalette instance is made, it is automatically added to a Floorplan's "palettes" property
|
||||
this._palettes = [];
|
||||
|
||||
// Point Nodes, Dimension Links, Angle Nodes on the Floorplan (never in model data)
|
||||
this._pointNodes = new go.Set(/*go.Node*/);
|
||||
this._dimensionLinks = new go.Set(/*go.Link*/);
|
||||
this._angleNodes = new go.Set(/*go.Node*/);
|
||||
|
||||
var $ = go.GraphObject.make;
|
||||
|
||||
this.allowLink = false;
|
||||
this.undoManager.isEnabled = true;
|
||||
this.layout.isOngoing = false;
|
||||
this.model = $(go.GraphLinksModel, {
|
||||
modelData: {
|
||||
"units": "centimeters",
|
||||
"unitsAbbreviation": "cm",
|
||||
"unitsConversionFactor": 2,
|
||||
"gridSize": 10,
|
||||
"wallThickness": 5,
|
||||
"preferences": {
|
||||
showWallGuidelines: true,
|
||||
showWallLengths: true,
|
||||
showWallAngles: true,
|
||||
showOnlySmallWallAngles: true,
|
||||
showGrid: true,
|
||||
gridSnap: true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.grid = $(go.Panel, "Grid",
|
||||
{ gridCellSize: new go.Size(this.model.modelData.gridSize, this.model.modelData.gridSize), visible: true },
|
||||
$(go.Shape, "LineH", { stroke: "lightgray" }),
|
||||
$(go.Shape, "LineV", { stroke: "lightgray" }));
|
||||
this.contextMenu = makeContextMenu();
|
||||
this.commandHandler.canGroupSelection = true;
|
||||
this.commandHandler.canUngroupSelection = true;
|
||||
this.commandHandler.archetypeGroupData = { isGroup: true };
|
||||
|
||||
// When floorplan model is changed, update stats in Statistics Window
|
||||
this.addModelChangedListener(function (e) {
|
||||
if (e.isTransactionFinished) {
|
||||
// find floorplan changed
|
||||
var floorplan = null;
|
||||
if (e.object !== null) {
|
||||
e.object.changes.each(function (change) {
|
||||
if (change.diagram instanceof Floorplan) floorplan = change.diagram;
|
||||
});
|
||||
}
|
||||
if (floorplan) {
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// When floorplan is modified, change document title to include a *
|
||||
this.addDiagramListener("Modified", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
if (floorplan.floorplanFilesystem) {
|
||||
var currentFile = document.getElementById(floorplan.floorplanFilesystem.state.currentFileId);
|
||||
if (currentFile) {
|
||||
var idx = currentFile.textContent.indexOf("*");
|
||||
if (floorplan.isModified) {
|
||||
if (idx < 0) currentFile.textContent = currentFile.textContent + "*";
|
||||
}
|
||||
else {
|
||||
if (idx >= 0) currentFile.textContent = currentFile.textContent.substr(0, idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if a wall is copied, update its geometry
|
||||
this.addDiagramListener("SelectionCopied", function (e) {
|
||||
e.diagram.selection.iterator.each(function(part){
|
||||
if (part.category == "WallGroup") {
|
||||
e.diagram.updateWall(part);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// If floorplan scale has been changed update the 'Scale' item in the View menu
|
||||
this.addDiagramListener("ViewportBoundsChanged", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
if (floorplan.floorplanUI) {
|
||||
var scaleEl = document.getElementById(floorplan.floorplanUI.state.scaleDisplayId);
|
||||
if (scaleEl) scaleEl.innerHTML = "Scale: " + (e.diagram.scale * 100).toFixed(2) + "%";
|
||||
}
|
||||
});
|
||||
|
||||
// If a node has been dropped onto the Floorplan from a Palette...
|
||||
this.addDiagramListener("ExternalObjectsDropped", function (e) {
|
||||
var garbage = [];
|
||||
var paletteWallNodes = [];
|
||||
var otherNodes = [];
|
||||
e.diagram.selection.iterator.each(function(node){
|
||||
// Event 1: handle a drag / drop of a wall node from the Palette (as opposed to wall construction via WallBuildingTool)
|
||||
if (node.category === "PaletteWallNode") {
|
||||
paletteWallNodes.push(node);
|
||||
}
|
||||
if (e.diagram.floorplanUI) {
|
||||
otherNodes.push(node);
|
||||
}
|
||||
});
|
||||
for (var i in paletteWallNodes) {
|
||||
var node = paletteWallNodes[i];
|
||||
var paletteWallNode = node;
|
||||
var endpoints = getWallPartEndpoints(paletteWallNode);
|
||||
var data = { key: "wall", category: "WallGroup", caption: "Wall", startpoint: endpoints[0], endpoint: endpoints[1], thickness: parseFloat(e.diagram.model.modelData.wallThickness), isGroup: true, notes: "" };
|
||||
e.diagram.model.addNodeData(data);
|
||||
var wall = e.diagram.findPartForKey(data.key);
|
||||
e.diagram.updateWall(wall);
|
||||
garbage.push(paletteWallNode);
|
||||
}
|
||||
for (var i in otherNodes) {
|
||||
var node = otherNodes[i];
|
||||
var floorplanUI = e.diagram.floorplanUI;
|
||||
// Event 2: Update the text of the Diagram Helper
|
||||
if (node.category === "WindowNode" || node.category === "DoorNode") floorplanUI.setDiagramHelper("Drag part so the cursor is over a wall to add this part to a wall");
|
||||
else floorplanUI.setDiagramHelper("Drag, resize, or rotate your selection (hold SHIFT for no grid-snapping)");
|
||||
// Event 3: If the select tool is not active, make it active
|
||||
if (e.diagram.toolManager.mouseDownTools.elt(0).isEnabled) floorplanUI.setBehavior('dragging', e.diagram);
|
||||
}
|
||||
for (var i in garbage) {
|
||||
e.diagram.remove(garbage[i]);
|
||||
}
|
||||
});
|
||||
|
||||
// When a wall is copied / pasted, update the wall geometry, angle, etc
|
||||
this.addDiagramListener("ClipboardPasted", function (e) {
|
||||
e.diagram.selection.iterator.each(function (node) { if (node.category === "WallGroup") e.diagram.updateWall(node); });
|
||||
});
|
||||
|
||||
// Display different help depending on selection context
|
||||
this.addDiagramListener("ChangedSelection", function (e) {
|
||||
var floorplan = e.diagram;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("remove dimension links and angle nodes");
|
||||
floorplan.pointNodes.iterator.each(function (node) { e.diagram.remove(node) });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { e.diagram.remove(link) });
|
||||
|
||||
var missedDimensionLinks = []; // used only in undo situations
|
||||
floorplan.links.iterator.each(function (link) { if (link.data.category == "DimensionLink") missedDimensionLinks.push(link); });
|
||||
for (var i = 0; i < missedDimensionLinks.length; i++) {
|
||||
e.diagram.remove(missedDimensionLinks[i]);
|
||||
}
|
||||
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.angleNodes.iterator.each(function (node) { e.diagram.remove(node); });
|
||||
floorplan.angleNodes.clear();
|
||||
|
||||
floorplan.commitTransaction("remove dimension links and angle nodes");
|
||||
floorplan.skipsUndoManager = false;
|
||||
floorplan.updateWallDimensions();
|
||||
floorplan.updateWallAngles();
|
||||
if (floorplan.floorplanUI) {
|
||||
var floorplanUI = floorplan.floorplanUI;
|
||||
var selection = floorplan.selection;
|
||||
var node = floorplan.selection.first(); // only used if selection.count === 1
|
||||
|
||||
if (selection.count === 0) floorplan.floorplanUI.setSelectionInfo('Nothing selected');
|
||||
else if (selection.count === 1) floorplan.floorplanUI.setSelectionInfo(floorplan.selection.first());
|
||||
else floorplan.floorplanUI.setSelectionInfo('Selection: ');
|
||||
|
||||
if (selection.count === 0) floorplanUI.setDiagramHelper("Click to select a part, drag one from a Palette, or draw a wall with the Wall Tool (Ctr + 1)");
|
||||
else if (selection.count > 1) {
|
||||
var ungroupable = false;
|
||||
selection.iterator.each(function (node) { if (node.category === "WindowNode" || node.category === "DoorNode" || node.category === "WallGroup") ungroupable = true; });
|
||||
if (!ungroupable) floorplanUI.setDiagramHelper("You may group your selection with the context menu (Right Click anywhere)");
|
||||
}
|
||||
else if (node.category === "WallGroup") floorplanUI.setDiagramHelper("Drag wall endpoints or add doors and windows to the wall from the Wall Parts Palette");
|
||||
else if (selection.first().category === "WindowNode" || selection.first().category === "DoorNode") {
|
||||
if (node.containingGroup !== null) floorplanUI.setDiagramHelper("Drag and resize wall part along the wall; drag away from wall to detach");
|
||||
else floorplanUI.setDiagramHelper("Drag part so the cursor is over a wall to add this part to a wall");
|
||||
}
|
||||
else if (selection.first().category === "MultiPurposeNode") floorplanUI.setDiagramHelper("Double click on part text to revise it");
|
||||
else floorplanUI.setDiagramHelper("Drag, resize, or rotate (hold SHIFT for no snap) your selection");
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
/*
|
||||
* Node Templates
|
||||
* Add Default Node, Multi-Purpose Node, Window Node, Palette Wall Node, and Door Node to the Node Template Map
|
||||
* Template functions defined in FloorPlanner-Templates-* js files
|
||||
*/
|
||||
|
||||
this.nodeTemplateMap.add("", makeDefaultNode()); // Default Node (furniture)
|
||||
this.nodeTemplateMap.add("MultiPurposeNode", makeMultiPurposeNode()); // Multi-Purpose Node
|
||||
this.nodeTemplateMap.add("WindowNode", makeWindowNode()); // Window Node
|
||||
this.nodeTemplateMap.add("PaletteWallNode", makePaletteWallNode()); // Palette Wall Node
|
||||
this.nodeTemplateMap.add("DoorNode", makeDoorNode()); // Door Node
|
||||
|
||||
/*
|
||||
* Group Templates
|
||||
* Add Default Group, Wall Group to Group Template Map
|
||||
* Template functions defined in FloorPlanner-Templates-* js files
|
||||
*/
|
||||
|
||||
this.groupTemplateMap.add("", makeDefaultGroup()); // Default Group
|
||||
this.groupTemplateMap.add("WallGroup", makeWallGroup()); // Wall Group
|
||||
|
||||
/*
|
||||
* Install Custom Tools
|
||||
* Wall Building Tool, Wall Reshaping Tool
|
||||
* Tools are defined in their own FloorPlanner-<Tool>.js files
|
||||
*/
|
||||
|
||||
var wallBuildingTool = new WallBuildingTool();
|
||||
this.toolManager.mouseDownTools.insertAt(0, wallBuildingTool);
|
||||
|
||||
var wallReshapingTool = new WallReshapingTool();
|
||||
this.toolManager.mouseDownTools.insertAt(3, wallReshapingTool);
|
||||
wallBuildingTool.isEnabled = false;
|
||||
|
||||
/*
|
||||
* Tool Overrides
|
||||
*/
|
||||
|
||||
// If a wall was dragged to intersect another wall, update angle displays
|
||||
this.toolManager.draggingTool.doMouseUp = function () {
|
||||
go.DraggingTool.prototype.doMouseUp.call(this);
|
||||
this.diagram.updateWallAngles();
|
||||
this.isGridSnapEnabled = this.diagram.model.modelData.preferences.gridSnap;
|
||||
}
|
||||
|
||||
// If user holds SHIFT while dragging, do not use grid snap
|
||||
this.toolManager.draggingTool.doMouseMove = function () {
|
||||
if (this.diagram.lastInput.shift) {
|
||||
this.isGridSnapEnabled = false;
|
||||
} else this.isGridSnapEnabled = this.diagram.model.modelData.preferences.gridSnap;
|
||||
go.DraggingTool.prototype.doMouseMove.call(this);
|
||||
}
|
||||
|
||||
// When resizing, constantly update the node info box with updated size info; constantly update Dimension Links
|
||||
this.toolManager.resizingTool.doMouseMove = function () {
|
||||
var floorplan = this.diagram;
|
||||
var node = this.adornedObject;
|
||||
// if node is the only thing selected, display its info as its resized
|
||||
if (floorplan.selection.count === 1 && floorplan.floorplanUI) floorplan.floorplanUI.setSelectionInfo(node);
|
||||
this.diagram.updateWallDimensions();
|
||||
go.ResizingTool.prototype.doMouseMove.call(this);
|
||||
}
|
||||
|
||||
// When resizing a wallPart, do not allow it to be resized past the nearest wallPart / wall endpoints
|
||||
this.toolManager.resizingTool.computeMaxSize = function () {
|
||||
var tool = this;
|
||||
var obj = tool.adornedObject.part;
|
||||
var wall = this.diagram.findPartForKey(obj.data.group);
|
||||
if ((obj.category === 'DoorNode' || obj.category === 'WindowNode') && wall !== null) {
|
||||
var stationaryPt; var movingPt;
|
||||
var resizeAdornment = null;
|
||||
obj.adornments.iterator.each(function (adorn) { if (adorn.name === "WallPartResizeAdornment") resizeAdornment = adorn; });
|
||||
resizeAdornment.elements.iterator.each(function (el) {
|
||||
if (el instanceof go.Shape && el.alignment === tool.handle.alignment) movingPt = el.getDocumentPoint(go.Spot.Center);
|
||||
if (el instanceof go.Shape && el.alignment !== tool.handle.alignment) stationaryPt = el.getDocumentPoint(go.Spot.Center);
|
||||
});
|
||||
// find the constrainingPt; that is, the endpoint (wallPart endpoint or wall endpoint) that is the one closest to movingPt but still farther from stationaryPt than movingPt
|
||||
// this loop checks all other wallPart endpoints of the wall that the resizing wallPart is a part of
|
||||
var constrainingPt; var closestDist = Number.MAX_VALUE;
|
||||
wall.memberParts.iterator.each(function (part) {
|
||||
if (part.data.key !== obj.data.key) {
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var point = endpoints[i];
|
||||
var distanceToMovingPt = Math.sqrt(point.distanceSquaredPoint(movingPt));
|
||||
if (distanceToMovingPt < closestDist) {
|
||||
var distanceToStationaryPt = Math.sqrt(point.distanceSquaredPoint(stationaryPt));
|
||||
if (distanceToStationaryPt > distanceToMovingPt) {
|
||||
closestDist = distanceToMovingPt;
|
||||
constrainingPt = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
// if we're not constrained by a wallPart endpoint, the constraint will come from a wall endpoint; figure out which one
|
||||
if (constrainingPt === undefined || constrainingPt === null) {
|
||||
if (wall.data.startpoint.distanceSquaredPoint(movingPt) > wall.data.startpoint.distanceSquaredPoint(stationaryPt)) constrainingPt = wall.data.endpoint;
|
||||
else constrainingPt = wall.data.startpoint;
|
||||
}
|
||||
// set the new max size of the wallPart according to the constrainingPt
|
||||
var maxLength = Math.sqrt(stationaryPt.distanceSquaredPoint(constrainingPt));
|
||||
return new go.Size(maxLength, wall.data.thickness);
|
||||
}
|
||||
return go.ResizingTool.prototype.computeMaxSize.call(tool);
|
||||
}
|
||||
|
||||
this.toolManager.draggingTool.isGridSnapEnabled = true;
|
||||
} go.Diagram.inherit(Floorplan, go.Diagram);
|
||||
|
||||
// Get/set the Floorplan Filesystem instance associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "floorplanFilesystem", {
|
||||
get: function () { return this._floorplanFilesystem; },
|
||||
set: function (val) {
|
||||
val instanceof FloorplanFilesystem ? this._floorplanFilesystem = val : this._floorplanFilesystem = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Get/set the FloorplanUI instance associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "floorplanUI", {
|
||||
get: function () { return this._floorplanUI; },
|
||||
set: function (val) {
|
||||
val instanceof FloorplanUI ? this._floorplanUI = val : this._floorplanUI = null;
|
||||
}
|
||||
});
|
||||
|
||||
// Get array of all FloorplanPalettes associated with this Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "palettes", {
|
||||
get: function () { return this._palettes; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Point Nodes in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "pointNodes", {
|
||||
get: function () { return this._pointNodes; },
|
||||
set: function (val) { this._pointNodes = val; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Dimension Links in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "dimensionLinks", {
|
||||
get: function () { return this._dimensionLinks; },
|
||||
set: function () { this._dimensionLinks = val; }
|
||||
});
|
||||
|
||||
// Get / set Set of all Angle Nodes in the Floorplan
|
||||
Object.defineProperty(Floorplan.prototype, "angleNodes", {
|
||||
get: function () { return this._angleNodes; },
|
||||
set: function () { this._angleNodes = val; }
|
||||
});
|
||||
|
||||
|
||||
// Check what units are being used, convert to cm then multiply by 2, (1px = 2cm, change this if you want to use a different paradigm)
|
||||
Floorplan.prototype.convertPixelsToUnits = function (num) {
|
||||
var units = this.model.modelData.units;
|
||||
var factor = this.model.modelData.unitsConversionFactor;
|
||||
if (units === 'meters') return (num / 100) * factor;
|
||||
if (units === 'feet') return (num / 30.48) * factor;
|
||||
if (units === 'inches') return (num / 2.54) * factor;
|
||||
return num * factor;
|
||||
}
|
||||
|
||||
// Take a number of units, convert to cm, then divide by 2, (1px = 2cm, change this if you want to use a different paradigm)
|
||||
Floorplan.prototype.convertUnitsToPixels = function (num) {
|
||||
var units = this.model.modelData.units;
|
||||
var factor = this.model.modelData.unitsConversionFactor;
|
||||
if (units === 'meters') return (num * 100) / factor;
|
||||
if (units === 'feet') return (num * 30.48) / factor;
|
||||
if (units === 'inches') return (num * 2.54) / factor;
|
||||
return num / factor;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update the geometry, angle, and location of a given wall
|
||||
* @param {Wall} wall A reference to a valid Wall Group (defined in Templates-Walls)
|
||||
*/
|
||||
Floorplan.prototype.updateWall = function (wall) {
|
||||
if (wall.data.startpoint && wall.data.endpoint) {
|
||||
var shape = wall.findObject("SHAPE");
|
||||
var geo = new go.Geometry(go.Geometry.Line);
|
||||
var sPt = wall.data.startpoint;
|
||||
var ePt = wall.data.endpoint;
|
||||
var mPt = new go.Point((sPt.x + ePt.x) / 2, (sPt.y + ePt.y) / 2);
|
||||
// define a wall's geometry as a simple horizontal line, then rotate it
|
||||
geo.startX = 0;
|
||||
geo.startY = 0;
|
||||
geo.endX = Math.sqrt(sPt.distanceSquaredPoint(ePt));
|
||||
geo.endY = 0;
|
||||
shape.geometry = geo;
|
||||
wall.location = mPt; // a wall's location is the midpoint between it's startpoint and endpoint
|
||||
var angle = sPt.directionPoint(ePt);
|
||||
wall.rotateObject.angle = angle;
|
||||
this.updateWallDimensions();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for Build Dimension Link: get a to/from point for a Dimension Link
|
||||
* @param {Wall} wall The Wall Group being given a Dimension Link
|
||||
* @param {Number} angle The angle of "wall"
|
||||
* @param {Number} wallOffset The distance the Dimension Link will be from wall (in pixels)
|
||||
*/
|
||||
Floorplan.prototype.getAdjustedPoint = function (point, wall, angle, wallOffset) {
|
||||
var oldPoint = point.copy();
|
||||
point.offset(0, -(wall.data.thickness * .5) - wallOffset);
|
||||
point.offset(-oldPoint.x, -oldPoint.y).rotate(angle).offset(oldPoint.x, oldPoint.y);
|
||||
return point;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for Update Wall Dimensions; used to build Dimension Links
|
||||
* @param {Wall} wall The wall the Link runs along (either describing the wall itself or some wallPart on "wall")
|
||||
* @param {Number} index A number appended to PointNode keys; used for finding PointNodes of Dimension Links later
|
||||
* @param {Point} point1 The first point of the wallPart being described by the Link
|
||||
* @param {Point} point2 The second point of the wallPart being described by the Link
|
||||
* @param {Number} angle The angle of the wallPart
|
||||
* @param {Number} wallOffset How far from the wall (in px) the Link should be
|
||||
* @param {Boolean} soloWallFlag If this Link is the only Dimension Link for "wall" (no other wallParts on "wall" selected) this is true; else, false
|
||||
* @param {Floorplan} floorplan A reference to a valid Floorplan
|
||||
*/
|
||||
Floorplan.prototype.buildDimensionLink = function (wall, index, point1, point2, angle, wallOffset, soloWallFlag, floorplan) {
|
||||
point1 = floorplan.getAdjustedPoint(point1, wall, angle, wallOffset);
|
||||
point2 = floorplan.getAdjustedPoint(point2, wall, angle, wallOffset);
|
||||
var data1 = { key: wall.data.key + "PointNode" + index, category: "PointNode", loc: go.Point.stringify(point1) };
|
||||
var data2 = { key: wall.data.key + "PointNode" + (index + 1), category: "PointNode", loc: go.Point.stringify(point2) };
|
||||
var data3 = { key: wall.data.key + "DimensionLink", category: 'DimensionLink', from: data1.key, to: data2.key, stroke: 'gray', angle: angle, wall: wall.data.key, soloWallFlag: soloWallFlag };
|
||||
var pointNode1 = makePointNode();
|
||||
var pointNode2 = makePointNode();
|
||||
var link = makeDimensionLink();
|
||||
|
||||
floorplan.pointNodes.add(pointNode1);
|
||||
floorplan.pointNodes.add(pointNode2);
|
||||
floorplan.dimensionLinks.add(link);
|
||||
floorplan.add(pointNode1);
|
||||
floorplan.add(pointNode2);
|
||||
floorplan.add(link);
|
||||
|
||||
pointNode1.data = data1;
|
||||
pointNode2.data = data2;
|
||||
link.data = data3;
|
||||
link.fromNode = pointNode1;
|
||||
link.toNode = pointNode2;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update Dimension Links shown along walls, based on which walls and wallParts are selected
|
||||
*/
|
||||
Floorplan.prototype.updateWallDimensions = function () {
|
||||
var floorplan = this;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("update wall dimensions");
|
||||
// if showWallLengths === false, remove all pointNodes (used to build wall dimensions)
|
||||
if (!floorplan.model.modelData.preferences.showWallLengths) {
|
||||
floorplan.pointNodes.iterator.each(function (node) { floorplan.remove(node); });
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { floorplan.remove(link); });
|
||||
floorplan.pointNodes.clear();
|
||||
floorplan.dimensionLinks.clear();
|
||||
floorplan.commitTransaction("update wall dimensions");
|
||||
floorplan.skipsUndoManager = false;
|
||||
return;
|
||||
}
|
||||
// make visible all dimension links (zero-length dimension links are set to invisible at the end of the function)
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { link.visible = true; });
|
||||
|
||||
var selection = floorplan.selection;
|
||||
// gather all selected walls, including walls of selected DoorNodes and WindowNodes
|
||||
var walls = new go.Set(/*go.Group*/);
|
||||
selection.iterator.each(function (part) {
|
||||
if ((part.category === 'WindowNode' || part.category === 'DoorNode') && part.containingGroup !== null) walls.add(part.containingGroup);
|
||||
if (part.category === 'WallGroup' && part.data && part.data.startpoint && part.data.endpoint) {
|
||||
var soloWallLink = null;
|
||||
floorplan.dimensionLinks.iterator.each(function (link) { if (link.data.soloWallFlag && link.data.wall === part.data.key) soloWallLink = link; });
|
||||
// if there's 1 Dimension Link for this wall (link has soloWallFlag), adjust to/from pointNodes of link, rather than deleting / redrawing
|
||||
if (soloWallLink !== null) {
|
||||
// since this is the only Dimension Link for this wall, keys of its pointNodes will be (wall.data.key) + 1 / (wall.data.key) + 2
|
||||
var linkPoint1 = null; var linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === part.data.key + "PointNode1") linkPoint1 = node;
|
||||
if (node.data.key === part.data.key + "PointNode2") linkPoint2 = node;
|
||||
});
|
||||
var startpoint = part.data.startpoint; var endpoint = part.data.endpoint;
|
||||
// adjust left/top-most / right/bottom-most wall endpoints so link angle is correct (else text appears on wrong side of Link)
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var newLoc1 = floorplan.getAdjustedPoint(firstWallPt.copy(), part, part.rotateObject.angle, 10);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(lastWallPt.copy(), part, part.rotateObject.angle, 10);
|
||||
// cannot use model.setDataProperty, since pointNodes and dimensionLinks are not stored in the model
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
soloWallLink.data.angle = part.rotateObject.angle;
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
soloWallLink.updateTargetBindings();
|
||||
}
|
||||
// else build a Dimension Link for this wall; this is removed / replaced if Dimension Links for wallParts this wall are built
|
||||
else {
|
||||
var startpoint = part.data.startpoint;
|
||||
var endpoint = part.data.endpoint;
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
floorplan.buildDimensionLink(part, 1, firstWallPt.copy(), lastWallPt.copy(), part.rotateObject.angle, 10, true, floorplan);
|
||||
}
|
||||
}
|
||||
});
|
||||
// create array of selected wall endpoints and selected wallPart endpoints along the wall that represent measured stretches
|
||||
walls.iterator.each(function (wall) {
|
||||
var startpoint = wall.data.startpoint;
|
||||
var endpoint = wall.data.endpoint;
|
||||
var firstWallPt = ((startpoint.x + startpoint.y) <= (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
var lastWallPt = ((startpoint.x + startpoint.y) > (endpoint.x + endpoint.y)) ? startpoint : endpoint;
|
||||
|
||||
// store all endpoints along with the part they correspond to (used later to either create DimensionLinks or simply adjust them)
|
||||
var wallPartEndpoints = [];
|
||||
wall.memberParts.iterator.each(function (wallPart) {
|
||||
if (wallPart.isSelected) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
wallPartEndpoints.push(endpoints[0]);
|
||||
wallPartEndpoints.push(endpoints[1]);
|
||||
}
|
||||
});
|
||||
// sort all wallPartEndpoints by x coordinate left to right/ up to down
|
||||
wallPartEndpoints.sort(function (a, b) {
|
||||
if ((a.x + a.y) > (b.x + b.y)) return 1;
|
||||
if ((a.x + a.y) < (b.x + b.y)) return -1;
|
||||
else return 0;
|
||||
});
|
||||
wallPartEndpoints.unshift(firstWallPt);
|
||||
wallPartEndpoints.push(lastWallPt);
|
||||
|
||||
var angle = wall.rotateObject.angle;
|
||||
var k = 1; // k is a counter for the indices of PointNodes
|
||||
// build / edit dimension links for each stretch, defined by pairs of points in wallPartEndpoints
|
||||
for (var j = 0; j < wallPartEndpoints.length - 1; j++) {
|
||||
var linkPoint1 = null; linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === wall.data.key + "PointNode" + k) linkPoint1 = node;
|
||||
if (node.data.key === wall.data.key + "PointNode" + (k + 1)) linkPoint2 = node;
|
||||
});
|
||||
if (linkPoint1 !== null) {
|
||||
var newLoc1 = floorplan.getAdjustedPoint(wallPartEndpoints[j].copy(), wall, angle, 5);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(wallPartEndpoints[j + 1].copy(), wall, angle, 5);
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
}
|
||||
// only build new links if needed -- normally simply change pointNode locations
|
||||
else floorplan.buildDimensionLink(wall, k, wallPartEndpoints[j].copy(), wallPartEndpoints[j + 1].copy(), angle, 5, false, floorplan);
|
||||
k += 2;
|
||||
}
|
||||
// total wall Dimension Link constructed of a kth and k+1st pointNode
|
||||
var totalWallDimensionLink = null;
|
||||
floorplan.dimensionLinks.iterator.each(function (link) {
|
||||
if ((link.fromNode.data.key === wall.data.key + "PointNode" + k) &&
|
||||
(link.toNode.data.key === wall.data.key + "PointNode" + (k + 1))) totalWallDimensionLink = link;
|
||||
});
|
||||
// if a total wall Dimension Link already exists, adjust its constituent point nodes
|
||||
if (totalWallDimensionLink !== null) {
|
||||
var linkPoint1 = null; var linkPoint2 = null;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key === wall.data.key + "PointNode" + k) linkPoint1 = node;
|
||||
if (node.data.key === wall.data.key + "PointNode" + (k + 1)) linkPoint2 = node;
|
||||
});
|
||||
var newLoc1 = floorplan.getAdjustedPoint(wallPartEndpoints[0].copy(), wall, angle, 25);
|
||||
var newLoc2 = floorplan.getAdjustedPoint(wallPartEndpoints[wallPartEndpoints.length - 1].copy(), wall, angle, 25);
|
||||
linkPoint1.data.loc = go.Point.stringify(newLoc1);
|
||||
linkPoint2.data.loc = go.Point.stringify(newLoc2);
|
||||
linkPoint1.updateTargetBindings();
|
||||
linkPoint2.updateTargetBindings();
|
||||
}
|
||||
// only build total wall Dimension Link (far out from wall to accomodate wallPart Dimension Links) if one does not already exist
|
||||
else floorplan.buildDimensionLink(wall, k, wallPartEndpoints[0].copy(), wallPartEndpoints[wallPartEndpoints.length - 1].copy(), angle, 25, false, floorplan);
|
||||
});
|
||||
|
||||
// Cleanup: hide zero-length Dimension Links, DimensionLinks with null wall points
|
||||
floorplan.dimensionLinks.iterator.each(function (link) {
|
||||
var canStay = false;
|
||||
floorplan.pointNodes.iterator.each(function (node) {
|
||||
if (node.data.key == link.data.to) canStay = true;
|
||||
});
|
||||
if (!canStay) floorplan.remove(link);
|
||||
else {
|
||||
var length = Math.sqrt(link.toNode.location.distanceSquaredPoint(link.fromNode.location));
|
||||
if (length < 1 && !link.data.soloWallFlag) link.visible = false;
|
||||
}
|
||||
});
|
||||
|
||||
floorplan.commitTransaction("update wall dimensions");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function for updateWallAngles(); returns the Point where two walls intersect; if they do not intersect, return null
|
||||
* @param {Wall} wall1
|
||||
* @param {Wall} wall2
|
||||
*/
|
||||
Floorplan.prototype.getWallsIntersection = function (wall1, wall2) {
|
||||
if (wall1 === null || wall2 === null) return null;
|
||||
// treat walls as lines; get lines in formula of ax + by = c
|
||||
var a1 = wall1.data.endpoint.y - wall1.data.startpoint.y;
|
||||
var b1 = wall1.data.startpoint.x - wall1.data.endpoint.x;
|
||||
var c1 = (a1 * wall1.data.startpoint.x) + (b1 * wall1.data.startpoint.y);
|
||||
var a2 = wall2.data.endpoint.y - wall2.data.startpoint.y;
|
||||
var b2 = wall2.data.startpoint.x - wall2.data.endpoint.x;
|
||||
var c2 = (a2 * wall2.data.startpoint.x) + (b2 * wall2.data.startpoint.y);
|
||||
// Solve the system of equations, finding where the lines (not segments) would intersect
|
||||
/** Algebra Explanation:
|
||||
Line 1: a1x + b1y = c1
|
||||
Line 2: a2x + b2y = c2
|
||||
|
||||
Multiply Line1 equation by b2, Line2 equation by b1, get:
|
||||
a1b1x + b1b2y = b2c1
|
||||
a2b1x + b1b2y = b1c2
|
||||
|
||||
Subtract bottom from top:
|
||||
a1b2x - a2b1x = b2c1 - b1c2
|
||||
|
||||
Divide both sides by a1b2 - a2b1, get equation for x. Equation for y is analogous
|
||||
**/
|
||||
var det = a1 * b2 - a2 * b1;
|
||||
var x = null; var y = null;
|
||||
// Edge Case: Lines are paralell
|
||||
if (det === 0) {
|
||||
// Edge Case: wall1 and wall2 have an endpoint to endpoint intersection (the only instance in which paralell walls could intersect at a specific point)
|
||||
if (wall1.data.startpoint.equals(wall2.data.startpoint) || wall1.data.startpoint.equals(wall2.data.endpoint)) return wall1.data.startpoint;
|
||||
if (wall1.data.endpoint.equals(wall2.data.startpoint) || wall1.data.endpoint.equals(wall2.data.endpoint)) return wall1.data.endpoint;
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
x = (b2 * c1 - b1 * c2) / det;
|
||||
y = (a1 * c2 - a2 * c1) / det;
|
||||
}
|
||||
// ensure proposed intersection is contained in both line segments (walls)
|
||||
var inWall1 = ((Math.min(wall1.data.startpoint.x, wall1.data.endpoint.x) <= x) && (Math.max(wall1.data.startpoint.x, wall1.data.endpoint.x) >= x)
|
||||
&& (Math.min(wall1.data.startpoint.y, wall1.data.endpoint.y) <= y) && (Math.max(wall1.data.startpoint.y, wall1.data.endpoint.y) >= y));
|
||||
var inWall2 = ((Math.min(wall2.data.startpoint.x, wall2.data.endpoint.x) <= x) && (Math.max(wall2.data.startpoint.x, wall2.data.endpoint.x) >= x)
|
||||
&& (Math.min(wall2.data.startpoint.y, wall2.data.endpoint.y) <= y) && (Math.max(wall2.data.startpoint.y, wall2.data.endpoint.y) >= y));
|
||||
if (inWall1 && inWall2) return new go.Point(x, y);
|
||||
else return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Update Angle Nodes shown along a wall, based on which wall(s) is/are selected
|
||||
*/
|
||||
Floorplan.prototype.updateWallAngles = function () {
|
||||
var floorplan = this;
|
||||
floorplan.skipsUndoManager = true; // do not store displaying angles as a transaction
|
||||
floorplan.startTransaction("display angles");
|
||||
if (floorplan.model.modelData.preferences.showWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { node.visible = true; });
|
||||
var selectedWalls = [];
|
||||
floorplan.selection.iterator.each(function (part) { if (part.category === "WallGroup") selectedWalls.push(part); });
|
||||
for (var i = 0; i < selectedWalls.length; i++) {
|
||||
var seen = new go.Set(/*"string"*/); // Set of all walls "seen" thus far for "wall"
|
||||
var wall = selectedWalls[i];
|
||||
var possibleWalls = floorplan.findNodesByExample({ category: "WallGroup" });
|
||||
|
||||
// go through all other walls; if the other wall intersects this wall, make angles
|
||||
possibleWalls.iterator.each(function (otherWall) {
|
||||
if (otherWall.data === null || wall.data === null || seen.contains(otherWall.data.key)) return;
|
||||
if ((otherWall.data.key !== wall.data.key) && (floorplan.getWallsIntersection(wall, otherWall) !== null) && (!seen.contains(otherWall.data.key))) {
|
||||
|
||||
seen.add(otherWall.data.key);
|
||||
// "otherWall" intersects "wall"; make or update angle nodes
|
||||
var intersectionPoint = floorplan.getWallsIntersection(wall, otherWall);
|
||||
var wallsInvolved = floorplan.findObjectsNear(intersectionPoint,
|
||||
1,
|
||||
function (x) { if (x.part !== null) return x.part; },
|
||||
function (p) { return p.category === "WallGroup"; },
|
||||
false);
|
||||
|
||||
var endpoints = []; // store endpoints and their corresponding walls here
|
||||
// gather endpoints of each wall in wallsInvolved; discard endpoints within a tolerance distance of intersectionPoint
|
||||
wallsInvolved.iterator.each(function (w) {
|
||||
var tolerance = (floorplan.model.modelData.gridSize >= 10) ? floorplan.model.modelData.gridSize : 10;
|
||||
if (Math.sqrt(w.data.startpoint.distanceSquaredPoint(intersectionPoint)) > tolerance) endpoints.push({ point: w.data.startpoint, wall: w.data.key });
|
||||
if (Math.sqrt(w.data.endpoint.distanceSquaredPoint(intersectionPoint)) > tolerance) endpoints.push({ point: w.data.endpoint, wall: w.data.key });
|
||||
});
|
||||
|
||||
// find maxRadius (shortest distance from an involved wall's endpoint to intersectionPoint or 30, whichever is smaller)
|
||||
var maxRadius = 30;
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var distance = Math.sqrt(endpoints[i].point.distanceSquaredPoint(intersectionPoint));
|
||||
if (distance < maxRadius) maxRadius = distance;
|
||||
}
|
||||
|
||||
// sort endpoints in a clockwise fashion around the intersectionPoint
|
||||
endpoints.sort(function (a, b) {
|
||||
a = a.point; b = b.point;
|
||||
if (a.x - intersectionPoint.x >= 0 && b.x - intersectionPoint.x < 0) return true;
|
||||
if (a.x - intersectionPoint.x < 0 && b.x - intersectionPoint.x >= 0) return false;
|
||||
if (a.x - intersectionPoint.x == 0 && b.x - intersectionPoint.x == 0) {
|
||||
if (a.y - intersectionPoint.y >= 0 || b.y - intersectionPoint.y >= 0) return a.y > b.y;
|
||||
return b.y > a.y;
|
||||
}
|
||||
|
||||
// compute the cross product of vectors (center -> a) x (center -> b)
|
||||
var det = (a.x - intersectionPoint.x) * (b.y - intersectionPoint.y) - (b.x - intersectionPoint.x) * (a.y - intersectionPoint.y);
|
||||
if (det < 0) return true;
|
||||
if (det > 0) return false;
|
||||
|
||||
// points a and b are on the same line from the center; check which point is closer to the center
|
||||
var d1 = (a.x - intersectionPoint.x) * (a.x - intersectionPoint.x) + (a.y - intersectionPoint.y) * (a.y - intersectionPoint.y);
|
||||
var d2 = (b.x - intersectionPoint.x) * (b.x - intersectionPoint.x) + (b.y - intersectionPoint.y) * (b.y - intersectionPoint.y);
|
||||
return d1 > d2;
|
||||
}); // end endpoints sort
|
||||
|
||||
// for each pair of endpoints, construct or modify an angleNode
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var p1 = endpoints[i];
|
||||
if (endpoints[i + 1] != null) var p2 = endpoints[i + 1];
|
||||
else var p2 = endpoints[0];
|
||||
var a1 = intersectionPoint.directionPoint(p1.point);
|
||||
var a2 = intersectionPoint.directionPoint(p2.point);
|
||||
var sweep = Math.abs(a2 - a1 + 360) % 360;
|
||||
var angle = a1;
|
||||
|
||||
/*
|
||||
construct proper key for angleNode
|
||||
proper angleNode key syntax is "wallWwallX...wallYangleNodeZ" such that W < Y < Y; angleNodes are sorted clockwise around the intersectionPoint by Z
|
||||
*/
|
||||
var keyArray = []; // used to construct proper key
|
||||
wallsInvolved.iterator.each(function (wall) { keyArray.push(wall); });
|
||||
keyArray.sort(function (a, b) {
|
||||
var aIndex = a.data.key.match(/\d+/g);
|
||||
var bIndex = b.data.key.match(/\d+/g);
|
||||
if (isNaN(aIndex)) return true;
|
||||
if (isNaN(bIndex)) return false;
|
||||
else return aIndex > bIndex;
|
||||
});
|
||||
|
||||
var key = "";
|
||||
for (var j = 0; j < keyArray.length; j++) key += keyArray[j].data.key;
|
||||
key += "angle" + i;
|
||||
|
||||
// check if this angleNode already exists -- if it does, adjust data (instead of deleting/redrawing)
|
||||
var angleNode = null;
|
||||
floorplan.angleNodes.iterator.each(function (aNode) { if (aNode.data.key === key) angleNode = aNode; });
|
||||
if (angleNode !== null) {
|
||||
angleNode.data.angle = angle;
|
||||
angleNode.data.sweep = sweep;
|
||||
angleNode.data.loc = go.Point.stringify(intersectionPoint);
|
||||
angleNode.data.maxRadius = maxRadius;
|
||||
angleNode.updateTargetBindings();
|
||||
}
|
||||
// if this angleNode does not already exist, create it and add it to the diagram
|
||||
else {
|
||||
var data = { key: key, category: "AngleNode", loc: go.Point.stringify(intersectionPoint), stroke: "dodgerblue", angle: angle, sweep: sweep, maxRadius: maxRadius };
|
||||
var newAngleNode = makeAngleNode();
|
||||
newAngleNode.data = data;
|
||||
floorplan.add(newAngleNode);
|
||||
newAngleNode.updateTargetBindings();
|
||||
floorplan.angleNodes.add(newAngleNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// garbage collection (angleNodes that should not exist any more)
|
||||
var garbage = [];
|
||||
floorplan.angleNodes.iterator.each(function (node) {
|
||||
var keyNums = node.data.key.match(/\d+/g); // values X for all wall keys involved, given key "wallX"
|
||||
var numWalls = (node.data.key.match(/wall/g) || []).length; // # of walls involved in in "node"'s construction
|
||||
var wallsInvolved = [];
|
||||
// add all walls involved in angleNode's construction to wallsInvolved
|
||||
for (var i = 0; i < keyNums.length - 1; i++) wallsInvolved.push("wall" + keyNums[i]);
|
||||
// edge case: if the numWalls != keyNums.length, that means the wall with key "wall" (no number in key) is involved
|
||||
if (numWalls !== keyNums.length - 1) wallsInvolved.push("wall");
|
||||
|
||||
// Case 1: if any wall pairs involved in this angleNode are no longer intersecting, add this angleNode to "garbage"
|
||||
for (var i = 0; i < wallsInvolved.length - 1; i++) {
|
||||
var wall1 = floorplan.findPartForKey(wallsInvolved[i]);
|
||||
var wall2 = floorplan.findPartForKey(wallsInvolved[i + 1]);
|
||||
var intersectionPoint = floorplan.getWallsIntersection(wall1, wall2);
|
||||
if (intersectionPoint === null) garbage.push(node);
|
||||
}
|
||||
// Case 2: if there are angleNode clusters with the same walls in their keys as "node" but different locations, destroy and rebuild
|
||||
// collect all angleNodes with same walls in their construction as "node"
|
||||
var possibleAngleNodes = new go.Set(/*go.Node*/);
|
||||
var allWalls = node.data.key.slice(0, node.data.key.indexOf("angle"));
|
||||
floorplan.angleNodes.iterator.each(function (other) { if (other.data.key.indexOf(allWalls) !== -1) possibleAngleNodes.add(other); });
|
||||
possibleAngleNodes.iterator.each(function (pNode) {
|
||||
if (pNode.data.loc !== node.data.loc) {
|
||||
garbage.push(pNode);
|
||||
}
|
||||
});
|
||||
|
||||
// Case 3: put any angleNodes with sweep === 0 in garbage
|
||||
if (node.data.sweep === 0) garbage.push(node);
|
||||
});
|
||||
|
||||
for (var i = 0; i < garbage.length; i++) {
|
||||
floorplan.remove(garbage[i]); // remove garbage
|
||||
floorplan.angleNodes.remove(garbage[i]);
|
||||
}
|
||||
}
|
||||
// hide all angles > 180 if show only small angles == true in preferences
|
||||
if (floorplan.model.modelData.preferences.showOnlySmallWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { if (node.data.sweep >= 180) node.visible = false; });
|
||||
}
|
||||
// hide all angles if show wall angles == false in preferences
|
||||
if (!floorplan.model.modelData.preferences.showWallAngles) {
|
||||
floorplan.angleNodes.iterator.each(function (node) { node.visible = false; });
|
||||
}
|
||||
floorplan.commitTransaction("display angles");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplan Filesystem Class
|
||||
* Handles Floorplan-specific saving / loading model data events
|
||||
* Attached to a specific instance of Floorplan (via constructor); can be assigned to Floorplan.floorplanFilesystem
|
||||
* Currently only supports saving / loading from localstorage
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan Filesystem Constructor
|
||||
* @param {Floorplan} floorplan A reference to a valid instance of Floorplan
|
||||
* @param {Object} state A JSON object with string ids for UI objects (windows, listboxes, HTML elements)
|
||||
* {
|
||||
* openWindowId: {String} the id of the HTML window to open a file
|
||||
* removeWindowId: {String} the id of the HTML window to remove files
|
||||
* currentFileId: {String} the id of the HTML element containing the name of the currently open file
|
||||
* filesToOpenListId: {String} the id of the HTML listbox in the openWindow
|
||||
* filesToRemoveListId: {String} the id of the HTML listbox of the removeWindow
|
||||
* }
|
||||
*/
|
||||
function FloorplanFilesystem(floorplan, state) {
|
||||
this._floorplan = floorplan;
|
||||
this._floorplan.floorplanFilesystem = this;
|
||||
this._UNSAVED_FILENAME = "(Unsaved File)";
|
||||
this._DEFAULT_MODELDATA = {
|
||||
"units": "centimeters",
|
||||
"unitsAbbreviation": "cm",
|
||||
"unitsConversionFactor": 2,
|
||||
"gridSize": 10,
|
||||
"wallWidth": 5,
|
||||
"preferences": {
|
||||
showWallGuidelines: true,
|
||||
showWallLengths: true,
|
||||
showWallAngles: true,
|
||||
showOnlySmallWallAngles: true,
|
||||
showGrid: true,
|
||||
gridSnap: true
|
||||
}
|
||||
};
|
||||
this._state = state;
|
||||
}
|
||||
|
||||
// Get the Floorplan associated with this Floorplan Filesystem
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "floorplan", {
|
||||
get: function () { return this._floorplan; }
|
||||
});
|
||||
|
||||
// Get constant name for an unsaved Floorplan
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "UNSAVED_FILENAME", {
|
||||
get: function () { return this._UNSAVED_FILENAME; }
|
||||
});
|
||||
|
||||
// Get constant default model data (default Floorplan model data for a new Floorplan)
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "DEFAULT_MODELDATA", {
|
||||
get: function () { return this._DEFAULT_MODELDATA; }
|
||||
});
|
||||
|
||||
// Get state information about this app's UI
|
||||
Object.defineProperty(FloorplanFilesystem.prototype, "state", {
|
||||
get: function () { return this._state; }
|
||||
});
|
||||
|
||||
/*
|
||||
* Helper functions
|
||||
* Check Local Storage, Update File List, Open Window
|
||||
*/
|
||||
|
||||
// Ensure local storage works in browser (not supported by MS IE/Edge)
|
||||
function checkLocalStorage() {
|
||||
try {
|
||||
window.localStorage.setItem('item', 'item');
|
||||
window.localStorage.removeItem('item');
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update listbox of files in window (for loading / removing files)
|
||||
function updateFileList(id) {
|
||||
// displays cached floor plan files in the listboxes
|
||||
var listbox = document.getElementById(id);
|
||||
// remove any old listing of files
|
||||
var last;
|
||||
while (last = listbox.lastChild) listbox.removeChild(last);
|
||||
// now add all saved files to the listbox
|
||||
for (key in window.localStorage) {
|
||||
var storedFile = window.localStorage.getItem(key);
|
||||
if (!storedFile) continue;
|
||||
var option = document.createElement("option");
|
||||
option.value = key;
|
||||
option.text = key;
|
||||
listbox.add(option, null)
|
||||
}
|
||||
}
|
||||
|
||||
// Open a specifed window element -- used for Remove / Open file windows
|
||||
function openWindow(id, listid) {
|
||||
var panel = document.getElementById(id);
|
||||
if (panel.style.visibility === "hidden") {
|
||||
updateFileList(listid);
|
||||
panel.style.visibility = "visible";
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Instance methods
|
||||
* New Floorplan, Save Floorplan, Save Floorplan As, Load Floorplan, Remove Floorplan
|
||||
* Show Open Window, Show Remove Window
|
||||
* Set Current File Name, Get Current File Name
|
||||
*/
|
||||
|
||||
// Create new floorplan (Ctrl + D or File -> New)
|
||||
FloorplanFilesystem.prototype.newFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
// checks to see if all changes have been saved
|
||||
if (floorplan.isModified) {
|
||||
var save = confirm("Would you like to save changes to " + this.getCurrentFileName() + "?");
|
||||
if (save) {
|
||||
this.saveFloorplan();
|
||||
}
|
||||
}
|
||||
this.setCurrentFileName(this.UNSAVED_FILENAME);
|
||||
// loads an empty diagram
|
||||
var model = new go.GraphLinksModel;
|
||||
// initialize all modelData
|
||||
model.modelData = this.DEFAULT_MODELDATA;
|
||||
floorplan.model = model;
|
||||
floorplan.undoManager.isEnabled = true;
|
||||
floorplan.isModified = false;
|
||||
if (floorplan.floorplanUI) {
|
||||
floorplan.floorplanUI.updateUI();
|
||||
floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
}
|
||||
|
||||
// Save current floor plan to local storage (Ctrl + S or File -> Save)
|
||||
FloorplanFilesystem.prototype.saveFloorplan = function () {
|
||||
if (checkLocalStorage()) {
|
||||
var saveName = this.getCurrentFileName();
|
||||
if (saveName === this.UNSAVED_FILENAME || saveName == null || saveName == undefined) {
|
||||
this.saveFloorplanAs();
|
||||
} else {
|
||||
window.localStorage.setItem(saveName, this.floorplan.model.toJson());
|
||||
this.floorplan.isModified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save floor plan to local storage with a new name (File -> Save As)
|
||||
FloorplanFilesystem.prototype.saveFloorplanAs = function () {
|
||||
if (checkLocalStorage()) {
|
||||
var saveName = prompt("Save file as...", this.getCurrentFileName());
|
||||
// if saveName is already in list of files, ask if overwrite is ok
|
||||
if (saveName && saveName !== this.UNSAVED_FILENAME) {
|
||||
var override = true;
|
||||
if (window.localStorage.getItem(saveName) !== null) {
|
||||
override = confirm("Do you want to overwrite " + saveName + "?");
|
||||
}
|
||||
if (override) {
|
||||
this.setCurrentFileName(saveName);
|
||||
window.localStorage.setItem(saveName, this.floorplan.model.toJson());
|
||||
this.floorplan.isModified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load floorplan model data in "Open" window (Ctrl + O or File -> Open)
|
||||
FloorplanFilesystem.prototype.loadFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var listbox = document.getElementById(this.state.filesToOpenListId);
|
||||
var fileName = undefined; // get selected filename
|
||||
for (var i = 0; i < listbox.options.length; i++) {
|
||||
if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file
|
||||
}
|
||||
if (fileName !== undefined) {
|
||||
var savedFile = window.localStorage.getItem(fileName);
|
||||
this.loadFloorplanFromModel(savedFile);
|
||||
floorplan.isModified = false;
|
||||
this.setCurrentFileName(fileName);
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.hideShow(this.state.openWindowId);
|
||||
}
|
||||
|
||||
FloorplanFilesystem.prototype.loadFloorplanFromModel = function (str) {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.model = go.Model.fromJson(str);
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("generate walls");
|
||||
floorplan.nodes.each(function (node) {
|
||||
if (node.category === "WallGroup") floorplan.updateWall(node);
|
||||
});
|
||||
if (floorplan.floorplanUI) {
|
||||
floorplan.floorplanUI.updateUI();
|
||||
floorplan.floorplanUI.updateStatistics();
|
||||
}
|
||||
|
||||
floorplan.commitTransaction("generate walls");
|
||||
floorplan.undoManager.isEnabled = true;
|
||||
|
||||
}
|
||||
|
||||
// Delete selected floorplan from local storage
|
||||
FloorplanFilesystem.prototype.removeFloorplan = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var listbox = document.getElementById(this.state.filesToRemoveListId);
|
||||
var fileName = undefined; // get selected filename
|
||||
for (var i = 0; i < listbox.options.length; i++) {
|
||||
if (listbox.options[i].selected) fileName = listbox.options[i].text; // selected file
|
||||
}
|
||||
if (fileName !== undefined) {
|
||||
// removes file from local storage
|
||||
window.localStorage.removeItem(fileName);
|
||||
}
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.hideShow(this.state.removeWindowId);
|
||||
}
|
||||
|
||||
// Check to see if all changes have been saved -> show the "Open" window
|
||||
FloorplanFilesystem.prototype.showOpenWindow = function () {
|
||||
if (checkLocalStorage()) {
|
||||
if (this.floorplan.isModified) {
|
||||
var save = confirm("Would you like to save changes to " + this.getCurrentFileName() + "?");
|
||||
if (save) {
|
||||
this.saveFloorplan();
|
||||
}
|
||||
}
|
||||
openWindow(this.state.openWindowId, this.state.filesToOpenListId);
|
||||
}
|
||||
}
|
||||
|
||||
// Show the Remove File window
|
||||
FloorplanFilesystem.prototype.showRemoveWindow = function () {
|
||||
if (checkLocalStorage()) {
|
||||
openWindow(this.state.removeWindowId, this.state.filesToRemoveListId);
|
||||
}
|
||||
}
|
||||
|
||||
// Add * to current file element if diagram has been modified
|
||||
FloorplanFilesystem.prototype.setCurrentFileName = function (name) {
|
||||
var currentFile = document.getElementById(this.state.currentFileId);
|
||||
if (currentFile) {
|
||||
if (this.floorplan.isModified) name += "*";
|
||||
currentFile.textContent = name;
|
||||
}
|
||||
}
|
||||
|
||||
// Get current file name from the current file element
|
||||
FloorplanFilesystem.prototype.getCurrentFileName = function () {
|
||||
var currentFile = document.getElementById(this.state.currentFileId);
|
||||
if (currentFile) {
|
||||
var name = currentFile.textContent;
|
||||
if (name[name.length - 1] === "*") return name.substr(0, name.length - 1);
|
||||
}
|
||||
return name;
|
||||
}
|
||||
+773
@@ -0,0 +1,773 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* FLOOR PLANN UI CLASS
|
||||
* Handle GUI manipulation (showing/changing data, populating windows, etc) for Floorplanner.html
|
||||
*/
|
||||
|
||||
/*
|
||||
* Floorplan UI Constructor
|
||||
* @param {Floorplan} floorplan A reference to a valid instance of Floorplan
|
||||
* @param {String} name The name of this FloorplanUI instance known to the DOM
|
||||
* @param {String} The name of this UI's floorplan known to the DOM
|
||||
* @param {Object} state A JSON object with string ids for UI HTML elements. Format is as follows:
|
||||
menuButtons: {
|
||||
selectionInfoWindowButtonId:
|
||||
palettesWindowButtonId:
|
||||
overviewWindowButtonId:
|
||||
optionsWindowButtonId:
|
||||
statisticsWindowButtonId:
|
||||
}
|
||||
windows: {
|
||||
diagramHelpDiv: {
|
||||
id:
|
||||
}
|
||||
selectionInfoWindow: {
|
||||
id:
|
||||
textDivId:
|
||||
handleId:
|
||||
colorPickerId:
|
||||
heightLabelId:
|
||||
heightInputId:
|
||||
widthInputId:
|
||||
nodeGroupInfoId:
|
||||
nameInputId:
|
||||
notesTextareaId:
|
||||
}
|
||||
palettesWindow:{
|
||||
id:
|
||||
furnitureSearchInputId:
|
||||
furniturePaletteId:
|
||||
}
|
||||
overviewWindow: {
|
||||
id:
|
||||
}
|
||||
optionsWindow: {
|
||||
id:
|
||||
gridSizeInputId:
|
||||
unitsConversionFactorInputId:
|
||||
unitsFormId:
|
||||
unitsFormName:
|
||||
checkboxes: {
|
||||
showGridCheckboxId:
|
||||
gridSnapCheckboxId:
|
||||
wallGuidelinesCheckboxId:
|
||||
wallLengthsCheckboxId:
|
||||
wallAnglesCheckboxId:
|
||||
smallWallAnglesCheckboxId:
|
||||
}
|
||||
}
|
||||
statisticsWindow: {
|
||||
id:
|
||||
textDivId:
|
||||
numsTableId:
|
||||
totalsTableId:
|
||||
}
|
||||
}
|
||||
scaleDisplayId:
|
||||
setBehaviorClass:
|
||||
wallThicknessInputId:
|
||||
wallThicknessBoxId:
|
||||
unitsBoxId:
|
||||
unitsInputId:
|
||||
*/
|
||||
function FloorplanUI(floorplan, name, floorplanName, state) {
|
||||
this._floorplan = floorplan;
|
||||
this._name = name;
|
||||
this._floorplanName = floorplanName;
|
||||
this._state = state;
|
||||
this._furnitureNodeData = null; // used for searchFurniture function. set only once
|
||||
this.floorplan.floorplanUI = this;
|
||||
}
|
||||
|
||||
// Get Floorplan associated with this UI
|
||||
Object.defineProperty(FloorplanUI.prototype, "floorplan", {
|
||||
get: function () { return this._floorplan; }
|
||||
});
|
||||
|
||||
// Get state object containing many ids of various UI elements
|
||||
Object.defineProperty(FloorplanUI.prototype, "state", {
|
||||
get: function () { return this._state; }
|
||||
});
|
||||
|
||||
// Get name of this FloorplanUI instance known to the DOM
|
||||
Object.defineProperty(FloorplanUI.prototype, "name", {
|
||||
get: function () { return this._name; }
|
||||
});
|
||||
|
||||
// Get name of the Floorplan associated with this FloorplanUI instance known to the DOM
|
||||
Object.defineProperty(FloorplanUI.prototype, "floorplanName", {
|
||||
get: function () { return this._floorplanName; }
|
||||
});
|
||||
|
||||
Object.defineProperty(FloorplanUI.prototype, "furnitureData", {
|
||||
get: function () { return this._furnitureData; },
|
||||
set: function (val) { this._furnitureData = val; }
|
||||
});
|
||||
|
||||
/*
|
||||
* UI manipulation:
|
||||
* Hide/Show Element, Adjust Scale, ChangeGridSize, Change Units Conversion Factor
|
||||
* Search Furniture, Checkbox Changed, Change Units, Set Behavior, Update UI
|
||||
*/
|
||||
|
||||
/*
|
||||
* Hide or show specific help/windows (used mainly with hotkeys)
|
||||
* @param {String} id The ID of the window to show / hide
|
||||
*/
|
||||
FloorplanUI.prototype.hideShow = function(id) {
|
||||
var element = document.getElementById(id); var str;
|
||||
var windows = this.state.windows;
|
||||
switch (id) {
|
||||
case windows.diagramHelpDiv.id: str = 'Diagram Help'; char = 'H'; break;
|
||||
case windows.selectionInfoWindow.id: str = 'Selection Help'; char = 'I'; break;
|
||||
case windows.overviewWindow.id: str = 'Overview'; char = 'E'; break;
|
||||
case windows.optionsWindow.id: str = 'Options'; char = 'B'; break;
|
||||
case windows.statisticsWindow.id: str = 'Statistics'; char = 'G'; break;
|
||||
case windows.palettesWindow.id: str = 'Palettes'; char = 'P'; {
|
||||
furniturePalette.layoutDiagram(true);
|
||||
wallPartsPalette.layoutDiagram(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
var button = document.getElementById(id + 'Button');
|
||||
element.style.visibility = element.style.visibility === "visible" ? "hidden" : "visible";
|
||||
var verb = element.style.visibility === "visible" ? "Hide " : "Show ";
|
||||
if (button) button.innerHTML = verb + str + "<p class='shortcut'> (Ctrl + " + char + " )</p>";
|
||||
}
|
||||
|
||||
/*
|
||||
* Set text under Diagram to suggest most common functions user could perform
|
||||
* @param {String} str The text to display in the Diagram Help div
|
||||
*/
|
||||
FloorplanUI.prototype.setDiagramHelper = function(str) {
|
||||
var helper = document.getElementById(this.state.windows.diagramHelpDiv.id);
|
||||
if (helper) helper.innerHTML = '<p>' + str + '</p>';
|
||||
}
|
||||
|
||||
/*
|
||||
* Increase / decrease diagram scale to the nearest 10%
|
||||
* @param {String} sign Accepted values are "+" and "-"
|
||||
*/
|
||||
FloorplanUI.prototype.adjustScale = function(sign) {
|
||||
var floorplan = this.floorplan;
|
||||
var el = document.getElementById(this.state.scaleDisplayId);
|
||||
floorplan.startTransaction('Change Scale');
|
||||
switch (sign) {
|
||||
case '-': floorplan.scale -= .1; break;
|
||||
case '+': floorplan.scale += .1; break;
|
||||
}
|
||||
floorplan.scale = parseFloat((Math.round(floorplan.scale / .1) * .1).toFixed(2));
|
||||
var scale = (floorplan.scale * 100).toFixed(2);
|
||||
el.innerHTML = 'Scale: ' + scale + '%';
|
||||
floorplan.commitTransaction('Change Scale');
|
||||
}
|
||||
|
||||
// Change edge length of the grid based on input
|
||||
FloorplanUI.prototype.changeGridSize = function () {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change grid size");
|
||||
var el = document.getElementById(this.state.windows.optionsWindow.gridSizeInputId); var input;
|
||||
if (!isNaN(el.value) && el.value != null && el.value != '' && el.value != undefined && el.value > 1) input = parseFloat(el.value);
|
||||
else {
|
||||
el.value = floorplan.convertPixelsToUnits(10); // if bad input given, revert to 20cm (10px) or unit equivalent
|
||||
input = parseFloat(el.value);
|
||||
}
|
||||
input = floorplan.convertUnitsToPixels(input);
|
||||
floorplan.grid.gridCellSize = new go.Size(input, input);
|
||||
floorplan.toolManager.draggingTool.gridCellSize = new go.Size(input, input);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "gridSize", input);
|
||||
floorplan.commitTransaction("change grid size");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
FloorplanUI.prototype.changeUnitsConversionFactor = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var val = document.getElementById(this.state.windows.optionsWindow.unitsConversionFactorInputId).value;
|
||||
if (isNaN(val) || !val || val == undefined) return;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.model.set(floorplan.model.modelData, "unitsConversionFactor", val);
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Search through all elements in the furniture palette (useful for a palette with many furniture nodes)
|
||||
FloorplanUI.prototype.searchFurniture = function () {
|
||||
var ui = this;
|
||||
var floorplan = this.floorplan;
|
||||
var furniturePaletteId = ui.state.windows.palettesWindow.furniturePaletteId;
|
||||
var str = document.getElementById(ui.state.windows.palettesWindow.furnitureSearchInputId).value;
|
||||
var furniturePalette = window.furniturePalette;
|
||||
/*var furniturePalette = null;
|
||||
for (var i = 0; i < floorplan.palettes.length; i++) {
|
||||
var palette = floorplan.palettes[i];
|
||||
if (palette.div.id == furniturePaletteId) {
|
||||
furniturePalette = floorplan.palettes[i];
|
||||
}
|
||||
}*/
|
||||
if (ui.furnitureData == null) ui.furnitureData = furniturePalette.model.nodeDataArray;
|
||||
var items = furniturePalette.model.nodeDataArray.slice();
|
||||
if (str !== null && str !== undefined && str !== "") {
|
||||
for (var i = 0; i < items.length; i += 0) {
|
||||
var item = items[i];
|
||||
if (item.type.toLowerCase().indexOf(str.toLowerCase()) === -1) {
|
||||
items.splice(i, 1);
|
||||
}
|
||||
else i++;
|
||||
}
|
||||
furniturePalette.model.nodeDataArray = items;
|
||||
}
|
||||
else furniturePalette.model.nodeDataArray = ui.furnitureData;
|
||||
furniturePalette.updateAllRelationshipsFromData();
|
||||
}
|
||||
|
||||
/*
|
||||
* Change the "checked" value of checkboxes in the Options Menu, and to have those changes reflected in app behavior / model data
|
||||
* @param {String} id The ID of the changed checkbox
|
||||
*/
|
||||
FloorplanUI.prototype.checkboxChanged = function (id) {
|
||||
var floorplan = this.floorplan;
|
||||
var checkboxes = this.state.windows.optionsWindow.checkboxes;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change preference");
|
||||
var element = document.getElementById(id);
|
||||
switch (id) {
|
||||
case checkboxes.showGridCheckboxId: {
|
||||
floorplan.grid.visible = element.checked;
|
||||
floorplan.model.modelData.preferences.showGrid = element.checked;
|
||||
break;
|
||||
}
|
||||
case checkboxes.gridSnapCheckboxId: {
|
||||
floorplan.toolManager.draggingTool.isGridSnapEnabled = element.checked;
|
||||
floorplan.model.modelData.preferences.gridSnap = element.checked;
|
||||
break;
|
||||
}
|
||||
case checkboxes.wallGuidelinesCheckboxId: floorplan.model.modelData.preferences.showWallGuidelines = element.checked; break;
|
||||
case checkboxes.wallLengthsCheckboxId: floorplan.model.modelData.preferences.showWallLengths = element.checked; floorplan.updateWallDimensions(); break;
|
||||
case checkboxes.wallAnglesCheckboxId: floorplan.model.modelData.preferences.showWallAngles = element.checked; floorplan.updateWallAngles(); break;
|
||||
case checkboxes.smallWallAnglesCheckboxId: floorplan.model.modelData.preferences.showOnlySmallWallAngles = element.checked; floorplan.updateWallAngles(); break;
|
||||
}
|
||||
floorplan.commitTransaction("change preference");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Adjust units based on the selected radio button in the Options Menu
|
||||
FloorplanUI.prototype.changeUnits = function() {
|
||||
var floorplan = this.floorplan;
|
||||
floorplan.startTransaction("set units");
|
||||
var prevUnits = floorplan.model.modelData.units;
|
||||
var radios = document.forms[this.state.windows.optionsWindow.unitsFormId].elements[this.state.windows.optionsWindow.unitsFormName];
|
||||
for (var i = 0; i < radios.length; i++) {
|
||||
if (radios[i].checked) {
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", radios[i].id);
|
||||
}
|
||||
}
|
||||
var units = floorplan.model.modelData.units;
|
||||
switch (units) {
|
||||
case 'centimeters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'cm'); break;
|
||||
case 'meters': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'm'); break;
|
||||
case 'feet': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'ft'); break;
|
||||
case 'inches': floorplan.model.setDataProperty(floorplan.model.modelData, "unitsAbbreviation", 'in'); break;
|
||||
}
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
// update all units boxes with new units
|
||||
var unitAbbrevInputs = document.getElementsByClassName(this.state.unitsBoxClass);
|
||||
for (var i = 0; i < unitAbbrevInputs.length; i++) {
|
||||
unitAbbrevInputs[i].value = unitsAbbreviation;
|
||||
}
|
||||
var unitInputs = document.getElementsByClassName(this.state.unitsInputClass);
|
||||
for (var i = 0; i < unitInputs.length; i++) {
|
||||
var input = unitInputs[i];
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", prevUnits);
|
||||
var value = floorplan.convertUnitsToPixels(input.value);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, "units", units)
|
||||
value = floorplan.convertPixelsToUnits(value);
|
||||
input.value = value;
|
||||
}
|
||||
if (floorplan.selection.count === 1) this.setSelectionInfo(floorplan.selection.first()); // reload node info measurements according to new units
|
||||
floorplan.commitTransaction("set units");
|
||||
}
|
||||
|
||||
/*
|
||||
* Set current tool (selecting/dragging or wallbuilding/reshaping)
|
||||
* @param {String} string Informs what behavior to switch to. Accepted values: "dragging", "wallbuilding"
|
||||
*/
|
||||
FloorplanUI.prototype.setBehavior = function (string) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var wallBuildingTool = floorplan.toolManager.mouseDownTools.elt(0);
|
||||
var wallReshapingTool = floorplan.toolManager.mouseDownTools.elt(3);
|
||||
// style the current tool HTML button accordingly
|
||||
var elements = document.getElementsByClassName(this.state.setBehaviorClass);
|
||||
for (var i = 0; i < elements.length; i++) {
|
||||
var el = elements[i];
|
||||
if (el.id === string + "Button") el.style.backgroundColor = '#4b545f';
|
||||
else el.style.backgroundColor = '#bbbbbb';
|
||||
}
|
||||
var wallThicknessBox = document.getElementById(this.state.wallThicknessBoxId)
|
||||
if (string === 'wallBuilding') {
|
||||
wallBuildingTool.isEnabled = true;
|
||||
wallReshapingTool.isEnabled = false;
|
||||
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("change wallThickness");
|
||||
// create walls with wallThickness in input box
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', parseFloat(document.getElementById(ui.state.wallThicknessInputId).value));
|
||||
var wallThickness = floorplan.model.modelData.wallThickness;
|
||||
if (isNaN(wallThickness)) floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', 5);
|
||||
else {
|
||||
var width = floorplan.convertUnitsToPixels(wallThickness);
|
||||
floorplan.model.setDataProperty(floorplan.model.modelData, 'wallThickness', width);
|
||||
}
|
||||
floorplan.commitTransaction("change wallThickness");
|
||||
floorplan.skipsUndoManager = false;
|
||||
wallThicknessBox.style.visibility = 'visible';
|
||||
wallThicknessBox.style.display = 'inline-block';
|
||||
ui.setDiagramHelper("Click and drag on the diagram to draw a wall (hold SHIFT for 45 degree angles)");
|
||||
}
|
||||
if (string === 'dragging') {
|
||||
wallBuildingTool.isEnabled = false;
|
||||
wallReshapingTool.isEnabled = true;
|
||||
wallThicknessBox.style.visibility = 'hidden';
|
||||
wallThicknessBox.style.display = 'none';
|
||||
}
|
||||
// clear resize adornments on walls/windows, if there are any
|
||||
floorplan.nodes.iterator.each(function (n) { n.clearAdornments(); })
|
||||
floorplan.clearSelection();
|
||||
}
|
||||
|
||||
/*
|
||||
* Populating UI Windows from Floorplan data:
|
||||
* Update UI, Update Statistics, Fill Rows With Nodes, Set Selection Info, Set Color, Set Height, Set Width, Apply Selection Changes
|
||||
*/
|
||||
|
||||
// Update the UI properly in accordance with model.modelData (called only when a new floorplan is loaded or created)
|
||||
FloorplanUI.prototype.updateUI = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var modelData = floorplan.model.modelData;
|
||||
var checkboxes = this.state.windows.optionsWindow.checkboxes;
|
||||
if (floorplan.floorplanUI) floorplan.floorplanUI.changeUnits();
|
||||
document.getElementById(this.state.wallThicknessInputId).value = floorplan.convertPixelsToUnits(modelData.wallThickness);
|
||||
// update options GUI based on floorplan.model.modelData.preferences
|
||||
var preferences = modelData.preferences;
|
||||
document.getElementById(checkboxes.showGridCheckboxId).checked = preferences.showGrid;
|
||||
document.getElementById(checkboxes.gridSnapCheckboxId).checked = preferences.gridSnap;
|
||||
document.getElementById(checkboxes.wallGuidelinesCheckboxId).checked = preferences.showWallGuidelines;
|
||||
document.getElementById(checkboxes.wallLengthsCheckboxId).checked = preferences.showWallLengths;
|
||||
document.getElementById(checkboxes.wallAnglesCheckboxId).checked = preferences.showWallAngles;
|
||||
document.getElementById(checkboxes.smallWallAnglesCheckboxId).checked = preferences.showOnlySmallWallAngles;
|
||||
}
|
||||
|
||||
// Update all statistics in Statistics Window - called when a Floorplan's model is changed
|
||||
FloorplanUI.prototype.updateStatistics = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var statsWindow = this.state.windows.statisticsWindow;
|
||||
var element = document.getElementById(statsWindow.textDivId);
|
||||
if (element) {
|
||||
element.innerHTML = "<div class='row'><div class='col-2' style='height: 165px; overflow: auto;'> Item Types <table id='" + statsWindow.numsTableId + "'></table></div><div class='col-2'> Totals <table id='totalsTable'></table></div></div>";
|
||||
// fill Item Types table with node type/count of all nodes in diagram
|
||||
var numsTable = document.getElementById(statsWindow.numsTableId);
|
||||
|
||||
// get all palette nodes associated with this Floorplan
|
||||
var palettes = floorplan.palettes;
|
||||
var allPaletteNodes = [];
|
||||
for (var i = 0; i < palettes.length; i++) {
|
||||
allPaletteNodes = allPaletteNodes.concat(palettes[i].model.nodeDataArray);
|
||||
}
|
||||
|
||||
for (var i = 0; i < allPaletteNodes.length; i++) {
|
||||
var type = allPaletteNodes[i].type;
|
||||
var num = floorplan.findNodesByExample({ type: type }).count;
|
||||
if (num > 0) // only display data for nodes that exist on the diagram
|
||||
numsTable.innerHTML += "<tr class='data'> <td style='float: left;'>" + type + "</td> <td style='float: right;'> " + num + "</td></tr>";
|
||||
}
|
||||
// fill Totals table with lengths of all walls
|
||||
totalsTable = document.getElementById('totalsTable');
|
||||
var walls = floorplan.findNodesByExample({ category: "WallGroup" });
|
||||
var totalLength = 0;
|
||||
walls.iterator.each(function (wall) {
|
||||
var wallLength = Math.sqrt(wall.data.startpoint.distanceSquaredPoint(wall.data.endpoint));
|
||||
totalLength += wallLength;
|
||||
});
|
||||
totalLength = floorplan.convertPixelsToUnits(totalLength).toFixed(2);
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
totalsTable.innerHTML += "<tr class='data'><td style='float: left;'>Wall Lengths</td><td style='float: right;'>" + totalLength + unitsAbbreviation + "</td></tr>";
|
||||
}
|
||||
}
|
||||
|
||||
/* Helper function for setSelectionInfo(); displays all nodes in a given set in a given 1 or 2 rows in a given HTML element
|
||||
* @param {Iterable | Array} iterator A iterable collection of nodes to display in rows
|
||||
* @param {String} element The ID of the HTML element to fill with this data
|
||||
* @param {String} selectedKey The key of the currently selected node -- this node's name will be styled differently in the rows
|
||||
*/
|
||||
// TODO some repetitive code here
|
||||
FloorplanUI.prototype.fillRowsWithNodes = function(iterator, element, selectedKey) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var arr = [];
|
||||
if (iterator.constructor !== Array) iterator.each(function (p) { arr.push(p); });
|
||||
else arr = iterator;
|
||||
|
||||
// helper
|
||||
function makeOnClick(key) {
|
||||
return ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + "'" + key + "'" + '))';
|
||||
}
|
||||
|
||||
for (var i = 0; i < arr.length; i += 2) {
|
||||
if (arr[i].data === null) { this.setSelectionInfo('Nothing selected'); return; }
|
||||
var key1 = arr[i].data.key; // keys used to locate the node if clicked on...
|
||||
var name1 = (arr[i].data.caption !== "MultiPurposeNode") ? arr[i].data.caption : arr[i].data.text; // ... names are editable, so users can distinguish between nodes
|
||||
// if there are two nodes for this row...
|
||||
if (arr[i + 1] != undefined && arr[i + 1] != null) {
|
||||
var key2 = arr[i + 1].data.key;
|
||||
var name2 = (arr[i + 1].data.caption !== "MultiPurposeNode") ? arr[i + 1].data.caption : arr[i + 1].data.text;
|
||||
// if there's a non-null selectedKey, highlight the selected node in the list
|
||||
if (key1 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
|
||||
else if (key2 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable"' + 'onclick="' + makeOnClick(key1) + '">' + name1 +
|
||||
'</p></div><div class="col-2"><p class="data clickable"' + 'onclick="' + makeOnClick(key2) + '">' + name2 + '</p></div></div>';
|
||||
}
|
||||
// if there's only one node for this row...
|
||||
else {
|
||||
if (key1 === selectedKey) element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable selectedKey" onclick="' + makeOnClick(key1) + '">' + name1 + '</p></div></div>';
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p class="data clickable" onclick="' + makeOnClick(key1) + '">' + name1 + '</p></div></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Displays dynamic, editable info about selection in Selection Info window (height/length, width, name, group info, color, etc.)
|
||||
* @param {Node | String} node Can be: Reference to Node / Group, go.Node/go.Group key, "Selection" (indicating multi-selection), "Nothing selected"
|
||||
*/
|
||||
FloorplanUI.prototype.setSelectionInfo = function(node) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
if (node instanceof go.GraphObject) node = node.part;
|
||||
var selectionInfoWindow = this.state.windows.selectionInfoWindow;
|
||||
var element = document.getElementById(selectionInfoWindow.textDivId);
|
||||
var state = this.state;
|
||||
var infoWindow = document.getElementById(selectionInfoWindow.id);
|
||||
if (element === null || infoWindow === null) return;
|
||||
if (node === 'Nothing selected' || node.layer === null || node === null) { element.innerHTML = '<p>' + node + '</p>'; return; }
|
||||
|
||||
// if there are multiple nodes selected, show all their names, allowing user to click on the node they want
|
||||
if (node === 'Selection: ') {
|
||||
var selectionIterator = floorplan.selection.iterator; var arr = [];
|
||||
element.innerHTML = '<p id="name"> Selection (' + selectionIterator.count + ' items selected): </p>';
|
||||
this.fillRowsWithNodes(selectionIterator, element, null);
|
||||
infoWindow.style.height = document.getElementById(selectionInfoWindow.textDivId).offsetHeight + document.getElementById(selectionInfoWindow.handleId).offsetHeight + 5 + 'px';
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO clean this to be usable by a more general template scheme
|
||||
// if we have one node selected, gather pertinent information for that node....
|
||||
floorplan.select(node);
|
||||
var name = ''; var length; var width; var nodeGroupCount = 0; var notes = node.data.notes;
|
||||
// get node name
|
||||
if (node.category === 'MultiPurposeNode') name = node.data.text;
|
||||
else name = node.data.caption;
|
||||
// get node height / width / length (dependent on node category)
|
||||
// Wall Groups
|
||||
if (node.category === 'WallGroup') {
|
||||
length = floorplan.convertPixelsToUnits(Math.sqrt(node.data.startpoint.distanceSquared(node.data.endpoint.x, node.data.endpoint.y))).toFixed(2); // wall length
|
||||
thickness = floorplan.convertPixelsToUnits(node.data.thickness).toFixed(2); // wall thickness
|
||||
}
|
||||
// Wall Parts (i.e. Door / Window Nodes)
|
||||
else if (node.category === 'DoorNode' || node.category === "WindowNode") {
|
||||
length = floorplan.convertPixelsToUnits(node.data.length).toFixed(2);
|
||||
}
|
||||
// Generic Groups
|
||||
else if (node.data.isGroup && node.category !== "WallGroup") {
|
||||
height = floorplan.convertPixelsToUnits(node.actualBounds.height).toFixed(2);
|
||||
width = floorplan.convertPixelsToUnits(node.actualBounds.width).toFixed(2);
|
||||
}
|
||||
// Furniture Nodes
|
||||
else {
|
||||
height = floorplan.convertPixelsToUnits(node.data.height).toFixed(2);
|
||||
width = floorplan.convertPixelsToUnits(node.data.width).toFixed(2);
|
||||
}
|
||||
// get node group info
|
||||
if (node.containingGroup != null && node.containingGroup != undefined) {
|
||||
var nodeGroupParts = node.containingGroup.memberParts;
|
||||
nodeGroupCount = nodeGroupParts.count;
|
||||
}
|
||||
if (node.data.isGroup) {
|
||||
var nodeGroupParts = node.memberParts;
|
||||
nodeGroupCount = nodeGroupParts.count;
|
||||
}
|
||||
var unitsAbbreviation = floorplan.model.modelData.unitsAbbreviation;
|
||||
|
||||
// display information in the selection info window
|
||||
element.innerHTML = '<p id="name" >Name: ' + '<input id="nameInput" class="nameNotesInput" value="' + name + '"/>' + '</p>'; // name
|
||||
element.innerHTML += "<p>Notes: <textarea id='"+ selectionInfoWindow.notesTextareaId +"' class='nameNotesInput' >" + notes + "</textarea></p>"; // notes
|
||||
// display color as a color picker element (furniture nodes only)
|
||||
if (!node.data.isGroup && node.data.category !== "DoorNode" && node.data.category !== "WindowNode") {
|
||||
element.innerHTML += '<p>Color: <input type="color" id="'+ selectionInfoWindow.colorPickerId +'" value="' + node.data.color + '" name="selectionColor"></input></p>';
|
||||
}
|
||||
|
||||
// display ONLY "Length" input (Door / Window Nodes only) (this is still technically the "width input" as far as IDs are concerned)
|
||||
if (node.category === "DoorNode" || node.category === "WindowNode") element.innerHTML += '<div class="row"><p id="'+ selectionInfoWindow.widthLabelId +'" class="data">Length: <br/><input id = "' + selectionInfoWindow.widthInputId + '" class = "dimensionsInput" name = "width" value = "' + length + '"/>'
|
||||
+ '<input id="widthUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p>';
|
||||
// for walls "width" is displayed as "length", "height" is displayed as "Thickness"
|
||||
else if (node.category === 'WallGroup') {
|
||||
element.innerHTML += '<div class="row"><div class="col-2"><p id="' + selectionInfoWindow.heightLabelId + '" class="data">Thickness: <br/><input id ="' + selectionInfoWindow.heightInputId + '" class = "dimensionsInput" name = "height" value = "' + thickness
|
||||
+ '"/><input id="heightUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p> ' + '</div><div class="col-2"><p class="data">Length: <br/><input id="' + selectionInfoWindow.widthInputId + '" class="dimensionsInput" value = "'
|
||||
+ length + '"/><input id="widthUnits" class="' + state.unitsBoxClass + '" value="' + unitsAbbreviation + '" disabled/></p>' + '</p></div></div>';
|
||||
}
|
||||
// display editable properties height and width (non Door / Window nodes)
|
||||
else element.innerHTML += '<div class="row"><div class="col-2"><p id="' + selectionInfoWindow.heightLabelId + '" class="data">Height: <br/><input id ="' + selectionInfoWindow.heightInputId + '" class = "dimensionsInput" name = "height" value = "' + height
|
||||
+ '"/><input id="heightUnits" class="' + state.unitsBoxClass + '" value=' + unitsAbbreviation + ' disabled/></p> ' + '</div><div class="col-2"><p class="data">Width: <br/><input id="' + selectionInfoWindow.widthInputId + '" class="dimensionsInput" value = "'
|
||||
+ width + '"/><input id="widthUnits" class="' + state.unitsBoxClass + '" value="' + unitsAbbreviation + '" disabled/></p>' + '</p></div></div>';
|
||||
|
||||
// do not allow height or width adjustment for group info
|
||||
if (node.data.isGroup && node.category !== "WallGroup") {
|
||||
document.getElementById(selectionInfoWindow.heightInputId).disabled = true;
|
||||
document.getElementById(selectionInfoWindow.widthInputId).disabled = true;
|
||||
}
|
||||
|
||||
// "Apply Changes" button
|
||||
element.innerHTML += '<div class="row"> <button id="applySelectionChanges" onClick="' + this.name + '.applySelectionChanges()">Apply Changes</button></div>';
|
||||
|
||||
// display group info for standard groups, wallParts for walls
|
||||
var groupName = null; var groupKey = null; var selectedKey = "";
|
||||
if (node.data.isGroup === true) {
|
||||
groupName = node.data.caption;
|
||||
groupKey = node.data.key;
|
||||
selectedKey = "selectedKey"; // the 'group' node is selected; make it blue to show this
|
||||
}
|
||||
if (node.containingGroup !== null) {
|
||||
groupName = node.containingGroup.data.caption;
|
||||
groupKey = node.containingGroup.data.key;
|
||||
}
|
||||
if (groupName !== null) {
|
||||
groupKey = "'" + groupKey + "'";
|
||||
element.innerHTML += '<div class="row data" id="' + selectionInfoWindow.nodeGroupInfoId +'"> <span class="clickable ' + selectedKey + '" onclick="' + ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + groupKey + '))">' +
|
||||
groupName + '</span> Info (' + nodeGroupCount + ' member(s) in <span class="clickable ' + selectedKey + '" onclick="' + ui.name + '.setSelectionInfo(' + ui.floorplanName + '.findPartForKey(' + groupKey + '))">' + groupName + '</span>) </div>';
|
||||
if (nodeGroupCount != 0) ui.fillRowsWithNodes(nodeGroupParts, document.getElementById(selectionInfoWindow.nodeGroupInfoId), node.data.key);
|
||||
}
|
||||
|
||||
var nameInput = document.getElementById(selectionInfoWindow.nameInputId);
|
||||
if (!floorplan.isReadOnly) {
|
||||
// dynamically adjust name of selected node based on user input
|
||||
nameInput.addEventListener('input', function (e) {
|
||||
var value = nameInput.value;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("rename node");
|
||||
if (value === null || value === "" || value === undefined) { floorplan.commitTransaction("rename node"); return; }
|
||||
floorplan.model.setDataProperty(node.data, "caption", value);
|
||||
floorplan.model.setDataProperty(node.data, "text", value); // if node is a multi purpose node, update the text on it
|
||||
floorplan.commitTransaction("rename node");
|
||||
floorplan.skipsUndoManager = false;
|
||||
});
|
||||
|
||||
// dynamically adjust notes of selected node based on user input
|
||||
var notesTextarea = document.getElementById(selectionInfoWindow.notesTextareaId);
|
||||
notesTextarea.addEventListener('input', function (e) {
|
||||
var value = notesTextarea.value;
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("edit node notes");
|
||||
if (value === null || value === undefined) return;
|
||||
floorplan.model.setDataProperty(node.data, "notes", value);
|
||||
floorplan.commitTransaction("edit node notes");
|
||||
floorplan.skipsUndoManager = false;
|
||||
});
|
||||
infoWindow.style.height = document.getElementById(selectionInfoWindow.textDivId).offsetHeight + document.getElementById(selectionInfoWindow.handleId).offsetHeight + 5 + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for fill color of the current selection
|
||||
FloorplanUI.prototype.setColor = function () {
|
||||
var floorplan = this.floorplan;
|
||||
var node = floorplan.selection.first();
|
||||
var colorPicker = document.getElementById(this.state.windows.selectionInfoWindow.colorPickerId);
|
||||
if (colorPicker !== null) {
|
||||
floorplan.startTransaction("recolor node");
|
||||
floorplan.model.setDataProperty(node.data, "color", colorPicker.value);
|
||||
floorplan.model.setDataProperty(node.data, "stroke", invertColor(colorPicker.value))
|
||||
floorplan.commitTransaction("recolor node");
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for height of the currently selected node (also handles door length for doors, wall length for walls)
|
||||
FloorplanUI.prototype.setHeight = function () {
|
||||
var heightInput = document.getElementById(this.state.windows.selectionInfoWindow.heightInputId);
|
||||
if (heightInput) {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var node = floorplan.selection.first();
|
||||
var value = parseFloat(floorplan.convertUnitsToPixels(heightInput.value));
|
||||
if (isNaN(value)) {
|
||||
alert("Please enter a valid number");
|
||||
ui.setSelectionInfo(node, floorplan);
|
||||
return;
|
||||
}
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("resize node");
|
||||
if (!floorplan.isReadOnly) {
|
||||
// Case: Furniture Nodes and Window Nodes; basic height adjustment
|
||||
if (node.category !== 'WallGroup' && node.category !== 'DoorNode') {
|
||||
floorplan.model.setDataProperty(node.data, "height", value);
|
||||
}
|
||||
// Case: Wall Groups; set wall's data.strokeWidth
|
||||
else if (node.category === 'WallGroup') {
|
||||
floorplan.model.setDataProperty(node.data, "thickness", value);
|
||||
node.memberParts.iterator.each(function (part) {
|
||||
if (part.category === 'DoorNode') floorplan.model.setDataProperty(part.data, "doorOpeningHeight", value);
|
||||
if (part.category === 'WindowNode') floorplan.model.setDataProperty(part.data, "height", value);
|
||||
});
|
||||
}
|
||||
// Note: Door Nodes are purposefully unaccounted for, as width (length) adjustment (in setWidth()) also adjusts node height
|
||||
}
|
||||
floorplan.commitTransaction("resize node");
|
||||
floorplan.updateWallDimensions(floorplan);
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Triggered by "Apply Changes"; set model data for width of the currently selected node
|
||||
FloorplanUI.prototype.setWidth = function() {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
var node = floorplan.selection.first();
|
||||
var widthInput = document.getElementById(this.state.windows.selectionInfoWindow.widthInputId);
|
||||
if (widthInput === null) return;
|
||||
var value = parseFloat(floorplan.convertUnitsToPixels(widthInput.value));
|
||||
if (isNaN(value)) {
|
||||
alert("Please enter a valid number");
|
||||
ui.setSelectionInfo(node, floorplan);
|
||||
return;
|
||||
}
|
||||
floorplan.skipsUndoManager = true;
|
||||
floorplan.startTransaction("resize node");
|
||||
if (!floorplan.isReadOnly) {
|
||||
// Case: Window / Door Nodes (part.width is part.data.length), keeps windows and doors within wall boundaries (and surrounding wall part boundaries)
|
||||
if (node.category === 'WindowNode' || node.category === "DoorNode") {
|
||||
var wall = floorplan.findPartForKey(node.data.group);
|
||||
var loc = node.location.copy();
|
||||
// constrain max width "value" by the free stretch on the wall "node" is in
|
||||
if (wall !== null) {
|
||||
var containingStretch = getWallPartStretch(node);
|
||||
var stretchLength = Math.sqrt(containingStretch.point1.distanceSquaredPoint(containingStretch.point2));
|
||||
if (stretchLength < value) {
|
||||
value = stretchLength;
|
||||
loc = new go.Point((containingStretch.point1.x + containingStretch.point2.x) / 2,
|
||||
(containingStretch.point1.y + containingStretch.point2.y) / 2);
|
||||
}
|
||||
}
|
||||
floorplan.model.setDataProperty(node.data, "length", value);
|
||||
node.location = loc;
|
||||
floorplan.updateWallDimensions();
|
||||
}
|
||||
// Case: Wall Groups; wall length adjustment; do not allow walls to be shorter than the distance between their fathest apart wallParts
|
||||
else if (node.category === "WallGroup") {
|
||||
var sPt = node.data.startpoint.copy();
|
||||
var ePt = node.data.endpoint.copy();
|
||||
var angle = sPt.directionPoint(ePt);
|
||||
|
||||
var midPoint = new go.Point(((sPt.x + ePt.x) / 2), ((sPt.y + ePt.y) / 2));
|
||||
var newEpt = new go.Point((midPoint.x + (value / 2)), midPoint.y);
|
||||
var newSpt = new go.Point((midPoint.x - (value / 2)), midPoint.y);
|
||||
newEpt.offset(-midPoint.x, -midPoint.y).rotate(angle).offset(midPoint.x, midPoint.y);
|
||||
newSpt.offset(-midPoint.x, -midPoint.y).rotate(angle).offset(midPoint.x, midPoint.y);
|
||||
|
||||
// Edge Case 1: The user has input a length shorter than the edge wallPart's endpoints allow
|
||||
// find the endpoints of the wallparts closest to the endpoints of the wall
|
||||
var closestPtToSpt = null; var farthestPtFromSpt;
|
||||
var closestDistToSpt = Number.MAX_VALUE; var farthestDistFromSpt = 0;
|
||||
node.memberParts.iterator.each(function (wallPart) {
|
||||
var endpoints = getWallPartEndpoints(wallPart);
|
||||
var endpoint1 = endpoints[0];
|
||||
var endpoint2 = endpoints[1];
|
||||
var distance1 = Math.sqrt(endpoint1.distanceSquaredPoint(sPt));
|
||||
var distance2 = Math.sqrt(endpoint2.distanceSquaredPoint(sPt));
|
||||
|
||||
if (distance1 < closestDistToSpt) {
|
||||
closestDistToSpt = distance1;
|
||||
closestPtToSpt = endpoint1;
|
||||
} if (distance1 > farthestDistFromSpt) {
|
||||
farthestDistFromSpt = distance1;
|
||||
farthestPtFromSpt = endpoint1;
|
||||
} if (distance2 < closestDistToSpt) {
|
||||
closestDistToSpt = distance2;
|
||||
closestPtToSpt = endpoint2;
|
||||
} if (distance2 > farthestDistFromSpt) {
|
||||
farthestDistFromSpt = distance2;
|
||||
farthestPtFromSpt = endpoint2;
|
||||
}
|
||||
});
|
||||
|
||||
if (closestPtToSpt !== null) {
|
||||
// if the proposed length is smaller than the minDistance, set wall length to minDistance
|
||||
var proposedDistance = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
var minDistance = Math.sqrt(closestPtToSpt.distanceSquaredPoint(farthestPtFromSpt));
|
||||
if (proposedDistance < minDistance) {
|
||||
newSpt = closestPtToSpt;
|
||||
newEpt = farthestPtFromSpt;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Edge Case 2: The new wall endpoints constructed based on user input do not generate a wall too short for the wall's edge wallPart's endpoints;
|
||||
* however, there is/are a/some wallPart(s) that do not fit along the new wall endpoints (due to midpoint construction)
|
||||
* if a wallPart endpoint is outside the line created by newSpt and newEpt, adjust the endpoints accordingly
|
||||
*/
|
||||
var farthestPtFromWallPt = null; var farthestFromWallPtDist = 0;
|
||||
node.memberParts.iterator.each(function (part) {
|
||||
var endpoints = getWallPartEndpoints(part);
|
||||
// check for endpoints of wallParts not along the line segment made by newSpt and newEpt
|
||||
for (var i = 0; i < endpoints.length; i++) {
|
||||
var point = endpoints[i];
|
||||
var distanceToStartPoint = parseFloat(Math.sqrt(point.distanceSquaredPoint(newSpt)).toFixed(2));
|
||||
var distanceToEndPoint = parseFloat(Math.sqrt(point.distanceSquaredPoint(newEpt)).toFixed(2));
|
||||
var wallLength = parseFloat(Math.sqrt(newSpt.distanceSquaredPoint(newEpt)).toFixed(2));
|
||||
if ((distanceToStartPoint + distanceToEndPoint).toFixed(2) !== wallLength.toFixed(2)) {
|
||||
var testDistance = Math.sqrt(point.distanceSquaredPoint(newSpt));
|
||||
if (testDistance > farthestFromWallPtDist) {
|
||||
farthestFromWallPtDist = testDistance;
|
||||
farthestPtFromWallPt = point;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// if a wallPart endpoint is outside the wall, adjust the endpoints of the wall to accomodate it
|
||||
if (farthestPtFromWallPt !== null) {
|
||||
var distance = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
if (farthestPtFromWallPt.distanceSquaredPoint(newSpt) < farthestPtFromWallPt.distanceSquaredPoint(newEpt)) {
|
||||
newSpt = farthestPtFromWallPt;
|
||||
var totalLength = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
newEpt = new go.Point(newSpt.x + ((distance / totalLength) * (newEpt.x - newSpt.x)),
|
||||
newSpt.y + ((distance / totalLength) * (newEpt.y - newSpt.y)));
|
||||
} else {
|
||||
newEpt = farthestPtFromWallPt;
|
||||
var totalLength = Math.sqrt(newSpt.distanceSquaredPoint(newEpt));
|
||||
newSpt = new go.Point(newEpt.x + ((distance / totalLength) * (newSpt.x - newEpt.x)),
|
||||
newEpt.y + ((distance / totalLength) * (newSpt.y - newEpt.y)));
|
||||
}
|
||||
}
|
||||
|
||||
floorplan.model.setDataProperty(node.data, "startpoint", newSpt);
|
||||
floorplan.model.setDataProperty(node.data, "endpoint", newEpt);
|
||||
floorplan.updateWall(node);
|
||||
}
|
||||
// Case: Standard / Multi-Purpose Nodes; basic width ajustment
|
||||
else floorplan.model.setDataProperty(node.data, "width", value);
|
||||
}
|
||||
floorplan.commitTransaction("resize node");
|
||||
floorplan.skipsUndoManager = false;
|
||||
}
|
||||
|
||||
// Set height, width, and color of the selection based on user input in the Selection Info Window
|
||||
FloorplanUI.prototype.applySelectionChanges = function() {
|
||||
var floorplan = this.floorplan;
|
||||
var ui = this;
|
||||
this.setHeight();
|
||||
this.setWidth();
|
||||
this.setColor();
|
||||
ui.setSelectionInfo(floorplan.selection.first(), floorplan);
|
||||
}
|
||||
@@ -0,0 +1,377 @@
|
||||
/*
|
||||
* Copyright (C) 1998-2020 by Northwoods Software Corporation
|
||||
* All Rights Reserved.
|
||||
*
|
||||
* Floorplanner Constants
|
||||
*/
|
||||
|
||||
// The diagram model data for the default floorplanner
|
||||
DEFAULT_MODEL_DATA = {
|
||||
"class": "go.GraphLinksModel",
|
||||
"modelData": { "units": "centimeters", "unitsAbbreviation": "cm", "unitsConversionFactor": 2, "gridSize": 10, "wallThickness": 5, "preferences": { "showWallGuidelines": true, "showWallLengths": true, "showWallAngles": true, "showOnlySmallWallAngles": true, "showGrid": true, "gridSnap": true } },
|
||||
"nodeDataArray": [
|
||||
{ "key": "wall", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": 240 }, "endpoint": { "class": "go.Point", "x": -430, "y": -240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall3", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": -240 }, "endpoint": { "class": "go.Point", "x": 260, "y": -240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall4", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": -240 }, "endpoint": { "class": "go.Point", "x": 260, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall5", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 140 }, "endpoint": { "class": "go.Point", "x": 550, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall6", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 240 }, "endpoint": { "class": "go.Point", "x": 390, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall7", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 550, "y": 140 }, "endpoint": { "class": "go.Point", "x": 550, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall8", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 390, "y": 240 }, "endpoint": { "class": "go.Point", "x": 390, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall9", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 390, "y": 400 }, "endpoint": { "class": "go.Point", "x": 550, "y": 400 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "staircase", "color": "#ffffff", "stroke": "#000000", "caption": "Staircase", "type": "Staircase", "geo": "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0", "width": 125, "height": 50, "notes": "", "loc": "430 330", "angle": 270 },
|
||||
{ "key": "staircase2", "color": "#ffffff", "stroke": "#000000", "caption": "Staircase", "type": "Staircase", "geo": "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0", "width": 125, "height": 50, "notes": "", "loc": "500 330", "angle": 270 },
|
||||
{ "key": "wall10", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -230, "y": -130 }, "endpoint": { "class": "go.Point", "x": 70, "y": -130 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall11", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 70, "y": -130 }, "endpoint": { "class": "go.Point", "x": 130, "y": -70 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall12", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 130, "y": -70 }, "endpoint": { "class": "go.Point", "x": 130, "y": 40 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall13", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 130, "y": 40 }, "endpoint": { "class": "go.Point", "x": 70, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall14", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 70, "y": 100 }, "endpoint": { "class": "go.Point", "x": -230, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall15", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -230, "y": -130 }, "endpoint": { "class": "go.Point", "x": -290, "y": -70 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall16", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -290, "y": -70 }, "endpoint": { "class": "go.Point", "x": -290, "y": 40 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall17", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -290, "y": 40 }, "endpoint": { "class": "go.Point", "x": -230, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 56, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "260 188.20000076293945", "group": "wall4", "angle": 90 },
|
||||
{ "key": "diningTable", "color": "#704332", "stroke": "#8FBCCD", "caption": "Dining Table", "type": "Dining Table", "geo": "F1 M 0 0 L 0 100 200 100 200 0 0 0 M 25 0 L 25 -10 75 -10 75 0 M 125 0 L 125 -10 175 -10 175 0 M 200 25 L 210 25 210 75 200 75 M 125 100 L 125 110 L 175 110 L 175 100 M 25 100 L 25 110 75 110 75 100 M 0 75 -10 75 -10 25 0 25", "width": 205, "height": 70.5, "notes": "", "loc": "-80 -20" },
|
||||
{ "key": "wall18", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 550, "y": -240 }, "endpoint": { "class": "go.Point", "x": 550, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall19", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": -240 }, "endpoint": { "class": "go.Point", "x": 310, "y": -310 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall20", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 310, "y": -310 }, "endpoint": { "class": "go.Point", "x": 500, "y": -310 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall21", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 500, "y": -310 }, "endpoint": { "class": "go.Point", "x": 550, "y": -240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door2", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-290 5.200000762939453", "group": "wall16", "angle": 90 },
|
||||
{ "category": "WindowNode", "key": "window", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 230, "notes": "", "loc": "-80.80000019073492 -130.00000000000006", "group": "wall10" },
|
||||
{ "category": "WindowNode", "key": "window2", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 230, "notes": "", "loc": "-80.80000019073492 100.00000000000003", "group": "wall14", "angle": 180 },
|
||||
{ "category": "WindowNode", "key": "window3", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-400 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window32", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-234.80000019073486 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window4", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-89.80000019073486 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window5", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "80.19999980926514 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window6", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "201.19999980926514 -240", "group": "wall3" },
|
||||
{ "category": "WindowNode", "key": "window7", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 175, "notes": "", "loc": "-430 -152.5", "group": "wall", "angle": 90 },
|
||||
{ "category": "WindowNode", "key": "window8", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 233, "notes": "", "loc": "-430 123.49999999999999", "group": "wall", "angle": 270 },
|
||||
{ "key": "wall32", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -430, "y": 240 }, "endpoint": { "class": "go.Point", "x": 260, "y": 240 }, "thickness": 10, "isGroup": true, "notes": "" },
|
||||
{ "category": "WindowNode", "key": "window33", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-400 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window322", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-234.80000019073486 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window42", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "-89.80000019073486 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window52", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "80.19999980926514 240", "group": "wall32" },
|
||||
{ "category": "WindowNode", "key": "window62", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 10, "length": 60, "notes": "", "loc": "201.19999980926514 240", "group": "wall32" },
|
||||
{ "key": "wall2", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 260, "y": 0 }, "endpoint": { "class": "go.Point", "x": 380, "y": 0 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door5", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "right", "notes": "", "loc": "-290 -34.79999923706055", "angle": 90, "group": "wall16" },
|
||||
{ "key": "door52", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "130 -37.79999923706055", "angle": 270, "group": "wall12" },
|
||||
{ "key": "door22", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "right", "notes": "", "loc": "130 2.200000762939453", "group": "wall12", "angle": 270 },
|
||||
{ "key": "sink", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Sink", "type": "Sink", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25", "width": 27, "height": 27, "notes": "", "loc": "361.5 110", "angle": 180, "group": -52 },
|
||||
{ "key": "shower", "color": "#b9cece", "stroke": "#463131", "caption": "Shower/Tub", "type": "Shower/Tub", "geo": "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99", "width": 57, "height": 109, "notes": "", "loc": "296 67", "group": -52 },
|
||||
{ "key": "toilet", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Toilet", "type": "Toilet", "geo": "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15", "width": 25, "height": 35, "notes": "", "loc": "350 30", "group": -52 },
|
||||
{ "key": "wall22", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 380, "y": 0 }, "endpoint": { "class": "go.Point", "x": 380, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "door3", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 36, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "380 65.20000076293945", "group": "wall22", "angle": 270 },
|
||||
{ "key": "wall23", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 430, "y": 0 }, "endpoint": { "class": "go.Point", "x": 550, "y": 0 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall24", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 430, "y": 0 }, "endpoint": { "class": "go.Point", "x": 430, "y": 140 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "isGroup": true, "key": -52, "caption": "Group", "notes": "" },
|
||||
{ "isGroup": true, "key": -53, "caption": "Group", "notes": "" },
|
||||
{ "key": "shower2", "color": "#b9cece", "stroke": "#463131", "caption": "Shower/Tub", "type": "Shower/Tub", "geo": "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99", "width": 57, "height": 109, "notes": "", "loc": "510 70", "group": -53 },
|
||||
{ "key": "toilet2", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Toilet", "type": "Toilet", "geo": "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15", "width": 25, "height": 35, "notes": "", "loc": "460 30", "group": -53 },
|
||||
{ "key": "sink2", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Sink", "type": "Sink", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25", "width": 27, "height": 27, "notes": "", "loc": "460 112", "angle": 180, "group": -53 },
|
||||
{ "key": "door32", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 36, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "430 69.20000076293945", "group": "wall24", "angle": 90 },
|
||||
{ "key": "sofaMedium", "color": "#c6a8c5", "stroke": "#39573A", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 45, "width": 90, "notes": "", "loc": "320 -40" },
|
||||
{ "key": "sofaMedium2", "color": "#c6a8c5", "stroke": "#39573A", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 45, "width": 90, "notes": "", "loc": "490 -40" },
|
||||
{ "key": "roundTable", "color": "#dadada", "stroke": "#252525", "caption": "Round Table", "type": "Round Table", "shape": "Ellipse", "width": 61, "height": 61, "notes": "", "loc": "410 -170", "group": -74 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode", "caption": "Multi Purpose Node", "color": "#ffffff", "stroke": "#000000", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Fridge", "width": 55, "height": 40, "notes": "", "loc": "342.5 -287.5" },
|
||||
{ "key": "doubleSink", "color": "#d9d9d9", "stroke": "#262626", "caption": "Double Sink", "type": "Double Sink", "geo": "F1 M0 0 L75 0 75 40 0 40 0 0 M5 7.5 L35 7.5 35 35 5 35 5 7.5 M44 7.5 L70 7.5 70 35 40 35 40 9 M15 21.25 A5 5 180 1 0 15 21.24 M50 21.25 A 5 5 180 1 0 50 21.24 M40.5 3.75 A3 3 180 1 1 40.5 3.74 M40.5 3.75 L50.5 13.75 47.5 16.5 37.5 6.75 M32.5 3.75 A 1 1 180 1 1 32.5 3.74 M 27.5 4.25 L 27.5 3.25 30.5 3.25 M 30.5 4.25 L 27.5 4.25 M44.5 3.75 A 1 1 180 1 1 44.5 3.74 M 44.35 3.25 L 47.5 3.25 47.5 4.25 M 44.35 4.25 L 47.5 4.25", "height": 27, "width": 52, "notes": "", "loc": "510 -260", "angle": 53.07333893129521 },
|
||||
{ "category": "WindowNode", "key": "window9", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "284.9054049801182 -274.8675669721655", "group": "wall19", "angle": 305.5376777919744 },
|
||||
{ "category": "WindowNode", "key": "window10", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "522.9324327288447 -277.8945941796174", "group": "wall21", "angle": 234.46232220802563 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode3", "caption": "Multi Purpose Node", "color": "#f7f9e3", "stroke": "#08061C", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Fridge", "width": 55, "height": 40, "notes": "", "loc": "342.5 -287.5" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode32", "caption": "Multi Purpose Node", "color": "#f7f9e3", "stroke": "#08061C", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Counter", "width": 55, "height": 40, "notes": "", "loc": "395 -287" },
|
||||
{ "category": "WindowNode", "key": "window11", "color": "white", "caption": "Window", "type": "Window", "shape": "Rectangle", "height": 5, "length": 60, "notes": "", "loc": "397.19999980926514 -310", "group": "wall20" },
|
||||
{ "key": "door4", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 56, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "260 -179.79999923706055", "group": "wall4", "angle": 90 },
|
||||
{ "key": "stove", "color": "#f7f9e3", "stroke": "#08061C", "caption": "Stove", "type": "Stove", "geo": "F1 M 0 0 L 0 100 100 100 100 0 0 0 M 30 15 A 15 15 180 1 0 30.01 15 M 70 15 A 15 15 180 1 0 70.01 15M 30 55 A 15 15 180 1 0 30.01 55 M 70 55 A 15 15 180 1 0 70.01 55", "width": 55, "height": 40, "notes": "", "loc": "450.22782650708155 -288" },
|
||||
{ "key": "armChair", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "410 -120", "group": -74 },
|
||||
{ "key": "armChair2", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "460 -170", "angle": 270, "group": -74 },
|
||||
{ "key": "armChair22", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "410 -220", "angle": 180, "group": -74 },
|
||||
{ "key": "armChair222", "color": "#c0c0c0", "stroke": "#3F3F3F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 32.865, "height": 32, "notes": "", "loc": "360 -170", "angle": 90, "group": -74 },
|
||||
{ "isGroup": true, "key": -74, "caption": "Group", "notes": "" },
|
||||
{ "key": "wall25", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": -240 }, "endpoint": { "class": "go.Point", "x": -300, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall252", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -160, "y": -240 }, "endpoint": { "class": "go.Point", "x": -160, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall2522", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 0, "y": -240 }, "endpoint": { "class": "go.Point", "x": 0, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall25222", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 140, "y": -240 }, "endpoint": { "class": "go.Point", "x": 140, "y": -180 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall253", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": 180 }, "endpoint": { "class": "go.Point", "x": -300, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall2523", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -160, "y": 180 }, "endpoint": { "class": "go.Point", "x": -160, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall25223", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 0, "y": 180 }, "endpoint": { "class": "go.Point", "x": 0, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall252222", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": 140, "y": 180 }, "endpoint": { "class": "go.Point", "x": 140, "y": 240 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "armChair3", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-231.93243243243245 -192.5" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-232 -220" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode25", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-230 221" },
|
||||
{ "key": "armChair35", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-230 190", "angle": 180 },
|
||||
{ "key": "armChair352", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-80 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode252", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-80 220" },
|
||||
{ "key": "armChair353", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "70 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode253", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "70 220" },
|
||||
{ "key": "armChair3532", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "200 190", "angle": 180 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2532", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "200 220" },
|
||||
{ "key": "armChair32", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "-80 -190" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode22", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "-80 -220" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode222", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "70 -220" },
|
||||
{ "key": "armChair322", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "70 -190" },
|
||||
{ "key": "armChair3222", "color": "#e1ddd0", "stroke": "#1E222F", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 29, "height": 27, "notes": "", "loc": "200 -190" },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode2222", "caption": "Multi Purpose Node", "color": "#e1ddd0", "stroke": "#1E222F", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 116, "height": 14, "notes": "", "loc": "200 -220" },
|
||||
{ "key": "sofaMedium3", "color": "#b9fde0", "stroke": "#46021F", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 27, "width": 90, "notes": "", "loc": "-410 -30", "angle": 90 },
|
||||
{ "key": "sofaMedium32", "color": "#b9fde0", "stroke": "#46021F", "caption": "Sofa", "type": "Sofa", "geo": "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35", "height": 27, "width": 90, "notes": "", "loc": "240 -20", "angle": 270 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode4", "caption": "Multi Purpose Node", "color": "#d6b196", "stroke": "#294E69", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 60, "height": 23, "notes": "", "loc": "-381.1676743184333 -190.94449856461264", "angle": 137.27258112448646 },
|
||||
{ "key": "wall26", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": -180 }, "endpoint": { "class": "go.Point", "x": -380, "y": -100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall27", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -380, "y": -100 }, "endpoint": { "class": "go.Point", "x": -430, "y": -100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall28", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -300, "y": 180 }, "endpoint": { "class": "go.Point", "x": -380, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "wall29", "category": "WallGroup", "caption": "Wall", "type": "Wall", "startpoint": { "class": "go.Point", "x": -380, "y": 100 }, "endpoint": { "class": "go.Point", "x": -430, "y": 100 }, "thickness": 5, "isGroup": true, "notes": "" },
|
||||
{ "key": "armChair4", "color": "#d6b196", "stroke": "#294E69", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 21, "height": 21, "notes": "", "loc": "-400 -210", "angle": 135 },
|
||||
{ "key": "roundTable2", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-402.5 -116.5", "text": "Plant" },
|
||||
{ "key": "armChair42", "color": "#d6b196", "stroke": "#294E69", "caption": "Arm Chair", "type": "Arm Chair", "geo": "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30", "width": 21, "height": 21, "notes": "", "loc": "-400 210", "angle": 45 },
|
||||
{ "category": "MultiPurposeNode", "key": "MultiPurposeNode42", "caption": "Multi Purpose Node", "color": "#d6b196", "stroke": "#294E69", "name": "Writable Node", "type": "Writable Node", "shape": "Rectangle", "text": "Desk", "width": 60, "height": 23, "notes": "", "loc": "-380 190", "angle": 47.002533598871146 },
|
||||
{ "key": "roundTable22", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-400 120", "text": "Plant" },
|
||||
{ "key": "roundTable222", "color": "#d6dfc8", "stroke": "#292037", "caption": "Plant", "type": "Round Table", "shape": "Ellipse", "width": 21, "height": 21, "notes": "", "loc": "-320 200", "text": "Plant" },
|
||||
{ "key": "door6", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-334.00000047683716 -145.99999952316284", "group": "wall26", "angle": 315 },
|
||||
{ "key": "door7", "category": "DoorNode", "color": "rgba(0, 0, 0, 0)", "caption": "Door", "type": "Door", "length": 40, "doorOpeningHeight": 5, "swing": "left", "notes": "", "loc": "-343.7999997138977 136.2000002861023", "group": "wall28", "angle": 225 }
|
||||
],
|
||||
"linkDataArray": []
|
||||
};
|
||||
|
||||
// UI Interaction state object for FlooplaUI
|
||||
GUI_STATE = {
|
||||
menuButtons: {
|
||||
selectionInfoWindowButtonId: "selectionInfoWindowButton",
|
||||
palettesWindowButtonId: "myPaletteWindowButton",
|
||||
overviewWindowButtonId: "myOverviewWindowButton",
|
||||
optionsWindowButtonId: "optionsWindowButton",
|
||||
statisticsWindowButtonId: "statisticsWindowButton"
|
||||
},
|
||||
windows: {
|
||||
diagramHelpDiv: {
|
||||
id: "diagramHelpDiv"
|
||||
},
|
||||
selectionInfoWindow: {
|
||||
id: "selectionInfoWindow",
|
||||
textDivId: "selectionInfoTextDiv",
|
||||
handleId: "selectionInfoWindowHandle",
|
||||
colorPickerId: "colorPicker",
|
||||
heightLabelId: "heightLabel",
|
||||
heightInputId: "heightInput",
|
||||
widthInputId: "widthInput",
|
||||
nodeGroupInfoId: "nodeGroupInfo",
|
||||
nameInputId: "nameInput",
|
||||
notesTextareaId: "notesTextarea"
|
||||
},
|
||||
palettesWindow: {
|
||||
id: "myPaletteWindow",
|
||||
furnitureSearchInputId: "furnitureSearchBar",
|
||||
furniturePaletteId: "furniturePaletteDiv"
|
||||
},
|
||||
overviewWindow: {
|
||||
id: "myOverviewWindow"
|
||||
},
|
||||
optionsWindow: {
|
||||
id: "optionsWindow",
|
||||
gridSizeInputId: "gridSizeInput",
|
||||
unitsConversionFactorInputId: "unitsConversionFactorInput",
|
||||
unitsFormId: "unitsForm",
|
||||
unitsFormName: "units",
|
||||
checkboxes: {
|
||||
showGridCheckboxId: "showGridCheckbox",
|
||||
gridSnapCheckboxId: "gridSnapCheckbox",
|
||||
wallGuidelinesCheckboxId: "wallGuidelinesCheckbox",
|
||||
wallLengthsCheckboxId: "wallLengthsCheckbox",
|
||||
wallAnglesCheckboxId: "wallAnglesCheckbox",
|
||||
smallWallAnglesCheckboxId: "smallWallAnglesCheckbox"
|
||||
},
|
||||
},
|
||||
statisticsWindow: {
|
||||
id: "statisticsWindow",
|
||||
textDivId: "statisticsWindowTextDiv",
|
||||
numsTableId: "numsTable",
|
||||
totalsTableId: "totalsTable"
|
||||
}
|
||||
},
|
||||
scaleDisplayId: "scaleDisplay",
|
||||
setBehaviorClass: "setBehavior",
|
||||
wallThicknessInputId: "wallThicknessInput",
|
||||
wallThicknessBoxId: "wallThicknessBox",
|
||||
unitsBoxClass: "unitsBox",
|
||||
unitsInputClass: "unitsInput"
|
||||
};
|
||||
|
||||
// Filesystem state object for FloorplanFilesystem
|
||||
FILESYSTEM_UI_STATE = {
|
||||
openWindowId: "openDocument",
|
||||
removeWindowId: "removeDocument",
|
||||
currentFileId: "currentFile",
|
||||
filesToRemoveListId: "filesToRemove",
|
||||
filesToOpenListId: "filesToOpen"
|
||||
};
|
||||
|
||||
// Node Data Array for Furniture Palette
|
||||
FURNITURE_NODE_DATA_ARRAY = [
|
||||
{
|
||||
category: "MultiPurposeNode",
|
||||
key: "MultiPurposeNode",
|
||||
caption: "Multi Purpose Node",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
name: "Writable Node",
|
||||
type: "Writable Node",
|
||||
shape: "Rectangle",
|
||||
text: "Write here",
|
||||
width: 60,
|
||||
height: 60,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "roundTable",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Round Table",
|
||||
type: "Round Table",
|
||||
shape: "Ellipse",
|
||||
width: 61,
|
||||
height: 61,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "armChair",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Arm Chair",
|
||||
type: "Arm Chair",
|
||||
geo: "F1 M0 0 L40 0 40 40 0 40 0 0 M10 30 L10 10 M0 0 Q8 0 10 10 M0 40 Q20 15 40 40 M30 10 Q32 0 40 0 M30 10 L30 30",
|
||||
width: 45,
|
||||
height: 45,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "sofaMedium",
|
||||
color: "#ffffff",
|
||||
stroke: "#000000",
|
||||
caption: "Sofa",
|
||||
type: "Sofa",
|
||||
geo: "F1 M0 0 L80 0 80 40 0 40 0 0 M10 35 L10 10 M0 0 Q8 0 10 10 M0 40 Q40 15 80 40 M70 10 Q72 0 80 0 M70 10 L70 35",
|
||||
height: 45,
|
||||
width: 90,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "sink",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Sink",
|
||||
type: "Sink",
|
||||
geo: "F1 M0 0 L40 0 40 40 0 40 0 0z M5 7.5 L18.5 7.5 M 21.5 7.5 L35 7.5 35 35 5 35 5 7.5 M 15 21.25 A 5 5 180 1 0 15 21.24 M23 3.75 A 3 3 180 1 1 23 3.74 M21.5 6.25 L 21.5 12.5 18.5 12.5 18.5 6.25 M15 3.75 A 1 1 180 1 1 15 3.74 M 10 4.25 L 10 3.25 13 3.25 M 13 4.25 L 10 4.25 M27 3.75 A 1 1 180 1 1 27 3.74 M 26.85 3.25 L 30 3.25 30 4.25 M 26.85 4.25 L 30 4.25",
|
||||
width: 27,
|
||||
height: 27,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "doubleSink",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Double Sink",
|
||||
type: "Double Sink",
|
||||
geo: "F1 M0 0 L75 0 75 40 0 40 0 0 M5 7.5 L35 7.5 35 35 5 35 5 7.5 M44 7.5 L70 7.5 70 35 40 35 40 9 M15 21.25 A5 5 180 1 0 15 21.24 M50 21.25 A 5 5 180 1 0 50 21.24 M40.5 3.75 A3 3 180 1 1 40.5 3.74 M40.5 3.75 L50.5 13.75 47.5 16.5 37.5 6.75 M32.5 3.75 A 1 1 180 1 1 32.5 3.74 M 27.5 4.25 L 27.5 3.25 30.5 3.25 M 30.5 4.25 L 27.5 4.25 M44.5 3.75 A 1 1 180 1 1 44.5 3.74 M 44.35 3.25 L 47.5 3.25 47.5 4.25 M 44.35 4.25 L 47.5 4.25",
|
||||
height: 27,
|
||||
width: 52,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "toilet",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Toilet",
|
||||
type: "Toilet",
|
||||
geo: "F1 M0 0 L25 0 25 10 0 10 0 0 M20 10 L20 15 5 15 5 10 20 10 M5 15 Q0 15 0 25 Q0 40 12.5 40 Q25 40 25 25 Q25 15 20 15",
|
||||
width: 25,
|
||||
height: 35,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "shower",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Shower/Tub",
|
||||
type: "Shower/Tub",
|
||||
geo: "F1 M0 0 L40 0 40 60 0 60 0 0 M35 15 L35 55 5 55 5 15 Q5 5 20 5 Q35 5 35 15 M22.5 20 A2.5 2.5 180 1 1 22.5 19.99",
|
||||
width: 45,
|
||||
height: 75,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "bed",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Bed",
|
||||
type: "Bed",
|
||||
geo: "F1 M0 0 L40 0 40 60 0 60 0 0 M 7.5 2.5 L32.5 2.5 32.5 17.5 7.5 17.5 7.5 2.5 M0 20 L40 20 M0 25 L40 25",
|
||||
width: 76.2,
|
||||
height: 101.6,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "staircase",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Staircase",
|
||||
type: "Staircase",
|
||||
geo: "F1 M0 0 L 0 100 250 100 250 0 0 0 M25 100 L 25 0 M 50 100 L 50 0 M 75 100 L 75 0 M 100 100 L 100 0 M 125 100 L 125 0 M 150 100 L 150 0 M 175 100 L 175 0 M 200 100 L 200 0 M 225 100 L 225 0",
|
||||
width: 125,
|
||||
height: 50,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "stove",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Stove",
|
||||
type: "Stove",
|
||||
geo: "F1 M 0 0 L 0 100 100 100 100 0 0 0 M 30 15 A 15 15 180 1 0 30.01 15 M 70 15 A 15 15 180 1 0 70.01 15"
|
||||
+ "M 30 55 A 15 15 180 1 0 30.01 55 M 70 55 A 15 15 180 1 0 70.01 55",
|
||||
width: 75,
|
||||
height: 75,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "diningTable",
|
||||
color: "#ffffff",
|
||||
stroke: '#000000',
|
||||
caption: "Dining Table",
|
||||
type: "Dining Table",
|
||||
geo: "F1 M 0 0 L 0 100 200 100 200 0 0 0 M 25 0 L 25 -10 75 -10 75 0 M 125 0 L 125 -10 175 -10 175 0 M 200 25 L 210 25 210 75 200 75 M 125 100 L 125 110 L 175 110 L 175 100 M 25 100 L 25 110 75 110 75 100 M 0 75 -10 75 -10 25 0 25",
|
||||
width: 125,
|
||||
height: 62.5,
|
||||
notes: ""
|
||||
}
|
||||
];
|
||||
|
||||
// Node Data Array for Wall Parts Palette
|
||||
WALLPARTS_NODE_DATA_ARRAY = [
|
||||
{
|
||||
category: "PaletteWallNode",
|
||||
key: "wall",
|
||||
caption: "Wall",
|
||||
type: "Wall",
|
||||
color: "#000000",
|
||||
shape: "Rectangle",
|
||||
height: 10,
|
||||
length: 60,
|
||||
notes: "",
|
||||
},
|
||||
{
|
||||
category: "WindowNode",
|
||||
key: "window",
|
||||
color: "white",
|
||||
caption: "Window",
|
||||
type: "Window",
|
||||
shape: "Rectangle",
|
||||
height: 10,
|
||||
length: 60,
|
||||
notes: ""
|
||||
},
|
||||
{
|
||||
key: "door",
|
||||
category: "DoorNode",
|
||||
color: "rgba(0, 0, 0, 0)",
|
||||
caption: "Door",
|
||||
type: "Door",
|
||||
length: 40,
|
||||
doorOpeningHeight: 5,
|
||||
swing: "left",
|
||||
notes: ""
|
||||
}
|
||||
];
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 KiB |
Reference in New Issue
Block a user