Skip to content

Commit 2f26753

Browse files
authored
Merge pull request #101 from devedse/3MFMultiObject
3 mf multi object
2 parents 9195274 + 9de515d commit 2f26753

File tree

13 files changed

+398
-170
lines changed

13 files changed

+398
-170
lines changed

DeveMazeGeneratorCore.Coaster3MF/BambuStudioMetadata.cs

Lines changed: 100 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,64 @@
1+
using DeveMazeGeneratorCore.Coaster3MF.Models;
2+
13
namespace DeveMazeGeneratorCore.Coaster3MF
24
{
35
public static class BambuStudioMetadata
46
{
57
public static string ContentTypes => """
68
<?xml version="1.0" encoding="UTF-8"?>
79
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
8-
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
9-
<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>
10-
<Default Extension="png" ContentType="image/png"/>
11-
<Default Extension="gcode" ContentType="text/x.gcode"/>
10+
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
11+
<Default Extension="model" ContentType="application/vnd.ms-package.3dmanufacturing-3dmodel+xml"/>
12+
<Default Extension="png" ContentType="image/png"/>
13+
<Default Extension="gcode" ContentType="text/x.gcode"/>
1214
</Types>
1315
""";
1416

1517
public static string RootRelationships => """
1618
<?xml version="1.0" encoding="UTF-8"?>
1719
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
18-
<Relationship Target="/3D/3dmodel.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
19-
<Relationship Target="/Metadata/plate_1.png" Id="rel-2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"/>
20-
<Relationship Target="/Metadata/plate_1.png" Id="rel-4" Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-middle"/>
21-
<Relationship Target="/Metadata/plate_1_small.png" Id="rel-5" Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-small"/>
20+
<Relationship Target="/3D/3dmodel.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
21+
<Relationship Target="/Metadata/plate_1.png" Id="rel-2" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail"/>
22+
<Relationship Target="/Metadata/plate_1.png" Id="rel-4" Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-middle"/>
23+
<Relationship Target="/Metadata/plate_1_small.png" Id="rel-5" Type="http://schemas.bambulab.com/package/2021/cover-thumbnail-small"/>
2224
</Relationships>
2325
""";
2426

25-
public static string ModelRelationships => """
26-
<?xml version="1.0" encoding="UTF-8"?>
27-
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
28-
<Relationship Target="/3D/Objects/object_1.model" Id="rel-1" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
29-
</Relationships>
30-
""";
27+
public static string GetSingleModelRelationShip(ThreeMFModel model)
28+
{
29+
return $"""
30+
<Relationship Target="/3D/Objects/object_{model.ModelId}.model" Id="rel-{model.ModelId}" Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dmodel"/>
31+
""";
32+
}
3133

32-
public static string CutInformation => """
33-
<?xml version="1.0" encoding="utf-8"?>
34-
<objects>
35-
<object id="1">
36-
<cut_id id="0" check_sum="1" connectors_cnt="0"/>
37-
</object>
38-
</objects>
39-
""";
34+
public static string GetModelRelationships(List<ThreeMFPlate> plates)
35+
{
36+
return $"""
37+
<?xml version="1.0" encoding="UTF-8"?>
38+
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
39+
{string.Join(Environment.NewLine, plates.SelectMany(p => p.Models).Select(m => GetSingleModelRelationShip(m)))}
40+
</Relationships>
41+
""";
42+
}
43+
44+
public static string GetSingleCutRelationship(ThreeMFModel model)
45+
{
46+
return $"""
47+
<object id="{model.ModelId}">
48+
<cut_id id="0" check_sum="1" connectors_cnt="0"/>
49+
</object>
50+
""";
51+
}
52+
53+
public static string GetCutInformation(List<ThreeMFPlate> plates)
54+
{
55+
return $"""
56+
<?xml version="1.0" encoding="utf-8"?>
57+
<objects>
58+
{string.Join(Environment.NewLine, plates.SelectMany(p => p.Models).Select(m => GetSingleCutRelationship(m)))}
59+
</objects>
60+
""";
61+
}
4062

4163
public static string SliceInfo => """
4264
<?xml version="1.0" encoding="UTF-8"?>
@@ -46,18 +68,47 @@ public static class BambuStudioMetadata
4668
<header_item key="X-BBL-Client-Version" value="02.01.01.52"/>
4769
</header>
4870
</config>
71+
4972
""";
5073

51-
public static string GetModelSettings(int faceCount)
74+
75+
private static string GetThreeMFPlateModelInstance(int objectId)
5276
{
5377
return $"""
54-
<?xml version="1.0" encoding="UTF-8"?>
55-
<config>
56-
<object id="2">
78+
<model_instance>
79+
<metadata key="object_id" value="{objectId}"/>
80+
<metadata key="instance_id" value="0"/>
81+
<metadata key="identify_id" value="{objectId + 100}"/>
82+
</model_instance>
83+
""";
84+
}
85+
86+
public static string GetThreeMFPlate(int plateId, IEnumerable<int> objectIds)
87+
{
88+
return $"""
89+
<plate>
90+
<metadata key="plater_id" value="{plateId}"/>
91+
<metadata key="plater_name" value=""/>
92+
<metadata key="locked" value="false"/>
93+
<metadata key="filament_map_mode" value="Auto For Flush"/>
94+
<metadata key="filament_maps" value="1 1 1 1"/>
95+
<metadata key="thumbnail_file" value="Metadata/plate_1.png"/>
96+
<metadata key="thumbnail_no_light_file" value="Metadata/plate_no_light_1.png"/>
97+
<metadata key="top_file" value="Metadata/top_1.png"/>
98+
<metadata key="pick_file" value="Metadata/pick_1.png"/>
99+
{string.Join(Environment.NewLine, objectIds.Select(id => GetThreeMFPlateModelInstance(id)))}
100+
</plate>
101+
""";
102+
}
103+
104+
public static string GetThreeMFObject(ThreeMFModel model)
105+
{
106+
return $"""
107+
<object id="{model.ObjectId}">
57108
<metadata key="name" value="Maze_Coaster"/>
58109
<metadata key="extruder" value="1"/>
59-
<metadata key="face_count" value="{faceCount}"/>
60-
<part id="1" subtype="normal_part">
110+
<metadata face_count="{model.MeshData.Triangles.Count / 2}"/>
111+
<part id="{model.PartId}" subtype="normal_part">
61112
<metadata key="name" value="Maze_Coaster"/>
62113
<metadata key="matrix" value="1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1"/>
63114
<metadata key="source_file" value="maze_coaster.3mf"/>
@@ -66,33 +117,36 @@ public static string GetModelSettings(int faceCount)
66117
<metadata key="source_offset_x" value="9.5"/>
67118
<metadata key="source_offset_y" value="9.5"/>
68119
<metadata key="source_offset_z" value="2.5"/>
69-
<mesh_stat face_count="{faceCount}" edges_fixed="0" degenerate_facets="0" facets_removed="0" facets_reversed="0" backwards_edges="0"/>
120+
<mesh_stat face_count="{model.MeshData.Triangles.Count / 2}" edges_fixed="0" degenerate_facets="0" facets_removed="0" facets_reversed="0" backwards_edges="0"/>
70121
</part>
71122
</object>
72-
<plate>
73-
<metadata key="plater_id" value="1"/>
74-
<metadata key="plater_name" value=""/>
75-
<metadata key="locked" value="false"/>
76-
<metadata key="filament_map_mode" value="Auto For Flush"/>
77-
<metadata key="thumbnail_file" value="Metadata/plate_1.png"/>
78-
<metadata key="thumbnail_no_light_file" value="Metadata/plate_no_light_1.png"/>
79-
<metadata key="top_file" value="Metadata/top_1.png"/>
80-
<metadata key="pick_file" value="Metadata/pick_1.png"/>
81-
<model_instance>
82-
<metadata key="object_id" value="2"/>
83-
<metadata key="instance_id" value="0"/>
84-
<metadata key="identify_id" value="84"/>
85-
</model_instance>
86-
</plate>
123+
""";
124+
}
125+
126+
public static string GetAssembleItem(int objectId)
127+
{
128+
return $"""
129+
<assemble_item object_id="{objectId}" instance_id="0" transform="1 0 0 0 1 0 0 0 1 0 0 0" offset="0 0 0" />
130+
""";
131+
}
132+
133+
public static string GetModelSettings(List<ThreeMFPlate> plates)
134+
{
135+
return $"""
136+
<?xml version="1.0" encoding="UTF-8"?>
137+
<config>
138+
{string.Join(Environment.NewLine, plates.SelectMany(t => t.Models).Select(m => GetThreeMFObject(m)))}
139+
{string.Join(Environment.NewLine, plates.Select(p => GetThreeMFPlate(p.PlateId, p.Models.Select(t => t.ObjectId))))}
87140
<assemble>
88-
<assemble_item object_id="2" instance_id="0" transform="1 0 0 0 1 0 0 0 1 0 0 0" offset="0 0 0"/>
141+
{string.Join(Environment.NewLine, plates.SelectMany(p => p.Models).Select(m => GetAssembleItem(m.ObjectId)))}
89142
</assemble>
90143
</config>
144+
91145
""";
92146
}
93147

94148
public static string ProjectSettings => """
95-
{
149+
{
96150
"accel_to_decel_enable": "0",
97151
"accel_to_decel_factor": "50%",
98152
"activate_air_filtration": [

DeveMazeGeneratorCore.Coaster3MF/MazeCoaster3MF.cs

Lines changed: 71 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1+
using DeveMazeGeneratorCore.Coaster3MF.Models;
12
using DeveMazeGeneratorCore.Factories;
23
using DeveMazeGeneratorCore.Generators;
34
using DeveMazeGeneratorCore.Generators.Helpers;
45
using DeveMazeGeneratorCore.Generators.SpeedOptimization;
5-
using DeveMazeGeneratorCore.Imageification;
6+
using DeveMazeGeneratorCore.Helpers;
67
using DeveMazeGeneratorCore.InnerMaps;
78
using DeveMazeGeneratorCore.Mazes;
89
using DeveMazeGeneratorCore.PathFinders;
9-
using DeveMazeGeneratorCore.Structures;
1010

1111
namespace DeveMazeGeneratorCore.Coaster3MF
1212
{
@@ -15,13 +15,31 @@ public class MazeCoaster3MF
1515
private readonly MazeGeometryGenerator _geometryGenerator;
1616
private readonly ThreeMFPackageGenerator _packageGenerator;
1717

18+
19+
/// PartId 1, 3, 5, 7
20+
/// ObjectId 2, 4, 6, 8
21+
/// ModelId1, 2, 3, 4
22+
private int _partId = 1;
23+
private int _modelId = 1;
24+
private int _objectId = 2;
25+
26+
private ThreeMFModel GenerateModel(MeshData meshData)
27+
{
28+
var retval = new ThreeMFModel(_partId, _objectId, _modelId, meshData);
29+
_partId += 2;
30+
_objectId += 2;
31+
_modelId++;
32+
return retval;
33+
}
34+
35+
1836
public MazeCoaster3MF()
1937
{
2038
_geometryGenerator = new MazeGeometryGenerator();
2139
_packageGenerator = new ThreeMFPackageGenerator();
2240
}
2341

24-
public void Generate3MFCoaster(int mazeSize, int? seed = null)
42+
public void Generate3MFCoaster(int mazeSize, int? seed = null, int? chunkItUp = null)
2543
{
2644
Console.WriteLine($"Generating {mazeSize}x{mazeSize} maze...");
2745

@@ -36,34 +54,51 @@ public void Generate3MFCoaster(int mazeSize, int? seed = null)
3654
Console.WriteLine($"Path found with {path.Count} points (seed: {seed ?? 1337})");
3755
Console.WriteLine("Generating 3MF file...");
3856

39-
// Generate the geometry data
40-
var meshData = _geometryGenerator.GenerateMazeGeometry(maze.InnerMap, path);
57+
var mazesToCoasterUp = new List<MazeWithPath>();
58+
var fullMazeWithPath = new MazeWithPath(maze.InnerMap, path);
4159

42-
var nonManifoldEdgeDetector = new NonManifoldEdgeDetector();
43-
if (mazeSize < 50)
44-
{
45-
var meshAnalyzeResult = nonManifoldEdgeDetector.AnalyzeMesh(meshData);
46-
Console.WriteLine($"Non-manifold edges detected:{Environment.NewLine}{meshAnalyzeResult.ToString("\t")}");
47-
}
48-
else if (mazeSize < 100)
60+
if (chunkItUp.HasValue && chunkItUp.Value > 0)
4961
{
50-
var meshAnalyzeResult = nonManifoldEdgeDetector.AnalyzeMeshOnlyBorderEdges(meshData);
51-
Console.WriteLine($"Border edges: {meshAnalyzeResult.Count}");
62+
Console.WriteLine($"Chunking up the maze into {chunkItUp.Value} parts...");
63+
var splittedMaze = MazeSplitter.SplitUpMazeIntoChunks(fullMazeWithPath, chunkItUp.Value).ToList();
64+
mazesToCoasterUp.AddRange(splittedMaze);
65+
66+
var og = fullMazeWithPath.InnerMap[1, 1];
67+
var thing = splittedMaze[0].InnerMap[1, 1];
5268
}
5369
else
5470
{
55-
Console.WriteLine("Skipping non-manifold edge detection for large maze size.");
71+
Console.WriteLine("No chunking up applied.");
72+
mazesToCoasterUp.Add(fullMazeWithPath);
5673
}
5774

5875

76+
77+
78+
int plateNumber = 0;
79+
// Bunch up the mesh into plates (4 coasters per plate)
80+
var plates = mazesToCoasterUp.Chunk(4).Select(t => {
81+
var plateModels = t.Select(mwp =>
82+
{
83+
// Generate the geometry data for each maze chunk
84+
var meshData = _geometryGenerator.GenerateMazeGeometry(mwp.InnerMap, mwp.Path);
85+
Validate(mazeSize, meshData);
86+
return GenerateModel(meshData);
87+
}).ToList();
88+
return new ThreeMFPlate(Interlocked.Increment(ref plateNumber), plateModels);
89+
}).ToList();
90+
91+
5992
// Generate filename with triangle and vertex counts
6093
var usedSeed = seed ?? 1337;
61-
var filename = $"maze_coaster_{mazeSize}x{mazeSize}_seed{usedSeed}_{meshData.Triangles.Count}tri_{meshData.Vertices.Count}vert.3mf";
94+
var totalTriangles = plates.Sum(p => p.Models.Sum(m => m.MeshData.Triangles.Count));
95+
var totalVertices = plates.Sum(p => p.Models.Sum(m => m.MeshData.Vertices.Count));
96+
var filename = $"maze_coaster_{mazeSize}x{mazeSize}_seed{usedSeed}_{totalTriangles}tri_{totalVertices}vert.3mf";
6297

6398
Console.WriteLine($"Creating file: {filename}");
6499

65100
// Generate the 3MF file
66-
_packageGenerator.Create3MFFile(maze.InnerMap, path, meshData, filename);
101+
_packageGenerator.Create3MFFile(filename, plates, maze.InnerMap, path);
67102

68103
// Generate preview image
69104
// using (var fs = new FileStream($"{filename}.png", FileMode.Create))
@@ -72,6 +107,25 @@ public void Generate3MFCoaster(int mazeSize, int? seed = null)
72107
// }
73108
}
74109

110+
private static void Validate(int mazeSize, Models.MeshData meshData)
111+
{
112+
var nonManifoldEdgeDetector = new NonManifoldEdgeDetector();
113+
if (mazeSize < 50)
114+
{
115+
var meshAnalyzeResult = nonManifoldEdgeDetector.AnalyzeMesh(meshData);
116+
Console.WriteLine($"Non-manifold edges detected:{Environment.NewLine}{meshAnalyzeResult.ToString("\t")}");
117+
}
118+
else if (mazeSize < 100)
119+
{
120+
var meshAnalyzeResult = nonManifoldEdgeDetector.AnalyzeMeshOnlyBorderEdges(meshData);
121+
Console.WriteLine($"Border edges: {meshAnalyzeResult.Count}");
122+
}
123+
else
124+
{
125+
Console.WriteLine("Skipping non-manifold edge detection for large maze size.");
126+
}
127+
}
128+
75129
private Maze GenerateMaze(int mazeSize, int? seed)
76130
{
77131
var alg = new AlgorithmBacktrack2Deluxe2_AsByte();

0 commit comments

Comments
 (0)