Skip to content

Commit 58f5a3f

Browse files
authored
Blueprint Improvements / Add support for exporting all building entities to one blueprint. (#484)
Upgrade Blueprint classes to support new logistic filter sections Fix issue when trying to export blueprint with modules. closes #471 Support for new burner fuel inventory Blueprints will now be exported with burner fuel inventory set (Default enabled, but can be disabled in preferences)
2 parents 92163c5 + 7a39840 commit 58f5a3f

File tree

9 files changed

+199
-10
lines changed

9 files changed

+199
-10
lines changed

Yafc.Model/Blueprints/Blueprint.cs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ public string ToJson() {
5858

5959
[Serializable]
6060
public class Blueprint(string label) {
61-
public const int VERSION = 0x01000000;
61+
public const long VERSION = 562949956632577;
6262

6363
public string item { get; set; } = "blueprint";
6464
public string label { get; set; } = label;
6565
public List<BlueprintEntity> entities { get; } = [];
6666
public List<BlueprintIcon> icons { get; } = [];
67-
public int version { get; set; } = VERSION;
67+
public long version { get; set; } = VERSION;
6868
}
6969

7070
[Serializable]
@@ -98,17 +98,44 @@ public void Set(IObjectWithQuality<Goods> goods) {
9898

9999
[Serializable]
100100
public class BlueprintEntity {
101-
[JsonPropertyName("entity_number")] public int index { get; set; }
101+
[JsonPropertyName("entity_number")]
102+
public int index { get; set; }
103+
102104
public string? name { get; set; }
103105
public string? quality { get; set; }
104106
public BlueprintPosition position { get; set; } = new BlueprintPosition();
105107
public int direction { get; set; }
108+
109+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
106110
public string? recipe { get; set; }
111+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
107112
public string? recipe_quality { get; set; }
108-
[JsonPropertyName("control_behavior")] public BlueprintControlBehavior? controlBehavior { get; set; }
113+
114+
[JsonPropertyName("control_behavior")]
115+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
116+
public BlueprintControlBehavior? controlBehavior { get; set; }
109117
public BlueprintConnection? connections { get; set; }
110-
[JsonPropertyName("request_filters")] public List<BlueprintRequestFilter> requestFilters { get; } = [];
118+
119+
[JsonPropertyName("request_filters")]
120+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
121+
public BlueprintRequestFilterSections? requestFilters { get; set; }
111122
public List<BlueprintItem> items { get; } = [];
123+
[JsonPropertyName("burner_fuel_inventory")]
124+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
125+
public BlueprintRequestFilterSection? burnerFuelInventory { get; set; }
126+
127+
public void SetFuel(string name, string? quality = null) {
128+
BlueprintRequestFilter fuelFilter = new() {
129+
index = 1,
130+
comparator = "=",
131+
name = name,
132+
quality = quality,
133+
count = 1
134+
};
135+
136+
burnerFuelInventory = new BlueprintRequestFilterSection();
137+
burnerFuelInventory.filters.Add(fuelFilter);
138+
}
112139

113140
public void Connect(BlueprintEntity other, bool red = true, bool secondPort = false, bool targetSecond = false) {
114141
ConnectSingle(other, red, secondPort, targetSecond);
@@ -130,13 +157,27 @@ private void ConnectSingle(BlueprintEntity other, bool red = true, bool secondPo
130157
}
131158
}
132159

160+
[Serializable]
161+
public class BlueprintRequestFilterSections {
162+
public List<BlueprintRequestFilterSection> sections { get; } = [];
163+
}
164+
165+
[Serializable]
166+
public class BlueprintRequestFilterSection {
167+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
168+
public int? index { get; set; }
169+
public List<BlueprintRequestFilter> filters { get; } = [];
170+
}
171+
133172
[Serializable]
134173
public class BlueprintRequestFilter {
135174
public string? name { get; set; }
136175
public string? quality { get; set; }
137176
public string? comparator { get; set; }
138177
public int index { get; set; }
139178
public int count { get; set; }
179+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
180+
public int? max_count { get; set; }
140181
}
141182

142183
[Serializable]

Yafc.Model/Blueprints/BlueprintUtilities.cs

Lines changed: 120 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
35
using SDL2;
46
using Yafc.Model;
57

@@ -63,10 +65,18 @@ public static string ExportRequesterChests(string name, IReadOnlyList<(IObjectWi
6365
BlueprintEntity entity = new BlueprintEntity { index = i + 1, position = { x = (i * chest.size) + offset, y = 0 }, name = chest.name };
6466
blueprint.blueprint.entities.Add(entity);
6567

68+
var section = new BlueprintRequestFilterSection {
69+
index = 1,
70+
};
71+
72+
entity.requestFilters = new BlueprintRequestFilterSections();
73+
entity.requestFilters.sections.Add(section);
74+
75+
6676
for (int j = 0; j < chest.logisticSlotsCount; j++) {
6777
var (item, amount) = goods[index++];
68-
BlueprintRequestFilter filter = new BlueprintRequestFilter { index = j + 1, count = amount, name = item.target.name, quality = item.quality.name, comparator = "=" };
69-
entity.requestFilters.Add(filter);
78+
BlueprintRequestFilter filter = new BlueprintRequestFilter { index = j + 1, count = amount, max_count = amount, name = item.target.name, quality = item.quality.name, comparator = "=" };
79+
section.filters.Add(filter);
7080

7181
if (index >= goods.Count) {
7282
break;
@@ -76,4 +86,112 @@ public static string ExportRequesterChests(string name, IReadOnlyList<(IObjectWi
7686

7787
return ExportBlueprint(blueprint, copyToClipboard);
7888
}
89+
90+
private class PlacedEntity {
91+
public RecipeRow Recipe { get; }
92+
public int X { get; } // Top-left X coordinate
93+
public int Y { get; } // Top-left Y coordinate
94+
95+
public PlacedEntity(RecipeRow recipe, int x, int y) {
96+
Recipe = recipe;
97+
X = x;
98+
Y = y;
99+
}
100+
}
101+
102+
private class LayoutResult {
103+
public int TotalWidth { get; set; }
104+
public int TotalHeight { get; set; }
105+
public List<PlacedEntity> Placements { get; set; } = new();
106+
}
107+
108+
public static string ExportRecipiesAsBlueprint(string name, IEnumerable<RecipeRow> recipies, bool includeFuel, bool copyToClipboard = true) {
109+
// Sort buildings largest to smallest (by height then width) for better packing
110+
var entities = recipies
111+
.Where(r => r.entity is not null)
112+
.OrderByDescending(r => r.entity!.target.size)
113+
.ToList();
114+
115+
// Estimate total area and try square-ish dimensions
116+
int totalArea = entities.Sum(r => (r.entity!.target.width + 1) * (r.entity!.target.height + 1));
117+
int sideLengthEstimate = (int)Math.Ceiling(Math.Sqrt(totalArea));
118+
119+
// Try increasing row width until it fits all buildings efficiently
120+
int bestWidth = int.MaxValue;
121+
int bestHeight = int.MaxValue;
122+
LayoutResult? bestResult = null;
123+
124+
for (int tryWidth = sideLengthEstimate; tryWidth < sideLengthEstimate * 2; tryWidth++) {
125+
int x = 0, y = 0, rowHeight = 0;
126+
List<PlacedEntity> placed = new();
127+
128+
foreach (var b in entities) {
129+
if (x + b.entity!.target.width > tryWidth) {
130+
// Move to next row
131+
y += rowHeight + 1; // Add 1 for gap
132+
x = 0;
133+
rowHeight = 0;
134+
}
135+
136+
placed.Add(new PlacedEntity(b, x, y));
137+
x += b.entity!.target.width + 1; // Add gap
138+
rowHeight = Math.Max(rowHeight, b.entity!.target.height);
139+
}
140+
141+
int totalHeight = y + rowHeight;
142+
int totalWidth = tryWidth;
143+
144+
// Evaluate how square-like it is
145+
if (Math.Max(totalWidth, totalHeight) < Math.Max(bestWidth, bestHeight)) {
146+
bestResult = new LayoutResult {
147+
TotalWidth = totalWidth,
148+
TotalHeight = totalHeight,
149+
Placements = placed
150+
};
151+
bestWidth = totalWidth;
152+
bestHeight = totalHeight;
153+
}
154+
}
155+
156+
BlueprintString blueprint = new BlueprintString(name);
157+
int buildingIndex = 0;
158+
159+
foreach (var placement in bestResult!.Placements) {
160+
var recipe = placement.Recipe;
161+
162+
BlueprintEntity entity = new BlueprintEntity {
163+
index = buildingIndex,
164+
position = { x = placement.X, y = placement.Y },
165+
name = recipe.entity!.target.name,
166+
};
167+
168+
if (!recipe.recipe.Is<Mechanics>()) {
169+
entity.recipe = recipe.recipe.target.name;
170+
entity.recipe_quality = recipe.recipe.quality.name;
171+
}
172+
173+
if (includeFuel && recipe.fuel is not null && !recipe.fuel.target.isPower) {
174+
entity.SetFuel(recipe.fuel.target.name, recipe.fuel.quality.name);
175+
}
176+
177+
var modules = recipe.usedModules.modules;
178+
179+
if (modules != null) {
180+
int idx = 0;
181+
foreach (var (module, count, beacon) in modules) {
182+
if (!beacon) {
183+
BlueprintItem item = new BlueprintItem { id = { name = module.target.name, quality = module.quality.name } };
184+
item.items.inInventory.AddRange(Enumerable.Range(idx, count).Select(i => new BlueprintInventoryItem { stack = i }));
185+
entity.items.Add(item);
186+
idx += count;
187+
}
188+
}
189+
}
190+
191+
blueprint.blueprint.entities.Add(entity);
192+
buildingIndex += 1;
193+
}
194+
195+
return ExportBlueprint(blueprint, copyToClipboard);
196+
}
79197
}

Yafc.Model/Data/DataClasses.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,8 @@ public float Power(Quality quality)
534534
internal List<Entity> sourceEntities { get; set; } = null!;
535535
internal string? autoplaceControl { get; set; }
536536
public float heatingPower { get; internal set; }
537+
public int width { get; internal set; }
538+
public int height { get; internal set; }
537539
public int size { get; internal set; }
538540
internal override FactorioObjectSortOrder sortingOrder => FactorioObjectSortOrder.Entities;
539541
public override string type => "Entity";

Yafc.Parser/Data/FactorioDataDeserializer_Entity.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,15 +117,15 @@ private void ReadEnergySource(LuaTable? energySource, Entity entity, float defau
117117
}
118118
}
119119

120-
private static int GetSize(LuaTable box) {
120+
private static (int Width, int Height) GetDimensions(LuaTable box) {
121121
_ = box.Get(1, out LuaTable? topLeft);
122122
_ = box.Get(2, out LuaTable? bottomRight);
123123
_ = topLeft.Get(1, out float x0);
124124
_ = topLeft.Get(2, out float y0);
125125
_ = bottomRight.Get(1, out float x1);
126126
_ = bottomRight.Get(2, out float y1);
127127

128-
return Math.Max(MathUtils.Round(x1 - x0), MathUtils.Round(y1 - y0));
128+
return (MathUtils.Round(x1 - x0), MathUtils.Round(y1 - y0));
129129
}
130130

131131
private static void ParseModules(LuaTable table, EntityWithModules entity, AllowedEffects def) {
@@ -584,7 +584,9 @@ void parseEffect(LuaTable effect) {
584584
}
585585
}
586586

587-
entity.size = table.Get("selection_box", out LuaTable? box) ? GetSize(box) : 3;
587+
(entity.width, entity.height) = table.Get("selection_box", out LuaTable? box) ? GetDimensions(box) : (3, 3);
588+
589+
entity.size = Math.Max(entity.width, entity.height);
588590

589591
_ = table.Get("energy_source", out LuaTable? energySource);
590592

Yafc/Data/locale/en/yafc.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,7 @@ prefs-spoiling-rate=Spoiling rate:
345345
prefs-show-inaccessible-milestone-overlays=Show milestone overlays on inaccessible objects
346346
prefs-dark-mode=Dark mode
347347
prefs-autosave=Enable autosave (Saves when the window loses focus)
348+
prefs-export-entities-with-fuel-filter-set=Export all entities with fuel filter set
348349
prefs-goods-unit-simple=Simple Amount__1__
349350
prefs-goods-unit-custom=Custom: 1 unit equals
350351
prefs-goods-unit-from-belt=Set from belt
@@ -728,6 +729,7 @@ production-table-header-modules=Modules
728729
production-table-add-raw-recipe=Add raw recipe
729730
production-table-add-technology=Add technology
730731
production-table-export-to-blueprint=Export inputs and outputs to blueprint with constant combinators:
732+
production-table-export-entities-to-blueprint=Export entities to blueprint
731733
export-blueprint-amount-per=Amount per:
732734
export-blueprint-amount-per-second=second
733735
export-blueprint-amount-per-minute=minute

Yafc/Utils/Preferences.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public void Save() {
4343
}
4444
public ProjectDefinition[] recentProjects { get; set; } = [];
4545
public bool darkMode { get; set; }
46+
public bool exportEntitiesWithFuelFilter { get; set; } = true;
4647
public string language {
4748
get => _language;
4849
set {

Yafc/Windows/PreferencesScreen.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,10 @@ private static void DrawGeneral(ImGui gui) {
209209
if (gui.BuildCheckBox(LSs.PrefsAutosave, Preferences.Instance.autosaveEnabled, out newValue)) {
210210
Preferences.Instance.autosaveEnabled = newValue;
211211
}
212+
213+
if (gui.BuildCheckBox(LSs.PrefsExportEntitiesWithFuelFilterSet, Preferences.Instance.exportEntitiesWithFuelFilter, out newValue)) {
214+
Preferences.Instance.exportEntitiesWithFuelFilter = newValue;
215+
}
212216
}
213217

214218
protected override void ReturnPressed() => Close();

Yafc/Workspace/ProductionTable/ProductionTableView.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,11 @@ private static void ShowEntityDropdown(ImGui gui, RecipeRow recipe) {
560560
}
561561
}
562562
}
563+
564+
if (Preferences.Instance.exportEntitiesWithFuelFilter && recipe.fuel is not null && !recipe.fuel.target.isPower) {
565+
entity.SetFuel(recipe.fuel.target.name, recipe.fuel.quality.name);
566+
}
567+
563568
BlueprintString bp = new BlueprintString(recipe.recipe.target.locName) { blueprint = { entities = { entity } } };
564569
_ = SDL.SDL_SetClipboardText(bp.ToBpString());
565570
}
@@ -611,6 +616,15 @@ public override void BuildMenu(ImGui gui) {
611616
if (gui.BuildButton(LSs.ShoppingList) && gui.CloseDropdown()) {
612617
view.BuildShoppingList(null);
613618
}
619+
620+
if (gui.BuildButton(LSs.ProductionTableExportEntitiesToBlueprint) && gui.CloseDropdown()) {
621+
bool includeFuel = Preferences.Instance.exportEntitiesWithFuelFilter;
622+
var uniqueEntites = view
623+
.GetRecipesRecursive()
624+
.DistinctBy(row => (row.entity, row.recipe, includeFuel ? row.fuel : null));
625+
626+
_ = BlueprintUtilities.ExportRecipiesAsBlueprint(view.projectPage!.name, uniqueEntites, includeFuel);
627+
}
614628
}
615629
}
616630

changelog.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
Version:
1919
Date:
2020
Features:
21+
- Add support for exporting all building entities to one blueprint.
22+
- Support for new burner fuel inventory. Exported entities will now have burner fuel inventory slot set as filter (Configurable in preferences).
2123
Fixes:
24+
- Fix issue when trying to export blueprint with modules (Key "count" was not found in property tree...)
25+
Internal Changes:
26+
- Upgrade Blueprint classes to support new logistic filter sections.
2227
----------------------------------------------------------------------------------------------------------------------
2328
Version: 2.12.0
2429
Date: May 12th 2025

0 commit comments

Comments
 (0)