Share via


Generieren eines 3D-Fertigungsformatpakets

In diesem Leitfaden wird die Struktur des Dateityps 3D Manufacturing Format (3MF) und die Verwendung der Windows.Graphics.Printing3D-API zum Erstellen und Bearbeiten beschrieben.

Wichtige APIs

Was ist das 3D-Fertigungsformat?

3MF ist eine Reihe von Konventionen für die Verwendung von XML, um das Aussehen und die Struktur von 3D-Modellen für die Fertigung (3D-Druck) zu beschreiben. Es definiert eine Reihe von Teilen (erforderlich und optional) und deren Beziehungen zu einem 3D-Fertigungsgerät. Ein Dataset, das 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 Leitfaden wird beschrieben, wie jeder der Standard Teile eines 3MF-Dokuments programmgesteuert erstellt und festgelegt werden kann, wie die 3MF-Materialerweiterung verwendet werden kann und wie ein Printing3D3MFPackage-Objekt konvertiert und als 3mf-Datei gespeichert werden kann. Weitere Informationen zu den Standards der 3MF- oder der 3MF Materials-Erweiterung finden Sie in der 3MF-Spezifikation.

Hauptklassen in der 3MF-Struktur

Die Printing3D3MFPackage-Klasse stellt ein vollständiges 3MF-Dokument dar, und den Kern eines 3MF-Dokuments bildet der Modellbestandteil, der durch die Printing3DModel-Klasse dargestellt wird. Die meisten Informationen zu einem 3D-Modell werden gespeichert, indem die Eigenschaften der Printing3DModel-Klasse und die Eigenschaften der 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 (ausführlicher in der 3MF-Spezifikation beschrieben). Der Empfänger des Pakets (ein 3D-Fertigungsgerät) muss bestimmen, ob und wie Metadaten behandelt werden. Es empfiehlt sich jedoch, so viele Informationen wie möglich 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 3-dimensionaler Geometrie, der aus einem einzelnen Satz von Scheitelpunkten erstellt wird (obwohl es nicht als ein einzelner Festkörper erscheinen muss). Ein Gitterteil wird durch die Printing3DMesh-Klasse dargestellt. Ein gültiges Gitterobjekt muss Informationen über die Position aller Scheitelpunkte sowie aller Dreiecksflächen enthalten, die zwischen bestimmten Scheitelpunkten vorhanden sind.

Die folgende Methode fügt einem Gitter Scheitelpunkte hinzu und gibt ihnen dann Positionen im 3D-Raum.

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

Die Indizes aller Dreiecke müssen gegen den Uhrzeigersinn definiert werden (bei Sicht auf das Dreieck von außerhalb des Gitterobjekts), sodass ihre Oberflächennormalvektoren nach außen zeigen.

Wenn ein Printing3DMesh-Objekt gültige Sätze von Scheitelpunkten und Dreiecken enthält, sollte es der Meshes-Eigenschaft des Modells hinzugefügt werden. Alle Printing3DMesh-Objekte in einem Paket müssen wie hier gezeigt unter der Meshes-Eigenschaft der Printing3DModel-Klasse gespeichert werden.

// add the mesh to the model
model.Meshes.Add(mesh);

Erstellen von Materialen

Ein 3D-Modell kann Daten für mehrere Materialien enthalten. Diese Konvention ist für 3D-Fertigungsgeräte konzipiert, die mehrere Materialien für einen einzelnen Druckauftrag verwenden können. Es gibt auch mehrere Arten von Materialgruppen, von denen jede eine Reihe verschiedener einzelner Materialien unterstützen kann.

Jede Materialgruppe muss über eine eindeutige Referenz-ID-Nummer verfügen, und jedes Material innerhalb dieser Gruppe muss auch über eine eindeutige ID verfügen. Die verschiedenen Gitterobjekte in einem Modell können dann auf die Materialien verweisen.

Darüber hinaus können einzelne Dreiecke auf jedem Gitter verschiedene Materialien angeben, und unterschiedliche Materialien können sogar innerhalb eines einzelnen Dreiecks dargestellt werden, wobei jedem Dreiecksvertex ein anderes Material zugewiesen ist und das Flächenmaterial als Farbverlauf zwischen ihnen berechnet wird.

Zunächst zeigen wir, wie Verschiedene Arten von Materialien innerhalb der jeweiligen Materialgruppen erstellt und als Ressourcen im Modellobjekt gespeichert werden. Anschließend weisen wir einzelnen Gitternetzen und einzelnen Dreiecken verschiedene Materialien zu.

Basismaterialien

Der Standardmaterialtyp ist Base Material, der sowohl einen Color Material-Wert (nachfolgend beschrieben) als auch ein Namensattribut aufweist, mit dem der Typ des zu verwendenden Materials angegeben werden 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 jeweiligen im 3MF gespeicherten virtuellen Materialelementen zugeordnet werden. Die 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, welchen Objekten oder Gesichtern verschiedene Materialien zugewiesen wurden.

Farbmaterialien

Farbmaterialien ähneln den Basismaterialien, enthalten jedoch keinen Namen. Somit enthalten sie keine Anweisungen, welche Materialart vom Gerät verwendet werden soll. Sie enthalten nur Farbdaten und lassen den Computer den Materialtyp auswählen (der Computer kann den Benutzer zur Auswahl auffordern). Im folgenden Beispiel werden die colrMat Objekte aus der vorherigen Methode selbst 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);

Zusammengesetzte Materialien

Verbundwerkstoffe weisen das Herstellungsgerät an, eine einheitliche Mischung verschiedener Basismaterialien zu verwenden. Jede zusammengesetzte Materialgruppe muss auf genau eine Basismaterialgruppe verweisen, aus der die Bestandteile entnommen werden sollen. Darüber hinaus müssen die Basismaterialien innerhalb dieser Gruppe, die zur Verfügung gestellt werden sollen, in einer Liste der Materialindizes aufgeführt werden, auf die jedes Zusammengesetzte Material verweist, wenn die Verhältnisse angegeben werden (jedes Verbundwerkstoff 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 einzufärben. Auf diese Weise kann das Modell deutlich mehr Farbdaten pro Dreiecksfläche übermitteln (als bei Verwendung eines einzelnen Farbwerts 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 und 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 die Texture3Coord-Materialien angegeben werden. Jedes dieser Materialien verweist auf eine Texturressource und gibt einen bestimmten Punkt im 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);

Zuordnung von Materialien zu Oberflächen

Um anzugeben, welche Materialien welchen Scheitelpunkten auf den einzelnen Dreiecken zugeordnet werden, ist mehr Arbeit am Gitterobjekt des Modells erforderlich (wenn ein Modell mehrere Gitter enthält, müssen die Materialien jeweils separat zugewiesen werden). Wie bereits erwähnt, werden die Materialien pro Vertex, 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 Erstellung

Die Komponentenstruktur ermöglicht es dem Benutzer, mehrere Gitterobjekte in einem druckbaren 3D-Modell zu platzieren. Ein Printing3DComponent-Objekt enthält ein einzelnes Gitter und eine Liste der Verweise auf andere Komponenten. Hierbei handelt es sich eigentlich um eine Liste der Printing3DComponentWithMatrix-Objekte. Printing3DComponentWithMatrix-Objekte enthalten jeweils ein Printing3DComponent und eine Transformationsmatrix, die für das Gitter und die enthaltenen Komponenten des Printing3DComponent gilt.

Das Modell eines Autos könnte zum Beispiel aus einer „Karosserie“-Printing3DComponent bestehen, die das Gitter für die Karosserie enthält. Die "Body"-Komponente kann dann Verweise auf vier verschiedene Printing3DComponentWithMatrix-Objekte enthalten, die alle auf denselben Printing3DComponent verweisen, während das Gitter "Wheel" vier verschiedene Transformationsmatrizen enthalten kann (wobei die Räder vier verschiedenen Positionen auf dem Fahrzeugkörper zugeordnet werden). In diesem Szenario müssen die Gitter „Karosserie“ und „Reifen“ nur jeweils einmal gespeichert werden, obwohl das endgültige Produkt insgesamt fünf Gitter umfassen würde.

Auf alle Printing3DComponent-Objekte muss direkt in der Components-Eigenschaft des Modells verwiesen werden. Die spezifische Komponente, die für den 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

Das vorliegende Modell mit den definierten Materialien und Komponenten kann nun im Paket gespeichert werden.

// 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 korrekt 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;
}

Jetzt kann ein Druckauftrag in der App gestartet (siehe 3D-Drucken in der App) oder das Printing3D3MFPackage als 3MF-Datei gespeichert werden.

Die folgende Methode akzeptiert ein fertiges Printing3D3MFPackage und speichert die 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);
        }
    }
}

3D-Druck über Ihre App
Beispiel für 3D-Druck – UWP