Compartir a través de


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 el aspecto 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, consulte la especificación 3MF.

Clases principales en la estructura 3MF

La clase Printing3D3MFPackage representa un documento completo de 3MF y, en el núcleo de un documento 3MF, es su parte de modelo, representada por 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

La parte del 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 único sólido). Una parte de 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 siguiente método 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 siguiente método 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 orden contrario a las agujas del reloj (al ver el triángulo desde fuera del objeto de malla), de modo que sus vectores faciales apunten hacia fuera.

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 para varios materiales. Esta convención está pensada para aprovechar las ventajas de los dispositivos de fabricación 3D que pueden usar varios materiales en un solo trabajo de impresión. También hay varios tipos de grupos de materiales, cada uno capaz de apoyar 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. 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 incluso se pueden representar dentro de un único triángulo, con cada vértice de triángulo que tiene un material diferente asignado a ella y el material facial calculado como 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 Color Material (descrito a continuación) y un atributo name que está pensado para especificar el tipo de material que se va a 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 qué elementos de material virtual almacenados en la 3MF. La asignación de material no tiene que ser 1:1. Si una impresora 3D solo usa un material, imprimirá todo el modelo en ese material, independientemente de los objetos o caras a los que se asignaron materiales diferentes.

Materiales de color

Los materiales de color son similares a los materiales base, pero no contienen un nombre. Por lo tanto, no proporcionan instrucciones sobre qué tipo de material debe ser utilizado por la máquina. Solo contienen 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

Materiales compuestos indica 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 dibujar ingredientes. Además, los materiales base de este grupo que se van a poner a disposición deben aparecer en una lista índices de materiales, a los que hará referencia cada material compuesto al especificar las relaciones (cada material compuesto es una proporció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 colorear las superficies de los modelos 3D. De este modo, el modelo puede transmitir mucho más datos de color por cara de triángulo (en lugar de tener solo un 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 textura pertenecen al propio paquete 3MF, no a la parte del modelo dentro 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 de estos hace referencia a un recurso de textura y especifica un punto determinado en 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 caras

Para especificar qué materiales se asignan a los vértices de cada triángulo, se requiere 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értice, 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 sola malla y una lista de referencias a otros componentes. En realidad, se trata de una lista de objetos Printing3DComponentWithMatrix. Los objetos Printing3DComponentWithMatrix contienen una printing3DComponent y una matriz de transformación que se aplica a la malla y a los componentes contenidos de Printing3DComponent.

Por ejemplo, un modelo de un automóvil podría constar de un objeto Printing3DComponent "Body" que contiene la malla para el cuerpo del automóvil. A continuación, el componente "Body" puede contener referencias a cuatro objetos Printing3DComponentWithMatrix diferentes, que hacen referencia al mismo 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 automóvil). En este escenario, la malla "Body" y la malla "Wheel" solo necesitarían almacenarse una vez, aunque el producto final presentaría cinco mallas en total.

Todos los objetos Printing3DComponent se deben hacer referencia directamente a ellos en la propiedad Components del modelo. El único componente concreto que se va a 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 impresión 3D desde la aplicación) o guardar este Printing3D3MFPackage como un archivo .3mf.

El método siguiente toma un printing3D3MFPackage terminado y guarda sus 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 3D desde la aplicación
Ejemplo de UWP de impresión 3D