Generieren eines 3D Manufacturing Format-Pakets
In diesem Handbuch wird die Struktur des 3D Manufacturing Format (3MF)-Dateityps und die Verwendung der Windows.Graphics.Printing3D-API zum Erstellen und Bearbeiten beschrieben.
Wichtige APIs
Was ist 3D Manufacturing Format?
3MF ist eine Reihe von Konventionen für die Verwendung von XML zur Beschreibung der Darstellung und Struktur von 3D-Modellen für die Herstellung (3D-Druck). Er definiert eine Reihe von Teilen (erforderlich und optional) und deren Beziehungen zu einem 3D-Fertigungsgerät. Ein Datensatz, der dem 3MF entspricht, kann als Datei mit der Erweiterung 3mf gespeichert werden.
Die Printing3D3MFPackage-Klasse im Windows.Graphics.Printing3D-Namespace entspricht einer einzelnen 3MF-Datei, während andere Klassen den bestimmten XML-Elementen in der 3mf-Datei zugeordnet sind. In diesem Handbuch wird beschrieben, wie jeder der Hauptteile eines 3MF-Dokuments erstellt und programmgesteuert erstellt und festgelegt werden kann, wie die Erweiterung 3MF Materials verwendet werden kann und wie ein Printing3D3MFPackage-Objekt konvertiert und als 3mf-Datei gespeichert werden kann. Weitere Informationen zu den Standards von 3MF oder der 3MF Materials Extension finden Sie in der 3MF-Spezifikation.
Kernklassen in der 3MF-Struktur
Die Printing3D3MFPackage-Klasse stellt ein vollständiges 3MF-Dokument dar, und im Kern eines 3MF-Dokuments ist der Modellteil, dargestellt durch die Printing3DModel-Klasse . Die meisten Informationen zu einem 3D-Modell werden gespeichert, indem die Eigenschaften der Printing3DModel-Klasse und die Eigenschaften ihrer zugrunde liegenden Klassen festgelegt werden.
var localPackage = new Printing3D3MFPackage();
var model = new Printing3DModel();
// specify scaling units for model data
model.Unit = Printing3DModelUnit.Millimeter;
Metadaten
Der Modellteil eines 3MF-Dokuments kann Metadaten in Form von Schlüssel-Wert-Paaren von Zeichenfolgen enthalten, die in der Metadata-Eigenschaft gespeichert sind. Es gibt vordefinierte Metadaten, aber benutzerdefinierte Paare können als Teil einer Erweiterung hinzugefügt werden (in der 3MF-Spezifikation ausführlicher beschrieben). Es liegt bei dem Empfänger des Pakets (einem 3D-Fertigungsgerät), um zu bestimmen, ob und wie Metadaten behandelt werden sollen, es empfiehlt sich jedoch, möglichst viele Informationen in das 3MF-Paket einzuschließen.
model.Metadata.Add("Title", "Cube");
model.Metadata.Add("Designer", "John Smith");
model.Metadata.Add("CreationDate", "1/1/2016");
Gitterdaten
In dieser Anleitung ist ein Gitter ein Körper aus 3dimensionalen Geometrien, die aus einem einzigen Satz von Scheitelpunkten erstellt wurden (obwohl es nicht als einzelnes Einfarbig erscheinen muss). Ein Gitterteil wird durch die Printing3DMesh-Klasse dargestellt. Ein gültiges Gitterobjekt muss Informationen über die Position aller Scheitelpunkte sowie alle Dreiecksflächen enthalten, die zwischen bestimmten Scheitelpunkten vorhanden sind.
Mit der folgenden Methode werden einem Gitter Scheitelpunkte hinzugefügt und anschließend Positionen im 3D-Raum zugewiesen.
private async Task GetVerticesAsync(Printing3DMesh mesh) {
Printing3DBufferDescription description;
description.Format = Printing3DBufferFormat.Printing3DDouble;
// have 3 xyz values
description.Stride = 3;
// have 8 vertices in all in this mesh
mesh.CreateVertexPositions(sizeof(double) * 3 * 8);
mesh.VertexPositionsDescription = description;
// set the locations (in 3D coordinate space) of each vertex
using (var stream = mesh.GetVertexPositions().AsStream()) {
double[] vertices =
{
0, 0, 0,
10, 0, 0,
0, 10, 0,
10, 10, 0,
0, 0, 10,
10, 0, 10,
0, 10, 10,
10, 10, 10,
};
// convert vertex data to a byte array
byte[] vertexData = vertices.SelectMany(v => BitConverter.GetBytes(v)).ToArray();
// write the locations to each vertex
await stream.WriteAsync(vertexData, 0, vertexData.Length);
}
// update vertex count: 8 vertices in the cube
mesh.VertexCount = 8;
}
Diese nächste Methode definiert alle Dreiecke, die über diese Scheitelpunkte gezogen werden sollen:
private static async Task SetTriangleIndicesAsync(Printing3DMesh mesh) {
Printing3DBufferDescription description;
description.Format = Printing3DBufferFormat.Printing3DUInt;
// 3 vertex indices
description.Stride = 3;
// 12 triangles in all in the cube
mesh.IndexCount = 12;
mesh.TriangleIndicesDescription = description;
// allocate space for 12 triangles
mesh.CreateTriangleIndices(sizeof(UInt32) * 3 * 12);
// get a datastream of the triangle indices (should be blank at this point)
var stream2 = mesh.GetTriangleIndices().AsStream();
{
// define a set of triangle indices: each row is one triangle. The values in each row
// correspond to the index of the vertex.
UInt32[] indices =
{
1, 0, 2,
1, 2, 3,
0, 1, 5,
0, 5, 4,
1, 3, 7,
1, 7, 5,
2, 7, 3,
2, 6, 7,
0, 6, 2,
0, 4, 6,
6, 5, 7,
4, 5, 6,
};
// convert index data to byte array
var vertexData = indices.SelectMany(v => BitConverter.GetBytes(v)).ToArray();
var len = vertexData.Length;
// write index data to the triangle indices stream
await stream2.WriteAsync(vertexData, 0, vertexData.Length);
}
}
Hinweis
Alle Dreiecke müssen ihre Indizes im Gegenzeigersinn (beim Anzeigen des Dreiecks von außerhalb des Gitterobjekts) definiert haben, damit ihre gesichtsnormalen Vektoren nach außen zeigen.
Wenn ein Printing3DMesh-Objekt gültige Scheitelpunkte und Dreiecke enthält, sollte es dann der Meshes-Eigenschaft des Modells hinzugefügt werden. Alle Printing3DMesh-Objekte in einem Paket müssen unter der Meshes-Eigenschaft der Printing3DModel-Klasse gespeichert werden, wie hier gezeigt.
// add the mesh to the model
model.Meshes.Add(mesh);
Erstellen von Materialien
Ein 3D-Modell kann Daten für mehrere Materialien enthalten. Diese Konvention soll die Vorteile von 3D-Fertigungsgeräten nutzen, die mehrere Materialien auf einem einzelnen Druckauftrag verwenden können. Es gibt auch mehrere Arten von Materialgruppen, die jeweils eine Reihe verschiedener einzelner Materialien unterstützen können.
Jede Materialgruppe muss über eine eindeutige Referenz-ID-Nummer verfügen, und jedes Material innerhalb dieser Gruppe muss auch eine eindeutige ID haben. Die verschiedenen Gitterobjekte innerhalb eines Modells können dann auf die Materialien verweisen.
Darüber hinaus können einzelne Dreiecke auf jedem Gitter unterschiedliche Materialien angeben und unterschiedliche Materialien können sogar innerhalb eines einzelnen Dreiecks dargestellt werden, wobei jedem Dreiecksvertex ein anderes Material zugewiesen ist und das Gesichtmaterial als Farbverlauf zwischen ihnen berechnet wird.
Zunächst zeigen wir, wie unterschiedliche Arten von Materialien innerhalb ihrer jeweiligen Materialgruppen erstellt und als Ressourcen im Modellobjekt gespeichert werden. Anschließend weisen wir einzelnen Gittern und einzelnen Dreiecken unterschiedliche Materialien zu.
Basismaterialien
Der Standardmaterialtyp ist "Basismaterial", das sowohl einen Farbmaterialwert (siehe unten) als auch ein Namensattribute aufweist, das den zu verwendenden Materialtyp angeben soll.
// add material group
// all material indices need to start from 1: 0 is a reserved id
// create new base materialgroup with id = 1
var baseMaterialGroup = new Printing3DBaseMaterialGroup(1);
// create color objects
// 'A' should be 255 if alpha = 100%
var darkBlue = Windows.UI.Color.FromArgb(255, 20, 20, 90);
var orange = Windows.UI.Color.FromArgb(255, 250, 120, 45);
var teal = Windows.UI.Color.FromArgb(255, 1, 250, 200);
// create new ColorMaterials, assigning color objects
var colrMat = new Printing3DColorMaterial();
colrMat.Color = darkBlue;
var colrMat2 = new Printing3DColorMaterial();
colrMat2.Color = orange;
var colrMat3 = new Printing3DColorMaterial();
colrMat3.Color = teal;
// setup new materials using the ColorMaterial objects
// set desired material type in the Name property
var baseMaterial = new Printing3DBaseMaterial {
Name = Printing3DBaseMaterial.Pla,
Color = colrMat
};
var baseMaterial2 = new Printing3DBaseMaterial {
Name = Printing3DBaseMaterial.Abs,
Color = colrMat2
};
// add base materials to the basematerialgroup
// material group index 0
baseMaterialGroup.Bases.Add(baseMaterial);
// material group index 1
baseMaterialGroup.Bases.Add(baseMaterial2);
// add material group to the basegroups property of the model
model.Material.BaseGroups.Add(baseMaterialGroup);
Hinweis
Das 3D-Fertigungsgerät bestimmt, welche verfügbaren physischen Materialien den in der 3MF gespeicherten virtuellen Materialelementen zugeordnet sind. Materialzuordnung muss nicht 1:1 sein. Wenn ein 3D-Drucker nur ein Material verwendet, wird das gesamte Modell in diesem Material gedruckt, unabhängig davon, welche Objekte oder Gesichter unterschiedlichen Materialien zugewiesen wurden.
Farbmaterialien
Farbmaterialien ähneln Basismaterialien, enthalten jedoch keinen Namen. Daher geben sie keine Anweisungen darüber, welche Art von Material von der Maschine verwendet werden soll. Sie enthalten nur Farbdaten, und lassen Sie die Maschine den Materialtyp auswählen (die Maschine fordert den Benutzer möglicherweise auf, dies auszuwählen). Im folgenden Beispiel werden die colrMat
Objekte aus der vorherigen Methode eigenständig verwendet.
// add ColorMaterials to the Color Material Group (with id 2)
var colorGroup = new Printing3DColorMaterialGroup(2);
// add the previous ColorMaterial objects to this ColorMaterialGroup
colorGroup.Colors.Add(colrMat);
colorGroup.Colors.Add(colrMat2);
colorGroup.Colors.Add(colrMat3);
// add colorGroup to the ColorGroups property on the model
model.Material.ColorGroups.Add(colorGroup);
Verbundmaterialien
Kompositmaterialien weisen das Herstellungsgerät an, eine einheitliche Mischung verschiedener Basismaterialien zu verwenden. Jede Verbundmaterialgruppe muss genau auf eine Basismaterialgruppe verweisen, aus der Zutaten gezeichnet werden sollen. Darüber hinaus müssen die Basismaterialien in dieser Gruppe, die zur Verfügung gestellt werden sollen, in einer Liste der Materialindizes aufgeführt sein, auf die jedes Zusammengesetzte Material verweist, wenn die Verhältnisse angegeben werden (jedes Kompositmaterial ist ein Verhältnis von Basismaterialien).
// CompositeGroups
// create new composite material group with id = 3
var compositeGroup = new Printing3DCompositeMaterialGroup(3);
// indices point to base materials in BaseMaterialGroup with id =1
compositeGroup.MaterialIndices.Add(0);
compositeGroup.MaterialIndices.Add(1);
// create new composite materials
var compMat = new Printing3DCompositeMaterial();
// fraction adds to 1.0
compMat.Values.Add(0.2); // .2 of first base material in BaseMaterialGroup 1
compMat.Values.Add(0.8); // .8 of second base material in BaseMaterialGroup 1
var compMat2 = new Printing3DCompositeMaterial();
// fraction adds to 1.0
compMat2.Values.Add(0.5);
compMat2.Values.Add(0.5);
var compMat3 = new Printing3DCompositeMaterial();
// fraction adds to 1.0
compMat3.Values.Add(0.8);
compMat3.Values.Add(0.2);
var compMat4 = new Printing3DCompositeMaterial();
// fraction adds to 1.0
compMat4.Values.Add(0.4);
compMat4.Values.Add(0.6);
// add composites to group
compositeGroup.Composites.Add(compMat);
compositeGroup.Composites.Add(compMat2);
compositeGroup.Composites.Add(compMat3);
compositeGroup.Composites.Add(compMat4);
// add group to model
model.Material.CompositeGroups.Add(compositeGroup);
Texturkoordinatenmaterialien
3MF unterstützt die Verwendung von 2D-Bildern, um die Oberflächen von 3D-Modellen zu befärben. Auf diese Weise kann das Modell viel mehr Farbdaten pro Dreiecksgesicht vermitteln (im Gegensatz zu nur einem Farbwert pro Dreiecksvertex). Wie Farbmaterialien vermitteln Texturkoordinatenmaterialien nur Farbdaten. Um eine 2D-Textur zu verwenden, muss zuerst eine Texturressource deklariert werden.
Hinweis
Texturdaten gehören zum 3MF-Paket selbst, nicht zum Modellteil innerhalb des Pakets.
// texture resource setup
Printing3DTextureResource texResource = new Printing3DTextureResource();
// name conveys the path within the 3MF document
texResource.Name = "/3D/Texture/msLogo.png";
// in this case, we reference texture data in the sample appx, convert it to
// an IRandomAccessStream, and assign it as the TextureData
Uri texUri = new Uri("ms-appx:///Assets/msLogo.png");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(texUri);
IRandomAccessStreamWithContentType iRandomAccessStreamWithContentType = await file.OpenReadAsync();
texResource.TextureData = iRandomAccessStreamWithContentType;
// add this testure resource to the 3MF Package
localPackage.Textures.Add(texResource);
// assign this texture resource to a Printing3DModelTexture
var modelTexture = new Printing3DModelTexture();
modelTexture.TextureResource = texResource;
Als Nächstes müssen wir Texture3Coord Materials ausfüllen. Jeder dieser Verweise verweist auf eine Texturressource und gibt einen bestimmten Punkt auf dem Bild an (in UV-Koordinaten).
// texture2Coord Group
// create new Texture2CoordMaterialGroup with id = 4
var tex2CoordGroup = new Printing3DTexture2CoordMaterialGroup(4);
// create texture materials:
// set up four tex2coordmaterial objects with four (u,v) pairs,
// mapping to each corner of the image:
var tex2CoordMaterial = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial.U = 0.0;
tex2CoordMaterial.V = 1.0;
tex2CoordGroup.Texture2Coords.Add(tex2CoordMaterial);
var tex2CoordMaterial2 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial2.U = 1.0;
tex2CoordMaterial2.V = 1.0;
tex2CoordGroup.Texture2Coords.Add(tex2CoordMaterial2);
var tex2CoordMaterial3 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial3.U = 0.0;
tex2CoordMaterial3.V = 0.0;
tex2CoordGroup.Texture2Coords.Add(tex2CoordMaterial3);
var tex2CoordMaterial4 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial4.U = 1.0;
tex2CoordMaterial4.V = 0.0;
tex2CoordGroup.Texture2Coords.Add(tex2CoordMaterial4);
// add our Printing3DModelTexture to the Texture property of the group
tex2CoordGroup.Texture = modelTexture;
// add metadata about the texture so that u,v values can be used
model.Metadata.Add("tex4", "/3D/Texture/msLogo.png");
// add group to groups on the model's material
model.Material.Texture2CoordGroups.Add(tex2CoordGroup);
Zuordnen von Materialien zu Gesichtern
Um anzugeben, welche Materialien für welche Scheitelpunkte auf jedem Dreieck zugeordnet werden, ist mehr Arbeit für das Gitterobjekt des Modells erforderlich (wenn ein Modell mehrere Gitter enthält, müssen sie jeweils ihre Materialien separat zugeordnet haben). Wie oben erwähnt, werden Materialien pro Scheitelpunkt pro Dreieck zugewiesen. Das folgende Beispiel zeigt, wie diese Informationen eingegeben und interpretiert werden.
private static async Task SetMaterialIndicesAsync(Printing3DMesh mesh) {
// declare a description of the material indices
Printing3DBufferDescription description;
description.Format = Printing3DBufferFormat.Printing3DUInt;
// 4 indices for material description per triangle
description.Stride = 4;
// 12 triangles total
mesh.IndexCount = 12;
mesh.TriangleMaterialIndicesDescription = description;
// create space for storing this data
mesh.CreateTriangleMaterialIndices(sizeof(UInt32) * 4 * 12);
{
// each row is a triangle face (in the order they were created)
// first column is the id of the material group, last 3 columns show which material id (within that group)
// maps to each triangle vertex (in the order they were listed when creating triangles)
UInt32[] indices =
{
// base materials:
// in the BaseMaterialGroup (id=1), the BaseMaterial with id=0 will be applied to these triangle vertices
1, 0, 0, 0,
1, 0, 0, 0,
// color materials:
// in the ColorMaterialGroup (id=2), the ColorMaterials with these ids will be applied to these triangle vertices
2, 1, 1, 1,
2, 1, 1, 1,
2, 0, 0, 0,
2, 0, 0, 0,
2, 0, 1, 2,
2, 1, 0, 2,
// composite materials:
// in the CompositeMaterialGroup (id=3), the CompositeMaterial with id=0 will be applied to these triangles
3,0,0,0,
3,0,0,0,
// texture materials:
// in the Texture2CoordMaterialGroup (id=4), each texture coordinate is mapped to the appropriate vertex on these
// two adjacent triangle faces, so that the square face they create displays the original rectangular image
4, 0, 3, 1,
4, 2, 3, 0,
};
// get the current (unassigned) vertex data as a stream and write our new 'indices' data to it.
var stream = mesh.GetTriangleMaterialIndices().AsStream();
var vertexData = indices.SelectMany(v => BitConverter.GetBytes(v)).ToArray();
var len = vertexData.Length;
await stream.WriteAsync(vertexData, 0, vertexData.Length);
}
}
Komponenten und Build
Die Komponentenstruktur ermöglicht es dem Benutzer, mehr als ein Gitterobjekt in einem druckbaren 3D-Modell zu platzieren. Ein Printing3DComponent -Objekt enthält ein einzelnes Gitter und eine Liste von Verweisen auf andere Komponenten. Dies ist tatsächlich eine Liste der Printing3DComponentWithMatrix-Objekte. Printing3DComponentWithMatrix-Objekte enthalten jeweils eine Printing3DComponent und eine Transformationsmatrix, die für das Gitter und die enthaltenen Komponenten des Printing3DComponent gilt.
Beispielsweise kann ein Modell eines Autos aus einem "Body" Printing3DComponent bestehen, das das Gitter für den Wagenkörper hält. Die "Body"-Komponente kann dann Verweise auf vier verschiedene Printing3DComponentWithMatrix-Objekte enthalten, die alle auf das gleiche Printing3DComponent verweisen, während das "Wheel"-Gitter vier verschiedene Transformationsmatrizen enthalten kann (wobei die Räder vier verschiedene Positionen auf dem Wagenkörper zugeordnet werden). In diesem Szenario müssten das "Body"-Gitter und das "Rad"-Gitter nur einmal gespeichert werden, obwohl das Endprodukt insgesamt fünf Gitter enthält.
Auf alle Printing3DComponent-Objekte muss direkt in der Components-Eigenschaft des Modells verwiesen werden. Die eine bestimmte Komponente, die im Druckauftrag verwendet werden soll, wird in der Build-Eigenschaft gespeichert.
// create new component
Printing3DComponent component = new Printing3DComponent();
// assign mesh to the component's mesh
component.Mesh = mesh;
// add component to the model's list of all used components
// a model can have references to multiple components
model.Components.Add(component);
// create the transform matrix
var componentWithMatrix = new Printing3DComponentWithMatrix();
// assign component to this componentwithmatrix
componentWithMatrix.Component = component;
// create an identity matrix
var identityMatrix = Matrix4x4.Identity;
// use the identity matrix as the transform matrix (no transformation)
componentWithMatrix.Matrix = identityMatrix;
// add component to the build property.
model.Build.Components.Add(componentWithMatrix);
Speichern des Pakets
Nachdem wir nun ein Modell mit definierten Materialien und Komponenten haben, können wir es im Paket speichern.
// save the model to the package:
await localPackage.SaveModelToPackageAsync(model);
// get the model stream
var modelStream = localPackage.ModelPart;
// fix any textures in the model file
localPackage.ModelPart = await FixTextureContentType(modelStream);
Die folgende Funktion stellt sicher, dass die Textur richtig angegeben wird.
/// <summary>
/// Ensure textures are saved correctly.
/// </summary>
/// <param name="modelStream">3dmodel.model data</param>
/// <returns></returns>
private async Task<IRandomAccessStream> FixTextureContentType(IRandomAccessStream modelStream) {
XDocument xmldoc = XDocument.Load(modelStream.AsStreamForRead());
var outputStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
var writer = new Windows.Storage.Streams.DataWriter(outputStream);
writer.UnicodeEncoding = Windows.Storage.Streams.UnicodeEncoding.Utf8;
writer.ByteOrder = Windows.Storage.Streams.ByteOrder.LittleEndian;
writer.WriteString("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
var text = xmldoc.ToString();
// ensure that content type is set correctly
// texture content can be either png or jpg
var replacedText = text.Replace("contenttype=\"\"", "contenttype=\"image/png\"");
writer.WriteString(replacedText);
await writer.StoreAsync();
await writer.FlushAsync();
writer.DetachStream();
return outputStream;
}
Von hier aus können wir entweder einen Druckauftrag innerhalb der App initiieren (siehe 3D-Druck aus Ihrer App), oder dieses Printing3D3MFPackage als 3MF-Datei speichern.
Die folgende Methode verwendet ein fertiges Printing3D3MFPackage und speichert seine Daten in einer 3MF-Datei.
private async void SaveTo3mf(Printing3D3MFPackage localPackage) {
// prompt the user to choose a location to save the file to
FileSavePicker savePicker = new FileSavePicker();
savePicker.DefaultFileExtension = ".3mf";
savePicker.SuggestedStartLocation = PickerLocationId.DocumentsLibrary;
savePicker.FileTypeChoices.Add("3MF File", new[] { ".3mf" });
var storageFile = await savePicker.PickSaveFileAsync();
if (storageFile == null) {
return;
}
// save the 3MF Package to an IRandomAccessStream
using (var stream = await localPackage.SaveAsync()) {
// go to the beginning of the stream
stream.Seek(0);
// read from the file stream and write to a buffer
using (var dataReader = new DataReader(stream)) {
await dataReader.LoadAsync((uint)stream.Size);
var buffer = dataReader.ReadBuffer((uint)stream.Size);
// write from the buffer to the storagefile specified
await FileIO.WriteBufferAsync(storageFile, buffer);
}
}
}