From f034d2f6a525ce1578b77fb666d7f68c96e4dd39 Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 24 Dec 2025 22:25:56 +0000
Subject: [PATCH 1/2] Refactor ResPlan.NET to be a pure data provider
- Removed BuildingGenerator.cs and associated tests
- Removed Building and BuildingFloor classes from Models.cs
- Added GetEntrance() to Plan class
- Removed rotation logic from Plan and PlanLoader
- Updated PlanRenderer to work directly with Plan objects
- Updated Demo to load and render plans individually
---
ResPlan.Demo/Program.cs | 28 ++--
ResPlan.Library/BuildingGenerator.cs | 181 ------------------------
ResPlan.Library/Models.cs | 95 ++-----------
ResPlan.Library/PlanLoader.cs | 54 +------
ResPlan.Library/PlanRenderer.cs | 43 +-----
ResPlan.Tests/BuildingGeneratorTests.cs | 116 ---------------
ResPlan.Tests/ConstraintTests.cs | 64 ---------
7 files changed, 31 insertions(+), 550 deletions(-)
delete mode 100644 ResPlan.Library/BuildingGenerator.cs
delete mode 100644 ResPlan.Tests/BuildingGeneratorTests.cs
delete mode 100644 ResPlan.Tests/ConstraintTests.cs
diff --git a/ResPlan.Demo/Program.cs b/ResPlan.Demo/Program.cs
index b2c62ff..aade30d 100644
--- a/ResPlan.Demo/Program.cs
+++ b/ResPlan.Demo/Program.cs
@@ -12,32 +12,38 @@ static async Task Main(string[] args)
try
{
Console.WriteLine("ResPlan Demo");
- Console.WriteLine("Generating Multi-Story Building...");
+ Console.WriteLine("Loading Floor Plans...");
- var plans = await PlanLoader.LoadPlansAsync(maxItems: 50);
+ var plans = await PlanLoader.LoadPlansAsync(maxItems: 5);
if (plans.Count == 0)
{
Console.WriteLine("No plans loaded.");
return;
}
- var generator = new BuildingGenerator();
- var building = generator.GenerateBuilding(plans, 3);
+ Console.WriteLine($"Loaded {plans.Count} plans.");
- Console.WriteLine($"Generated Building with {building.Floors.Count} floors.");
-
- // Render each floor
string outputDir = "output";
if (!Directory.Exists(outputDir))
{
Directory.CreateDirectory(outputDir);
}
- foreach (var floor in building.Floors)
+ foreach (var plan in plans)
{
- string filename = Path.Combine(outputDir, $"floor_{floor.FloorNumber}.png");
- PlanRenderer.RenderFloor(floor, filename);
- Console.WriteLine($"Rendered {filename}");
+ string filename = Path.Combine(outputDir, $"plan_{plan.Id}.png");
+ PlanRenderer.Render(plan, filename);
+ Console.WriteLine($"Rendered Plan {plan.Id} to {filename}");
+
+ var entrance = plan.GetEntrance();
+ if (entrance != null)
+ {
+ Console.WriteLine($" Plan {plan.Id} Entrance: {entrance}");
+ }
+ else
+ {
+ Console.WriteLine($" Plan {plan.Id} has no entrance.");
+ }
}
Console.WriteLine("Done.");
diff --git a/ResPlan.Library/BuildingGenerator.cs b/ResPlan.Library/BuildingGenerator.cs
deleted file mode 100644
index f259ab5..0000000
--- a/ResPlan.Library/BuildingGenerator.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using NetTopologySuite.Geometries;
-using NetTopologySuite.Geometries.Utilities;
-
-namespace ResPlan.Library
-{
- ///
- /// Generates multi-story buildings by stacking compatible floor plans.
- ///
- public class BuildingGenerator
- {
- private readonly GeometryFactory _geometryFactory = new GeometryFactory();
-
- ///
- /// Generates a building with a specified number of floors using the provided plans.
- ///
- /// A list of candidate plans to stack.
- /// The desired number of floors.
- /// A object containing the stacked floors.
- public Building GenerateBuilding(List availablePlans, int targetFloors)
- {
- var building = new Building();
- if (availablePlans == null || availablePlans.Count == 0)
- return building;
-
- // Work on copies to avoid mutating the input list objects
- var unusedPlans = availablePlans.Select(ClonePlan).ToList();
-
- // Step 1: Normalize all plans (Center Front Door to 0,0)
- foreach (var p in unusedPlans)
- {
- NormalizePlan(p);
- }
-
- // Step 2: Sort by Area descending (simple heuristic)
- unusedPlans = unusedPlans.OrderByDescending(p => p.Bounds.Area).ToList();
-
- if (unusedPlans.Count == 0) return building;
-
- // Step 3: Pick Base Floor
- var basePlan = unusedPlans[0];
- unusedPlans.RemoveAt(0);
-
- var floor0 = new BuildingFloor
- {
- FloorNumber = 0,
- Plan = basePlan
- };
- AddStairCore(floor0);
- building.Floors.Add(floor0);
-
- // Step 4: Stack subsequent floors
- var currentFloorPlan = basePlan;
-
- for (int i = 1; i < targetFloors; i++)
- {
- Plan bestFitPlan = null;
- int bestFitIndex = -1;
- double bestFitScore = -1;
-
- // Search for a plan that fits
- for (int j = 0; j < unusedPlans.Count; j++)
- {
- var candidate = unusedPlans[j];
-
- // We need to test rotations WITHOUT mutating 'candidate' permanently for the next iteration of 'j' loop
- // But 'candidate' is already a clone from the input.
- // However, if we rotate it for test 1, we must rotate it back or use a temp copy.
- // Since geometry operations can be expensive, let's clone for the inner loop?
- // Or just rotate, check, rotate back? Rotate back is cheaper.
-
- for (int r = 0; r < 4; r++)
- {
- // Rotate 90 degrees (relative to 0,0)
- if (r > 0)
- {
- candidate.Rotate(Math.PI / 2.0, new Coordinate(0, 0));
- }
-
- // Check fit
- var currentGeom = _geometryFactory.ToGeometry(currentFloorPlan.Bounds);
- var candidateGeom = _geometryFactory.ToGeometry(candidate.Bounds);
-
- var intersection = currentGeom.Intersection(candidateGeom);
- var coverage = intersection.Area / candidateGeom.Area;
-
- if (coverage > 0.95) // 95% contained
- {
- if (candidateGeom.Area > bestFitScore)
- {
- bestFitScore = candidateGeom.Area;
- // We found a better fit. Snapshot the plan in its current state.
- bestFitPlan = ClonePlan(candidate);
- bestFitIndex = j;
- }
- }
- }
-
- // Rotate back to original state for next outer loop usage
- candidate.Rotate(Math.PI / 2.0, new Coordinate(0, 0));
- }
-
- if (bestFitPlan != null)
- {
- unusedPlans.RemoveAt(bestFitIndex);
- var nextFloor = new BuildingFloor
- {
- FloorNumber = i,
- Plan = bestFitPlan
- };
- AddStairCore(nextFloor);
- building.Floors.Add(nextFloor);
- currentFloorPlan = bestFitPlan;
- }
- else
- {
- break;
- }
- }
-
- return building;
- }
-
- private Plan ClonePlan(Plan source)
- {
- var newPlan = new Plan
- {
- Id = source.Id,
- Geometries = new Dictionary>(),
- // Bounds will be recalculated or copied
- ReferenceGraph = null // Not deep cloning graph for now as we don't use it for generation
- };
-
- if (source.Bounds != null)
- {
- newPlan.Bounds = new Envelope(source.Bounds);
- }
-
- foreach(var kvp in source.Geometries)
- {
- newPlan.Geometries[kvp.Key] = new List();
- foreach(var g in kvp.Value)
- {
- newPlan.Geometries[kvp.Key].Add(g.Copy());
- }
- }
-
- return newPlan;
- }
-
- private void NormalizePlan(Plan plan)
- {
- if (plan.Geometries.ContainsKey("front_door") && plan.Geometries["front_door"].Count > 0)
- {
- var fd = plan.Geometries["front_door"][0];
- var center = fd.Centroid.Coordinate;
-
- plan.Translate(-center.X, -center.Y);
- }
- }
-
- private void AddStairCore(BuildingFloor floor)
- {
- // Create a stair core rectangle 4x3m placed "south" of 0,0
- var coords = new Coordinate[]
- {
- new Coordinate(-2, -4),
- new Coordinate(2, -4),
- new Coordinate(2, 0), // Touches 0,0 (Front Door)
- new Coordinate(-2, 0),
- new Coordinate(-2, -4)
- };
- var shell = _geometryFactory.CreateLinearRing(coords);
- var poly = _geometryFactory.CreatePolygon(shell);
-
- floor.AdditionalGeometries["stairs"] = new List { poly };
- }
- }
-}
diff --git a/ResPlan.Library/Models.cs b/ResPlan.Library/Models.cs
index 09a69b7..a5f3b28 100644
--- a/ResPlan.Library/Models.cs
+++ b/ResPlan.Library/Models.cs
@@ -54,59 +54,20 @@ public class Plan
[Key(3)]
public Graph ReferenceGraph { get; set; }
- public void Rotate(double angleRadians, Coordinate center)
+ public Coordinate GetEntrance()
{
- var transform = new AffineTransformation();
- transform.Rotate(angleRadians, center.X, center.Y);
-
- ApplyTransformation(transform);
- }
-
- public void Translate(double dx, double dy)
- {
- var transform = new AffineTransformation();
- transform.Translate(dx, dy);
-
- ApplyTransformation(transform);
- }
-
- private void ApplyTransformation(AffineTransformation transform)
- {
- // Transform geometries
- foreach (var key in Geometries.Keys.ToList())
- {
- var originalList = Geometries[key];
- var newList = new List();
- foreach (var geom in originalList)
- {
- var newGeom = transform.Transform(geom);
- newList.Add(newGeom);
- }
- Geometries[key] = newList;
- }
-
- // Update Bounds
- var newEnvelope = new Envelope();
- foreach (var list in Geometries.Values)
+ if (Geometries.TryGetValue("front_door", out var doors) && doors.Any())
{
- foreach (var geom in list)
- {
- newEnvelope.ExpandToInclude(geom.EnvelopeInternal);
- }
+ // Return the centroid of the first front door
+ var door = doors.First();
+ return door.Centroid.Coordinate;
}
- Bounds = newEnvelope;
-
- // Transform Graph Nodes
- if (ReferenceGraph != null && ReferenceGraph.Nodes != null)
+ else if (Geometries.TryGetValue("entrance", out var entrances) && entrances.Any())
{
- foreach (var node in ReferenceGraph.Nodes.Values)
- {
- if (node.Geometry != null)
- {
- node.Geometry = transform.Transform(node.Geometry);
- }
- }
+ var entrance = entrances.First();
+ return entrance.Centroid.Coordinate;
}
+ return null;
}
}
@@ -142,42 +103,4 @@ public class Edge
[Key(2)]
public string Type { get; set; }
}
-
- ///
- /// Represents a multi-story building composed of stacked floor plans.
- ///
- [MessagePackObject]
- public class Building
- {
- ///
- /// The list of floors in the building, typically ordered by floor number.
- ///
- [Key(0)]
- public List Floors { get; set; } = new List();
- }
-
- ///
- /// Represents a single floor within a building.
- ///
- [MessagePackObject]
- public class BuildingFloor
- {
- ///
- /// The 0-based index of the floor.
- ///
- [Key(0)]
- public int FloorNumber { get; set; }
-
- ///
- /// The floor plan associated with this level.
- ///
- [Key(1)]
- public Plan Plan { get; set; }
-
- ///
- /// Additional geometries generated for this floor (e.g., stairs, corridors) that are not part of the original plan.
- ///
- [Key(2)]
- public Dictionary> AdditionalGeometries { get; set; } = new Dictionary>();
- }
}
diff --git a/ResPlan.Library/PlanLoader.cs b/ResPlan.Library/PlanLoader.cs
index 6036012..ef58f43 100644
--- a/ResPlan.Library/PlanLoader.cs
+++ b/ResPlan.Library/PlanLoader.cs
@@ -20,8 +20,6 @@ public static async Task> LoadPlansAsync(string jsonPath = null, stri
{
if (!string.IsNullOrEmpty(jsonPath) && File.Exists(jsonPath))
{
- // In this path, we don't apply constraints inside LoadPlansFromJson for now, or we should?
- // The prompt says "When using the API". It's better to be consistent.
var loaded = LoadPlansFromJson(jsonPath);
return ApplyConstraints(loaded, constraints, logger);
}
@@ -72,9 +70,6 @@ public static async Task> LoadPlansAsync(string jsonPath = null, stri
UseShellExecute = false
};
- // Set environment to use the venv?
- // Invoking the python binary in venv/bin/python3 sets up sys.path correctly automatically.
-
using (var p = Process.Start(psi))
{
var stdout = await p.StandardOutput.ReadToEndAsync();
@@ -87,10 +82,6 @@ public static async Task> LoadPlansAsync(string jsonPath = null, stri
}
// Parse stdout as JSON
- // The script should output JSON.
- // We might need to filter stdout if there are prints.
- // The wrapper script should avoid prints.
-
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
@@ -129,51 +120,14 @@ private static List ApplyConstraints(List plans, PlanGenerationConst
{
logger?.Invoke("Warning: Garage/Driveway facing constraints are not supported by the current ResPlan dataset.");
}
+ if (constraints.FrontDoorFacing.HasValue)
+ {
+ logger?.Invoke("Warning: FrontDoorFacing constraint is no longer supported in PlanLoader. Consumers should handle orientation.");
+ }
var result = new List();
foreach (var plan in plans)
{
- // Apply Facing Constraint
- if (constraints.FrontDoorFacing.HasValue)
- {
- // Find front door
- Geometry frontDoor = null;
- if (plan.Geometries.ContainsKey("front_door"))
- {
- var fds = plan.Geometries["front_door"];
- if (fds.Count > 0)
- {
- // If multiple, pick first?
- frontDoor = fds[0];
- }
- }
-
- if (frontDoor != null)
- {
- var centroid = frontDoor.Centroid.Coordinate;
- var planCenter = plan.Bounds.Centre; // Envelope center
-
- // Vector from plan center to door center
- var currentVec = new NetTopologySuite.Geometries.Coordinate(centroid.X - planCenter.X, centroid.Y - planCenter.Y);
-
- // If center and door are same (rare), skip
- if (Math.Abs(currentVec.X) > 1e-6 || Math.Abs(currentVec.Y) > 1e-6)
- {
- var targetX = constraints.FrontDoorFacing.Value.X;
- var targetY = constraints.FrontDoorFacing.Value.Y;
-
- // Angle of current vector
- var currentAngle = Math.Atan2(currentVec.Y, currentVec.X);
- // Angle of target
- var targetAngle = Math.Atan2(targetY, targetX);
-
- var diff = targetAngle - currentAngle;
-
- plan.Rotate(diff, planCenter);
- }
- }
- }
-
// Apply Bounding Constraint
if (!IsPlanCompatible(plan, constraints.BoundingPolygon))
{
diff --git a/ResPlan.Library/PlanRenderer.cs b/ResPlan.Library/PlanRenderer.cs
index f85f458..e574b63 100644
--- a/ResPlan.Library/PlanRenderer.cs
+++ b/ResPlan.Library/PlanRenderer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.IO;
using NetTopologySuite.Geometries;
using SkiaSharp;
@@ -28,48 +29,6 @@ public class PlanRenderer
"living", "bedroom", "bathroom", "kitchen", "door", "window", "wall", "front_door", "balcony", "stairs"
};
- public static void RenderFloor(BuildingFloor floor, string outputPath, int width = 800, int height = 800)
- {
- // Create a composite plan for rendering
- // We can shallow copy the plan logic
- // But Render takes a Plan.
- // We can create a temporary Plan object that merges Geometries + AdditionalGeometries
-
- // Create a composite plan for rendering
- // We use deep copy of the lists to avoid mutating the original plan
- var compositePlan = new Plan
- {
- Id = floor.Plan.Id,
- Geometries = new Dictionary>(),
- Bounds = new Envelope(floor.Plan.Bounds)
- };
-
- foreach(var kvp in floor.Plan.Geometries)
- {
- compositePlan.Geometries[kvp.Key] = new List(kvp.Value);
- }
-
- foreach(var kvp in floor.AdditionalGeometries)
- {
- if(compositePlan.Geometries.ContainsKey(kvp.Key))
- {
- compositePlan.Geometries[kvp.Key].AddRange(kvp.Value);
- }
- else
- {
- compositePlan.Geometries[kvp.Key] = new List(kvp.Value);
- }
-
- // Expand bounds
- foreach(var g in kvp.Value)
- {
- compositePlan.Bounds.ExpandToInclude(g.EnvelopeInternal);
- }
- }
-
- Render(compositePlan, outputPath, width, height);
- }
-
public static void Render(Plan plan, string outputPath, int width = 800, int height = 800)
{
using var surface = SKSurface.Create(new SKImageInfo(width, height));
diff --git a/ResPlan.Tests/BuildingGeneratorTests.cs b/ResPlan.Tests/BuildingGeneratorTests.cs
deleted file mode 100644
index 1ead246..0000000
--- a/ResPlan.Tests/BuildingGeneratorTests.cs
+++ /dev/null
@@ -1,116 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using NetTopologySuite.Geometries;
-using ResPlan.Library;
-using Xunit;
-
-namespace ResPlan.Tests
-{
- public class BuildingGeneratorTests
- {
- private readonly GeometryFactory _geometryFactory = new GeometryFactory();
-
- [Fact]
- public void TestGenerateBuilding()
- {
- // Create mock plans manually to avoid Python dependency in unit tests
- var plans = new List
- {
- CreateMockPlan(1, 20, 20), // 400 area
- CreateMockPlan(2, 15, 15), // 225 area
- CreateMockPlan(3, 10, 10), // 100 area
- CreateMockPlan(4, 30, 30) // 900 area (Largest)
- };
-
- var generator = new BuildingGenerator();
- var building = generator.GenerateBuilding(plans, targetFloors: 5);
-
- Assert.NotNull(building);
- Assert.NotEmpty(building.Floors);
-
- // We provided 4 plans, target 5. Should use all 4 if they fit.
- // Order should be 30x30 -> 20x20 -> 15x15 -> 10x10.
- Assert.Equal(4, building.Floors.Count);
-
- // Verify Floor Sorting (Area Descending)
- Assert.Equal(4, building.Floors[0].Plan.Id); // 30x30
- Assert.Equal(1, building.Floors[1].Plan.Id); // 20x20
- Assert.Equal(2, building.Floors[2].Plan.Id); // 15x15
- Assert.Equal(3, building.Floors[3].Plan.Id); // 10x10
-
- // Check Stair Core
- foreach(var floor in building.Floors)
- {
- Assert.True(floor.AdditionalGeometries.ContainsKey("stairs"));
- Assert.NotEmpty(floor.AdditionalGeometries["stairs"]);
-
- // Verify Front Door is at 0,0 (Normalization)
- if (floor.Plan.Geometries.ContainsKey("front_door"))
- {
- var fd = floor.Plan.Geometries["front_door"].FirstOrDefault();
- if (fd != null)
- {
- var c = fd.Centroid.Coordinate;
- Assert.InRange(c.X, -0.001, 0.001);
- Assert.InRange(c.Y, -0.001, 0.001);
- }
- }
- }
-
- // Check containment
- for(int i=0; i upper.Bounds.Area);
- }
- }
-
- private Plan CreateMockPlan(int id, double width, double height)
- {
- var plan = new Plan
- {
- Id = id,
- Geometries = new Dictionary>()
- };
-
- // Define coordinates for a box
- // Assume Front Door is at bottom-center relative to the box?
- // Or we just place a box and a front door point.
- // Let's place the box from (0,0) to (width, height)
- // And Front Door at (width/2, 0).
-
- var box = _geometryFactory.CreatePolygon(new Coordinate[]
- {
- new Coordinate(0, 0),
- new Coordinate(width, 0),
- new Coordinate(width, height),
- new Coordinate(0, height),
- new Coordinate(0, 0)
- });
-
- // Create Front Door geometry (small line or polygon)
- var fdCenterX = width / 2.0;
- var fd = _geometryFactory.CreatePolygon(new Coordinate[]
- {
- new Coordinate(fdCenterX - 0.5, 0),
- new Coordinate(fdCenterX + 0.5, 0),
- new Coordinate(fdCenterX + 0.5, 0.2), // Slight thickness
- new Coordinate(fdCenterX - 0.5, 0.2),
- new Coordinate(fdCenterX - 0.5, 0)
- });
-
- plan.Geometries["living"] = new List { box };
- plan.Geometries["front_door"] = new List { fd };
-
- plan.Bounds = box.EnvelopeInternal;
-
- return plan;
- }
- }
-}
diff --git a/ResPlan.Tests/ConstraintTests.cs b/ResPlan.Tests/ConstraintTests.cs
deleted file mode 100644
index 72abd53..0000000
--- a/ResPlan.Tests/ConstraintTests.cs
+++ /dev/null
@@ -1,64 +0,0 @@
-using System.Collections.Generic;
-using NetTopologySuite.Geometries;
-using ResPlan.Library;
-using Xunit;
-
-namespace ResPlan.Tests
-{
- public class ConstraintTests
- {
- [Fact]
- public void IsPlanCompatible_RotatedPlanInRotatedLot_ReturnsTrue()
- {
- // Arrange
- var geometryFactory = new GeometryFactory();
-
- // Create a Plan with a 10x10 square at (0,0)
- // Centered at 5,5
- var coords = new Coordinate[]
- {
- new Coordinate(0, 0),
- new Coordinate(10, 0),
- new Coordinate(10, 10),
- new Coordinate(0, 10),
- new Coordinate(0, 0)
- };
- var shell = geometryFactory.CreateLinearRing(coords);
- var poly = geometryFactory.CreatePolygon(shell);
-
- var plan = new Plan
- {
- Id = 1,
- Geometries = new Dictionary>
- {
- { "room", new List { poly } }
- }
- };
- // Initial bounds (0,0) to (10,10)
- plan.Bounds = new Envelope(0, 10, 0, 10);
-
- // Rotate the plan by 45 degrees around its center (5,5)
- plan.Rotate(System.Math.PI / 4.0, new Coordinate(5, 5));
-
- // Now create a bounding polygon that matches the rotated plan geometry exactly
- // We can cheat by using the plan's geometry itself (the rotated poly)
- // But to be rigorous, let's use the rotated poly from the plan
- var rotatedGeom = plan.Geometries["room"][0];
- Assert.True(rotatedGeom is Polygon);
- var boundingPolygon = (Polygon)rotatedGeom.Copy();
-
- // The bounding polygon now matches the plan's geometry.
- // However, plan.Bounds (AABB) will be larger than the rotated geometry.
- // AABB of a 45-degree rotated 10x10 square is roughly 14.14x14.14.
- // This AABB will NOT be contained in the 10x10 rotated square (which is the boundingPolygon).
-
- // Act
- bool isCompatible = PlanLoader.IsPlanCompatible(plan, boundingPolygon);
-
- // Assert
- // This should be true if we are checking geometry containment correctly.
- // It will be false if we are only checking AABB containment.
- Assert.True(isCompatible, "Plan should be compatible with a bounding polygon that exactly matches its geometry.");
- }
- }
-}
From a055fb27fc4bb286ea42cf897c4483dffbd3573f Mon Sep 17 00:00:00 2001
From: "google-labs-jules[bot]"
<161369871+google-labs-jules[bot]@users.noreply.github.com>
Date: Wed, 24 Dec 2025 22:32:33 +0000
Subject: [PATCH 2/2] Address PR feedback: fix docs and constraints
- Updated README.md and DOCUMENTATION.md to remove all references to BuildingGenerator and multi-story features.
- Removed FrontDoorFacing and GarageFacing from PlanGenerationConstraints to enforce a cleaner API.
- Updated PlanLoader to remove logic related to removed constraints.
- Refined Plan.GetEntrance() to use deterministic sorting (Centroid X then Y).
- Removed unused using statements in Models.cs.
---
DOCUMENTATION.md | 46 +++-----------------
README.md | 34 ++-------------
ResPlan.Library/Models.cs | 18 +++++---
ResPlan.Library/PlanGenerationConstraints.cs | 2 -
ResPlan.Library/PlanLoader.cs | 9 ----
5 files changed, 20 insertions(+), 89 deletions(-)
diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md
index c4ed46a..4a9da88 100644
--- a/DOCUMENTATION.md
+++ b/DOCUMENTATION.md
@@ -11,7 +11,6 @@ graph TD
subgraph DotNet ["ResPlan.Library (.NET)"]
PL[PlanLoader]
GG[GraphGenerator]
- BG[BuildingGenerator]
PR[PlanRenderer]
Models[Data Models]
PEM[PythonEnvManager]
@@ -33,7 +32,6 @@ graph TD
Wrapper -->|Streams JSON| PL
PL -->|Deserializes to| Models
GG -->|Consumes| Models
- BG -->|Consumes & Produces| Models
PR -->|Consumes| Models
PL -.->|Manages| PEM
PEM -.->|Configures| Venv
@@ -86,12 +84,11 @@ classDiagram
+Dictionary Geometries
+Envelope Bounds
+Graph ReferenceGraph
+ +Coordinate GetEntrance()
}
class PlanGenerationConstraints {
+Polygon BoundingPolygon
- +Vector2? FrontDoorFacing
- +Vector2? GarageFacing
}
class Graph {
@@ -121,25 +118,17 @@ classDiagram
Graph "1" *-- "*" Node
Graph "1" *-- "*" Edge
Node "1" o-- "1" Geometry
- Building "1" *-- "*" BuildingFloor
- BuildingFloor "1" o-- "1" Plan
```
### Type Definitions
-* **`Building`**: Represents a multi-story structure.
- * `Floors`: A list of `BuildingFloor` objects sorted by floor number.
-* **`BuildingFloor`**: A single floor in a building.
- * `Plan`: The floorplan associated with this level.
- * `AdditionalGeometries`: Generated common areas (e.g., stairs, corridors) not present in the original plan.
* **`Plan`**: The root object representing a single floorplan.
* `Geometries`: A dictionary mapping category names (e.g., "living", "wall", "door") to lists of NetTopologySuite `Geometry` objects.
* `Bounds`: The spatial bounding box of the plan.
* `ReferenceGraph`: The ground-truth graph provided by the dataset (if available).
-* **`PlanGenerationConstraints`**: Configuration for filtering and orienting loaded plans.
+ * `GetEntrance()`: Returns the coordinate of the plan's entrance (front door), or null if not found.
+* **`PlanGenerationConstraints`**: Configuration for filtering loaded plans.
* `BoundingPolygon`: A polygon that must fully contain the plan's bounds.
- * `FrontDoorFacing`: A target vector for the front door's orientation relative to the plan's center.
- * `GarageFacing`: *Unsupported in current dataset*.
* **`Graph`**: Represents the connectivity graph of the floorplan.
* Generated by `GraphGenerator` or loaded as `ReferenceGraph`.
* **`Node`**: A node in the graph, typically representing a room or a portal (door/window).
@@ -159,10 +148,8 @@ Handles the loading of plan data.
* **pklPathOverride**: Optional path to a specific `.pkl` file. If null, defaults to the managed dataset path.
* **maxItems**: Optional limit on the number of plans to load.
* **logger**: Optional callback to receive real-time progress updates (e.g., dependency installation logs, download progress).
- * **constraints**: Optional `PlanGenerationConstraints` to filter or orient plans.
- * **BoundingPolygon**: If set, only plans whose geometry is completely contained within this polygon are returned. The check first validates the plan's Axis-Aligned Bounding Box (AABB) for performance, and if that fails, performs a precise geometry-by-geometry containment check to support rotated plans.
- * **FrontDoorFacing**: If set, plans are rotated so their front door (vector from center to door) aligns with this vector.
- * **GarageFacing**: Currently unsupported; will log a warning if set.
+ * **constraints**: Optional `PlanGenerationConstraints` to filter plans.
+ * **BoundingPolygon**: If set, only plans whose geometry is completely contained within this polygon are returned. The check first validates the plan's Axis-Aligned Bounding Box (AABB) for performance, and if that fails, performs a precise geometry-by-geometry containment check.
* **Returns**: A list of `Plan` objects.
### `ResPlan.Library.Data.PlanSerializer`
@@ -190,35 +177,12 @@ Generates connectivity graphs from `Plan` geometries.
* Connects rooms via "door" or "window" geometries.
* **Returns**: A new `Graph` object.
-## Building Generation
-
-### `ResPlan.Library.BuildingGenerator`
-
-Procedurally generates multi-story buildings from a set of available plans.
-
-* `Building GenerateBuilding(List availablePlans, int targetFloors)`
- * Stacks plans to form a coherent building structure.
- * **Algorithm**:
- 1. **Normalization**: All candidate plans are translated so their "front door" is at (0,0).
- 2. **Sorting**: Plans are sorted by bounding box area (largest first).
- 3. **Stacking**:
- * Floor 0 is the largest available plan.
- * Subsequent floors are chosen from the remaining plans such that they "fit" within the previous floor's footprint (checking geometric containment > 95%).
- * The generator attempts 4 cardinal rotations (0, 90, 180, 270 degrees) to find the best fit.
- 4. **Stairs**: A "stair core" geometry (4x3m) is automatically generated adjacent to the front door for vertical circulation.
- * **Returns**: A `Building` object containing the stacked floors.
-
### `ResPlan.Library.PlanRenderer`
Visualizes plans using SkiaSharp.
* `static void Render(Plan plan, string outputPath, int width = 800, int height = 800)`
* Renders a single `Plan`.
-* `static void RenderFloor(BuildingFloor floor, string outputPath, int width = 800, int height = 800)`
- * **floor**: The `BuildingFloor` to render.
- * **Details**:
- * Combines the `Plan` geometries with `AdditionalGeometries` (e.g., stairs).
- * Renders "stairs" in a distinct color (DarkBlue).
## Testing
diff --git a/README.md b/README.md
index 5d04b61..bd7b75f 100644
--- a/README.md
+++ b/README.md
@@ -45,7 +45,7 @@ Console.WriteLine($"Loaded {plans.Count} plans.");
### 2. Applying Constraints (Optional)
-You can filter and orient plans using `PlanGenerationConstraints`.
+You can filter plans using `PlanGenerationConstraints`.
```csharp
using NetTopologySuite.Geometries;
@@ -66,10 +66,7 @@ var boundingPoly = new GeometryFactory().CreatePolygon(coordinates);
var constraints = new PlanGenerationConstraints
{
// Filter: Plan must fit inside this polygon
- BoundingPolygon = boundingPoly,
-
- // Orientation: Rotate plan so the front door faces "Up" (positive Y)
- FrontDoorFacing = new Vector2(0, 1)
+ BoundingPolygon = boundingPoly
};
var constrainedPlans = await PlanLoader.LoadPlansAsync(
@@ -121,32 +118,7 @@ foreach (var plan in plans)
}
```
-### 5. Multi-Story Building Generation
-
-You can generate multi-story buildings by stacking compatible floor plans using `BuildingGenerator`.
-
-```csharp
-using ResPlan.Library;
-
-// 1. Load a pool of plans (load enough to find good matches)
-var plans = await PlanLoader.LoadPlansAsync(maxItems: 50, logger: logger);
-
-// 2. Generate a building
-var generator = new BuildingGenerator();
-// Attempt to stack 3 floors using the available plans
-Building building = generator.GenerateBuilding(plans, targetFloors: 3);
-
-Console.WriteLine($"Generated building with {building.Floors.Count} floors.");
-
-// 3. Render each floor (including generated stairs)
-foreach (var floor in building.Floors)
-{
- // RenderFloor visualizes the plan plus additional geometries (e.g., stairs)
- PlanRenderer.RenderFloor(floor, $"building_floor_{floor.FloorNumber}.png");
-}
-```
-
-### 6. Serialization
+### 5. Serialization
To support binary serialization and handle special floating-point values (like `NaN` or `Infinity`) which are not standard in JSON, the library provides a helper class `PlanSerializer` using **MessagePack**.
diff --git a/ResPlan.Library/Models.cs b/ResPlan.Library/Models.cs
index a5f3b28..58e770a 100644
--- a/ResPlan.Library/Models.cs
+++ b/ResPlan.Library/Models.cs
@@ -4,7 +4,6 @@
using System.Text.Json.Serialization;
using MessagePack;
using NetTopologySuite.Geometries;
-using NetTopologySuite.Geometries.Utilities;
using ResPlan.Library.Data;
namespace ResPlan.Library
@@ -56,17 +55,24 @@ public class Plan
public Coordinate GetEntrance()
{
+ List candidates = null;
+
if (Geometries.TryGetValue("front_door", out var doors) && doors.Any())
{
- // Return the centroid of the first front door
- var door = doors.First();
- return door.Centroid.Coordinate;
+ candidates = doors;
}
else if (Geometries.TryGetValue("entrance", out var entrances) && entrances.Any())
{
- var entrance = entrances.First();
- return entrance.Centroid.Coordinate;
+ candidates = entrances;
}
+
+ if (candidates != null && candidates.Count > 0)
+ {
+ // Sort deterministically by Centroid X then Y
+ var sorted = candidates.OrderBy(g => g.Centroid.X).ThenBy(g => g.Centroid.Y).ToList();
+ return sorted[0].Centroid.Coordinate;
+ }
+
return null;
}
}
diff --git a/ResPlan.Library/PlanGenerationConstraints.cs b/ResPlan.Library/PlanGenerationConstraints.cs
index f3abc62..f6384c2 100644
--- a/ResPlan.Library/PlanGenerationConstraints.cs
+++ b/ResPlan.Library/PlanGenerationConstraints.cs
@@ -7,7 +7,5 @@ namespace ResPlan.Library
public class PlanGenerationConstraints
{
public Polygon BoundingPolygon { get; set; }
- public Vector2? FrontDoorFacing { get; set; }
- public Vector2? GarageFacing { get; set; }
}
}
diff --git a/ResPlan.Library/PlanLoader.cs b/ResPlan.Library/PlanLoader.cs
index ef58f43..5211b81 100644
--- a/ResPlan.Library/PlanLoader.cs
+++ b/ResPlan.Library/PlanLoader.cs
@@ -116,15 +116,6 @@ private static List ApplyConstraints(List plans, PlanGenerationConst
{
if (constraints == null) return plans;
- if (constraints.GarageFacing.HasValue)
- {
- logger?.Invoke("Warning: Garage/Driveway facing constraints are not supported by the current ResPlan dataset.");
- }
- if (constraints.FrontDoorFacing.HasValue)
- {
- logger?.Invoke("Warning: FrontDoorFacing constraint is no longer supported in PlanLoader. Consumers should handle orientation.");
- }
-
var result = new List();
foreach (var plan in plans)
{