Generare un pacchetto 3D Manufacturing Format

La presente guida descrive la struttura del tipo di file 3D Manufacturing Format (3MF) e il modo in cui l'API Windows.Graphics.Printing3D può essere usata per crearla e modificarla.

API importanti

Che cos'è il formato di produzione 3D?

3MF è un set di convenzioni per l'uso di XML per descrivere l'aspetto e la struttura dei modelli 3D per la produzione (stampa 3D). Definisce un set di parti (obbligatorio e facoltativo) e le relative relazioni a un dispositivo di produzione 3D. Un set di dati conforme al formato di produzione 3MF può essere salvato come file con estensione 3mf.

La classe Printing3D3MFPackage nello spazio dei nomi Windows.Graphics.Printing3D è analoga a un singolo file 3mf e altre classi eseguono il mapping agli elementi XML specifici del file .3mf. Questa guida descrive come è possibile creare e impostare a livello di codice ogni parte principale di un documento 3MF, come utilizzare l'estensione materiali 3MF e come convertire e salvare un oggetto Printing3D3MFPackage come file con estensione 3mf. Per altre informazioni sugli standard di 3MF o sull'estensione dei materiali 3MF, vedere la specifica 3MF.

Classi principali nella struttura 3MF

La classe Printing3D3MFPackage rappresenta un documento 3MF completo e al centro di un documento 3MF è la parte del modello, rappresentata dalla classe Printing3DModel. La maggior parte delle informazioni da specificare su un modello 3D viene archiviata impostando le proprietà della classe Printing3DModel e le proprietà delle relative classi sottostanti.

var localPackage = new Printing3D3MFPackage();
var model = new Printing3DModel();
// specify scaling units for model data
model.Unit = Printing3DModelUnit.Millimeter;

Metadati UFX

La parte del modello di un documento 3MF può contenere metadati sotto forma di coppie chiave/valore di stringhe archiviate nella proprietà Metadati . Sono presenti metadati predefiniti, ma è possibile aggiungere coppie personalizzate come parte di un'estensione (descritta in modo più dettagliato nella specifica 3MF). Spetta al ricevitore del pacchetto (un dispositivo di produzione 3D) determinare se e come gestire i metadati, ma è consigliabile includere il maggior numero possibile di informazioni di base nel pacchetto 3MF.

model.Metadata.Add("Title", "Cube");
model.Metadata.Add("Designer", "John Smith");
model.Metadata.Add("CreationDate", "1/1/2016");

Dati Mesh

In questa guida, una mesh è un corpo di geometria tridimensionale costruito da un singolo set di vertici (anche se non deve apparire come un singolo solido). Una parte mesh è rappresentata dalla classe Printing3DMesh. Un oggetto mesh valido deve contenere informazioni sulla posizione di tutti i relativi vertici, nonché su tutte le facce del triangolo presenti tra determinati set di vertici.

Il metodo seguente aggiunge vertici a una mesh e quindi assegna loro posizioni nello spazio 3D.

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

Questo metodo successivo definisce tutti i triangoli da disegnare tra questi vertici:

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);
    }

}

Nota

Tutti i triangoli devono avere i relativi indici definiti in senso antiorario (quando si visualizza il triangolo dall'esterno dell'oggetto mesh), in modo che i relativi vettori normali delle facce puntino verso l'esterno.

Quando un oggetto Printing3DMesh contiene set validi di vertici e triangoli, deve essere aggiunto alla proprietà Mesh del modello. Tutti gli oggetti Printing3DMesh in un pacchetto devono essere archiviati nella proprietà Mesh della classe Printing3DModel, come mostrato qui.

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

Crea materiali

Un modello 3D può contenere dati per più materiali. Questa convenzione è progettata per sfruttare i vantaggi dei dispositivi di produzione 3D che possono usare più materiali in un unico processo di stampa. Ci sono anche più tipi di gruppi di materiale, ciascuno in grado di supportare un certo numero di singoli materiali differenti.

Ogni gruppo di materiali deve avere un numero ID riferimento univoco e ogni materiale all'interno di tale gruppo deve avere anche un ID univoco. I diversi oggetti mesh all'interno di un modello possono quindi fare riferimento ai materiali.

Inoltre, i singoli triangoli su ogni mesh possono specificare materiali diversi e quest'ultimi possono anche essere rappresentati all'interno di un singolo triangolo, con ogni vertice triangolare a cui è assegnato un materiale diverso e il materiale del viso calcolato come sfumatura tra di essi.

Per prima cosa, mostreremo innanzitutto come creare diversi tipi di materiali all'interno dei rispettivi gruppi di materiali e archiviarli come risorse nell'oggetto modello. Verranno quindi assegnati materiali diversi a singole mesh e singoli triangoli.

Materiali di base

Il tipo di materiale predefinito è Materiale base, che ha sia un valore Materiale colore (descritto di seguito) e un attributo nome destinato a specificare il tipo di materiale da utilizzare.

// 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);

Nota

 Il dispositivo di produzione 3D determinerà quali materiali fisici disponibili corrispondono, in termini di mapping, a quali elementi di materiale virtuale memorizzati nel 3MF. Il mapping dei materiali non deve essere 1:1. Se una stampante 3D usa solo un materiale, stamperà l'intero modello in tale materiale, indipendentemente dagli oggetti o dalle facce a cui sono stati assegnati materiali diversi.

Materiali a colori

i Materiali colore sono simili ai Materiali base, ma non contengono un nome. Pertanto, non forniscono istruzioni sul tipo di materiale che deve essere utilizzato dalla macchina. Contengono solo dati di colore e consentono alla macchina di scegliere il tipo di materiale (e la macchina può quindi chiedere all'utente di scegliere). Nell'esempio seguente gli colrMat oggetti del metodo precedente vengono usati autonomamente.

// 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);

Materiali compositi

I Materiali compositi indicano semplicemente al dispositivo di produzione di utilizzare una combinazione uniforme di Materiali di base diversi. Ogni Gruppo di materiali compositi deve fare riferimento esattamente a un Gruppo materiale di base da cui disegnare ingredienti. In aggiunta, i Materiali base all'interno di questo gruppo da rendere disponibili devono essere riportati in un elenco di Indici materiali, a cui ogni Materiale composito farà quindi riferimento quando si specificano i rapporti (ogni Materiale composito è semplicemente un rapporto di Materiali base).

// 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);

Materiali delle coordinate delle trame

3MF supporta l'uso di immagini 2D per colorare le superfici dei modelli 3D. In questo modo, il modello può trasmettere molti più dati di colore per ogni faccia del triangolo (anziché avere un solo valore di colore per vertice triangolo). Come i Materiali colore, materiali delle coordinate delle trame si limitano a dati di colore convey. Per usare una trama 2D, è prima necessario dichiarare una risorsa texture.

Nota

I dati delle texture appartengono al pacchetto 3MF stesso, non alla parte del modello all'interno del pacchetto.

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

Successivamente, è necessario compilare Materiali Texture3Coord. Ognuno di questi fa riferimento a una risorsa texture e specifica un punto specifico sull'immagine (nelle coordinate UV).

// 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);

Mappare i materiali alle facce

Per specificare i materiali mappati ai vertici di ogni triangolo, è necessario maggior lavoro sull'oggetto mesh del modello (se un modello contiene più mesh, ognuno deve avere i relativi materiali assegnati separatamente). Come accennato in precedenza, i materiali vengono assegnati per vertice, per triangolo. Nell'esempio successivo viene illustrato come queste informazioni vengono immesse e interpretate.

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);
    }
}

Componenti e compilazioni

La struttura del componente consente all'utente di inserire più oggetti mesh in un modello 3D stampabile. Un oggetto Printing3DComponent contiene una singola mesh e un elenco di riferimenti ad altri componenti. Si tratta in realtà di un elenco di oggetti Printing3DComponentWithMatrix. Gli oggetti Printing3DComponentWithMatrix contengono ognuno un oggetto Printing3DComponent e, soprattutto, una matrice di trasformazione che si applica alla mesh e ai componenti contenuti di Printing3DComponent.

Ad esempio, un modello di un'automobile può essere costituito da un "Body" Printing3DComponent che contiene la mesh per il corpo dell'auto. Il componente "Body" può quindi contenere riferimenti a quattro diversi oggetti Printing3DComponentWithMatrix che fanno riferimento allo stesso Printing3DComponent mentre la mesh "Wheel" potrebbe contenere quattro matrici di trasformazione diverse (mapping delle ruote a quattro posizioni diverse sul corpo dell'auto). In questo scenario, la mesh "Body" e la mesh "Wheel" devono essere archiviate una sola volta, anche se il prodotto finale includerebbe cinque mesh in totale.

Tutti gli oggetti Printing3DComponent devono essere referenziati direttamente nella proprietà Componenti del modello. Il componente specifico da utilizzare nel processo di stampa viene archiviato nella proprietà Compilazione.

// 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);

Salvare il pacchetto

Ora che abbiamo un modello, con materiali e componenti definiti, possiamo salvarlo nel pacchetto.

// 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);

La funzione seguente garantisce che la trama venga specificata correttamente.

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

Da qui è possibile avviare un processo di stampa all'interno dell'app (vedere stampa 3D dalla propria app) o salvare il pacchetto Printing3D3MFPackage come file con estensione 3mf.

Il metodo seguente accetta un Printing3D3MFPackage e salva i dati in un file con estensione 3mf.

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);
        }
    }
}

Stampa 3D dall'app
Esempio UWP di stampa 3D