Generación de un paquete de formato de fabricación 3D

En esta guía se describe la estructura del tipo de archivo 3D Manufacturing Format (3MF) y cómo se puede usar la API Windows.Graphics.Printing3D para crearlo y manipularlo.

API importantes

¿Qué es el formato de fabricación 3D?

3MF es un conjunto de convenciones para usar XML para describir la apariencia y la estructura de los modelos 3D para la fabricación (impresión 3D). Define un conjunto de piezas (obligatorias y opcionales) y sus relaciones, en un dispositivo de fabricación 3D. Un conjunto de datos que se adhiere a la 3MF se puede guardar como un archivo con la extensión .3mf.

La clase Printing3D3MFPackage del espacio de nombres Windows.Graphics.Printing3D es análoga a un único archivo .3mf, mientras que otras clases se asignan a los elementos XML concretos del archivo .3mf. En esta guía se describe cómo se puede crear y establecer mediante programación cada una de las partes principales de un documento 3MF, cómo se puede usar la extensión de materiales 3MF y cómo se puede convertir y guardar un objeto Printing3D3MFPackage como un archivo .3mf. Para obtener más información sobre los estándares de 3MF o la extensión de materiales 3MF, consulta 3MF Specification (Especificación 3MF).

Clases principales de la estructura 3MF

La clase Printing3D3MFPackage representa un documento 3MF completo y en el centro de un documento 3MF hay su parte de modelo, que representa la clase Printing3DModel. La mayoría de la información sobre un modelo 3D se almacena estableciendo las propiedades de la clase Printing3DModel y las propiedades de sus clases subyacentes.

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

Metadatos

El componente de modelo de un documento 3MF puede contener metadatos en forma de pares clave-valor de cadenas almacenadas en la propiedad Metadata. Hay metadatos predefinidos, pero los pares personalizados se pueden agregar como parte de una extensión (que se describe con más detalle en la especificación 3MF). Es hasta el receptor del paquete (un dispositivo de fabricación 3D) para determinar si y cómo controlar los metadatos, pero es recomendable incluir la mayor cantidad de información posible en el paquete 3MF.

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

Datos de malla

En esta guía, una malla es un cuerpo de geometría 3 dimensional construida a partir de un único conjunto de vértices (aunque no tiene que aparecer como un solo sólido). Una parte de la malla se representa mediante la clase Printing3DMesh. Un objeto de malla válido debe contener información sobre la ubicación de todos los vértices, así como todas las caras de triángulo que existen entre determinados conjuntos de vértices.

El método siguiente agrega vértices a una malla y, a continuación, les proporciona ubicaciones en el espacio 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;
}

Este método siguiente define todos los triángulos que se van a dibujar en estos vértices:

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

Todos los triángulos deben tener sus índices definidos en el sentido contrario a las agujas del reloj (cuando se visualiza el triángulo desde fuera del objeto de malla), para que sus vectores normales de cara apunten hacia afuera.

Cuando un objeto Printing3DMesh contiene conjuntos válidos de vértices y triángulos, se debe agregar a la propiedad Meshes del modelo. Todos los objetos Printing3DMesh de un paquete deben almacenarse en la propiedad Meshes de la clase Printing3DModel , como se muestra aquí.

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

Crear materiales

Un modelo 3D puede contener datos de varios materiales. Esta convención está diseñada para sacar provecho de los dispositivos de fabricación 3D que pueden usar varios materiales en un único trabajo de impresión. También hay varios tipos de grupos de materiales, cada uno capaz de admitir una serie de materiales individuales diferentes.

Cada grupo de materiales debe tener un número de identificador de referencia único y cada material dentro de ese grupo también debe tener un identificador único. A continuación, los distintos objetos de malla dentro de un modelo pueden hacer referencia a los materiales.

Además, los triángulos individuales de cada malla pueden especificar diferentes materiales y diferentes materiales pueden incluso representarse dentro de un único triángulo, con cada vértice de triángulo con un material diferente asignado y el material facial calculado como el degradado entre ellos.

En primer lugar, mostraremos cómo crear diferentes tipos de materiales dentro de sus respectivos grupos de materiales y almacenarlos como recursos en el objeto de modelo. A continuación, asignaremos diferentes materiales a mallas individuales y triángulos individuales.

Materiales base

El tipo de material predeterminado es material base, que tiene un valor material de color (descrito a continuación) y un atributo de nombre que sirve para especificar el tipo de material que se debe usar.

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

 El dispositivo de fabricación 3D determinará qué materiales físicos disponibles se asignan a cada elemento de material virtual almacenado en 3MF. La asignación de material no tiene que ser 1:1. Si una impresora 3D solo utiliza un material, imprimirá todo el modelo en ese material, independientemente de los objetos o caras a los que se hayan asignado materiales diferentes.

Materiales de color

Los materiales de color son similares a los materiales base, pero no contienen ningún nombre. Por lo tanto, no proporcionan instrucciones sobre el tipo de material que debe usar la máquina. Contienen solo datos de color y permiten que la máquina elija el tipo de material (es posible que la máquina pida al usuario que elija). En el ejemplo siguiente, los colrMat objetos del método anterior se usan por sí mismos.

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

Materiales compuestos

Los materiales compuestos indican al dispositivo de fabricación que use una mezcla uniforme de diferentes materiales base. Cada grupo de materiales compuestos debe hacer referencia exactamente a un grupo de materiales base desde el que se deban dibujar ingredientes. Additonally, los materiales base dentro de este grupo que se van a poner a disposición deben aparecer en una lista índices de materiales , al que cada material compuesto hará referencia al especificar las relaciones (cada material compuesto es una relación de materiales 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);

Materiales de coordenadas de textura

3MF admite el uso de imágenes 2D para dar color a las superficies de modelos 3D. De esta forma, el modelo puede transmitir muchos más datos de color por cara de triángulo (en lugar de tener un solo valor de color por vértice de triángulo). Al igual que los materiales de color, los materiales de coordenadas de textura solo transmiten datos de color. Para usar una textura 2D, primero se debe declarar un recurso de textura.

Nota

Los datos de texturas pertenecen al paquete 3MF en sí y no a la parte del modelo del paquete.

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

A continuación, debemos rellenar Texture3Coord Materials. Cada uno hace referencia a un recurso de textura y especifica un punto concreto de la imagen (en coordenadas 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);

Asignar materiales a las caras

Para especificar qué materiales se asignan a qué vértices de cada triángulo se necesita más trabajo en el objeto de malla del modelo (si un modelo contiene varias mallas, cada uno debe tener asignados sus materiales por separado). Como se mencionó anteriormente, los materiales se asignan por vértices por triángulo. En el ejemplo siguiente se muestra cómo se escribe e interpreta esta información.

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

Componentes y compilación

La estructura de componentes permite al usuario colocar más de un objeto de malla en un modelo 3D imprimible. Un objeto Printing3DComponent contiene una única malla y una lista de referencias a otros componentes. En realidad, se trata de una lista de objetos Printing3DComponentWithMatrix. Los objetos Printing3DComponentWithMatrix contienen un Printing3DComponent y una matriz de transformación que se aplica a la malla y a los componentes contenidos de Printing3DComponent.

Por ejemplo, es posible que un modelo de un coche conste de un elemento Printing3DComponent de "cuerpo" que contenga la malla para la carrocería del coche. Después, el componente "Body" puede contener referencias a cuatro objetos Printing3DComponentWithMatrix diferentes, que hacen referencia a la misma Printing3DComponent mientras que la malla "Wheel" puede contener cuatro matrices de transformación diferentes (asignando las ruedas a cuatro posiciones diferentes en el cuerpo del coche). En este escenario, la malla de "cuerpo" y de "rueda" solo necesitaría almacenarse una vez, aunque el producto final presentara cinco mallas en total.

Se debe hacer referencia directamente a todos los objetos Printing3DComponent en la propiedad Components del modelo. El componente concreto que se debe usar en el trabajo de impresión se almacena en la propiedad Build.

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

Guardar el paquete

Ahora que tenemos un modelo con materiales y componentes definidos, podemos guardarlo en el paquete.

// 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 siguiente función garantiza que la textura se especifique correctamente.

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

Desde aquí, podemos iniciar un trabajo de impresión dentro de la aplicación (consulta 3D printing from your app [Impresión 3D desde la aplicación]), o guardar este paquete Printing3D3MFPackage como un archivo .3mf.

En el siguiente método se toma un paquete Printing3D3MFPackage terminado y se guardan los datos en un archivo .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);
        }
    }
}

Impresión en 3D desde la aplicación
Ejemplo de impresión en 3D de UWP