-
Notifications
You must be signed in to change notification settings - Fork 0
/
PlanMakerComponent.cs
356 lines (320 loc) · 10.7 KB
/
PlanMakerComponent.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
using Accord.Math.Optimization;
using Grasshopper.Kernel;
using marmot;
using Newtonsoft.Json;
using Rhino.Geometry;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using static marmot.Helpers;
namespace Marmot
{
public class PlanMaker : GH_Component
{
public PlanMaker()
: base("PlanMaker", "PlanMaker",
"Create an optimized rectangular plan from a graph with rooms, edges and areas.",
"Marmot", "Plan")
{
}
protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
{
pManager.AddGenericParameter("Graph", "G", "Graph object", GH_ParamAccess.item);
pManager.AddRectangleParameter(
"Boundary", "B",
"Rectangle for outer boundary of plan",
GH_ParamAccess.item
);
pManager.AddTextParameter("Fixed Rooms", "fR", "Rooms with fixed position", GH_ParamAccess.list);
pManager.AddPointParameter("Fixed Points", "fP", "Points for position of fixed rooms", GH_ParamAccess.list);
pManager.AddGenericParameter("PlanSettings", "S", "Advanced PlanSettings", GH_ParamAccess.item);
pManager[2].Optional = true;
pManager[3].Optional = true;
pManager[4].Optional = true;
}
protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
{
pManager.AddRectangleParameter("Rectangles", "R", "Rectangles representing the rooms", GH_ParamAccess.list);
}
protected override void SolveInstance(IGH_DataAccess DA)
{
Graph graph = null;
Rectangle3d inRectangle = new Rectangle3d();
List<string> fixedRooms = new List<string>();
List<Point3d> fixedPoints = new List<Point3d>();
Settings settings = null;
if (!DA.GetData(0, ref graph)) return;
if (!DA.GetData(1, ref inRectangle)) return;
if (!DA.GetDataList(2, fixedRooms)) { };
if (!DA.GetDataList(3, fixedPoints)) { };
if (!DA.GetData(4, ref settings)) { };
// Error handling
if (fixedRooms.Count != fixedPoints.Count)
{
throw new Exception(
"Amount of fixed rooms should be equal to amount of fixed points."
);
}
if (fixedRooms.Count > graph.Nodes.Count)
{
throw new Exception(
"Amount of fixed rooms can not be more than graph nodes."
);
}
// Load dissection data from resource into memory
int numberOfRooms = graph.Nodes.Count;
string resourceName = $"marmot.Dissections.dissections_{numberOfRooms}_rooms.json";
var assembly = Assembly.GetExecutingAssembly();
var stream = assembly.GetManifestResourceStream(resourceName);
var reader = new StreamReader(stream);
string dissectionData = reader.ReadToEnd();
// Parse json with dissection data
var root = JsonConvert.DeserializeObject<Dissection[]>(dissectionData);
if (root == null)
{
return;
}
// Get geometry inputs
var width = inRectangle.Width;
var height = inRectangle.Height;
var moveVector = new Vector3d(width / 2, height / 2, 0);
Transform transform = Transform.ChangeBasis(Plane.WorldXY, inRectangle.Plane);
Transform reverseTransform = transform.TryGetInverse(out Transform inverse) ? inverse : Transform.Unset;
Vector3d reverseVector = -moveVector;
// Determine weights
double relativeFixedRoomWeight;
double relativeAreaWeight;
double relativeProportionWeight;
double minSize;
int timeOut;
if (settings is null)
{
relativeFixedRoomWeight = 1.0;
relativeAreaWeight = 1.0;
relativeProportionWeight = 1.0;
minSize = 1.0;
timeOut = 60;
}
else
{
double fixedRoomWeight = settings.WFixedRooms ?? 1.0;
double areaWeight = settings.WAreas ?? 1.0;
double proportionWeight = settings.WProportions ?? 1.0;
minSize = settings.MinSize ?? 1.0;
timeOut = settings.TimeOut ?? 60;
double totalWeight = fixedRoomWeight + areaWeight + proportionWeight;
relativeFixedRoomWeight = fixedRoomWeight / totalWeight;
relativeAreaWeight = areaWeight / totalWeight;
relativeProportionWeight = proportionWeight / totalWeight;
}
// Determine fixed rooms
var roomsFixed = fixedRooms.Any() & fixedPoints.Any();
if (roomsFixed)
{
for (var i = 0; i < fixedPoints.Count; i++)
{
var point = fixedPoints[i];
point.Transform(transform);
point.Transform(Transform.Translation(moveVector));
fixedPoints[i] = point;
}
}
// Loop through dissections
TimeSpan timeoutMapping = TimeSpan.FromSeconds(timeOut / 2); // Timeout duration
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
var mappedGraphs = new ConcurrentBag<Graph>();
Parallel.ForEach(root, (dissection, state) =>
{
// Create new Graph from dissection
var dissectionGraph = new Graph(
nodes: dissection.Nodes.Select(node => node.ToString()).ToList(),
edges: dissection.Edges.Select(
edge => new Tuple<string, string>(edge[0].ToString(), edge[1].ToString())
).ToList(),
rooms: dissection.Rooms.Select(
room => new Tuple<List<int>, List<int>>(room[0], room[1])
).ToList()
);
// Map requirement graph onto dissection graph
var mappedGraphsTemp = graph.MapOnto(dissectionGraph);
foreach (var mappedGraph in mappedGraphsTemp) mappedGraphs.Add(mappedGraph);
// Get equivalent graphs from mapped graphs
foreach (var mappedGraph in mappedGraphsTemp)
{
var rotatedGraph = mappedGraph.RotateGraph();
mappedGraphs.Add(rotatedGraph);
if (roomsFixed) // If rooms fixed, orientation matters
{
var mirroredGraphs = mappedGraph.MirrorGraph();
foreach (var mirroredGraph in mirroredGraphs)
{
mappedGraphs.Add(mirroredGraph);
}
}
}
// Check for timeout
if (stopwatch.Elapsed > timeoutMapping) state.Break();
});
stopwatch.Stop();
stopwatch.Reset();
// Raise exception if no solutions found
if (mappedGraphs.Count < 1)
{
throw new Exception(
"No possible solutions found for requirement graph. Try removing some edges."
);
}
// Starting vals
object lockObject = new object();
double topScore = double.MaxValue;
Graph topGraph = null;
List<double> topX = new List<double>();
List<double> topY = new List<double>();
// Loop through mapped graphs
TimeSpan timeoutOptimizing = TimeSpan.FromSeconds(timeOut / 2); // Timeout duration
stopwatch.Start();
Parallel.ForEach(mappedGraphs, (mappedGraph, state) =>
{
// Determine starting values of room sizes
var xSpacing = new List<List<int>>();
var ySpacing = new List<List<int>>();
foreach (var room in mappedGraph.Rooms)
{
xSpacing.Add(room.Item1);
ySpacing.Add(room.Item2);
}
int xLen = xSpacing.Max(space => space.Last()) + 1;
int yLen = ySpacing.Max(space => space.Last()) + 1;
double[] StartingValues = Enumerable.Repeat(1.0 / (xLen), xLen)
.Concat(Enumerable.Repeat(1.0 / (yLen), yLen)).ToArray();
// Dynamically define objective function to optimize
double Objective(double[] z)
{
// Split values in x and y distances
var spacingVals = CalculateSpacing(z, xLen, yLen, width, height);
List<double> xVals = spacingVals.Item1;
List<double> yVals = spacingVals.Item2;
// Start counting score to minimize
double totalDiff = 0.0;
// Add punishment for minimum widths
foreach (double val in xVals.Concat(yVals))
{
if (val < minSize) totalDiff += ((minSize - val) * 10);
}
// Add punishment for distance of fixedRoom to fixedRoomPoint
if (roomsFixed)
{
var constraintDistance = ConstraintDistanceRoomToPoint(
mappedGraph, fixedRooms, fixedPoints, xVals, yVals
);
totalDiff += relativeFixedRoomWeight * constraintDistance;
}
// Add punishment for stretched proportion of rooms
for (int i = 0; i < mappedGraph.Rooms.Count; i++)
{
var room = mappedGraph.Rooms[i];
double xDimension = room.Item1.Sum(a => Math.Abs(xVals[a]));
double yDimension = room.Item2.Sum(a => Math.Abs(yVals[a]));
totalDiff += relativeAreaWeight * Math.Pow(
Math.Abs(mappedGraph.Areas[i] - xDimension * yDimension), 2
) * 0.1;
totalDiff += relativeProportionWeight * ConstraintProportion(
xDimension, yDimension
);
}
return totalDiff;
}
// Optimize room sizes
var optimizer = new NelderMead(numberOfVariables: StartingValues.Length)
{
Function = Objective,
};
optimizer.Convergence.StartTime = DateTime.Now;
optimizer.Convergence.MaximumTime = TimeSpan.FromSeconds(10);
bool success = optimizer.Minimize(StartingValues);
if (success)
{
// Unpack values
double score = optimizer.Value;
double[] optimized = optimizer.Solution;
// Save top option
if (score < topScore)
{
lock (lockObject)
{
// Re-check the condition to ensure it hasn't
// been updated by another thread
if (score < topScore)
{
topScore = score;
var spacingVals = CalculateSpacing(
optimized, xLen, yLen, width, height
);
topX = spacingVals.Item1;
topY = spacingVals.Item2;
topGraph = mappedGraph;
}
}
}
}
// Check for timeout
if (stopwatch.Elapsed > timeoutMapping) state.Break();
});
stopwatch.Stop();
// Draw rectangles for rooms of top solution
List<Rectangle3d> roomRectangles = new List<Rectangle3d>();
for (int i = 0; i < topGraph.Nodes.Count; i++)
{
Plane originalPlane = Plane.WorldXY;
Point3d insertionPoint = new Point3d(
topX.Take(topGraph.Rooms[i].Item1[0]).Sum(),
topY.Take(topGraph.Rooms[i].Item2[0]).Sum(),
0
);
Plane insertionPlane = new Plane(insertionPoint, originalPlane.Normal);
double xSize = topX.GetRange(
topGraph.Rooms[i].Item1[0],
topGraph.Rooms[i].Item1.Count
).Sum();
double ySize = topY.GetRange(
topGraph.Rooms[i].Item2[0],
topGraph.Rooms[i].Item2.Count
).Sum();
Rectangle3d roomRectangle = new Rectangle3d(
insertionPlane,
new Interval(0, xSize),
new Interval(0, ySize)
);
roomRectangle.Transform(
Transform.Multiply(
Transform.Translation(reverseVector),
reverseTransform
)
);
roomRectangles.Add(roomRectangle);
}
// Return rectangle list
DA.SetDataList(0, roomRectangles);
}
public override GH_Exposure Exposure => GH_Exposure.primary;
protected override System.Drawing.Bitmap Icon
{
get
{
string resourceName = "marmot.Icons.planMaker.png";
var assembly = Assembly.GetExecutingAssembly();
using (var stream = assembly.GetManifestResourceStream(resourceName))
{
return new System.Drawing.Bitmap(stream);
}
}
}
public override Guid ComponentGuid => new Guid("ded68cf8-aeb0-4a18-a2e3-be2c7a7f843b");
}
}