Share via


Gerar um pacote de formato de fabricação 3D

Este guia descreve a estrutura do tipo de arquivo 3MF (Formato de Fabricação 3D ) e como a API Windows.Graphics.Printing3D pode ser usada para criá-la e manipulá-la.

APIs importantes

O que é o Formato de Fabricação 3D?

3MF é um conjunto de convenções para usar XML para descrever a aparência e a estrutura de modelos 3D para fabricação (impressão 3D). Ele define um conjunto de partes (obrigatórias e opcionais) e suas relações, para um dispositivo de fabricação 3D. Um conjunto de dados que segue o 3MF pode ser salvo como um arquivo com a extensão .3mf.

A classe Printing3D3MFPackage no namespace Windows.Graphics.Printing3D é análoga a um único arquivo .3mf, enquanto outras classes são mapeadas para os elementos XML específicos no arquivo .3mf. Este guia descreve como cada uma das partes main de um documento 3MF pode ser criada e definida programaticamente, como a Extensão de Materiais 3MF pode ser usada e como um objeto Printing3D3MFPackage pode ser convertido e salvo como um arquivo .3mf. Para saber mais sobre os padrões de 3MF ou a Extensão de Materiais 3MF, consulte a Especificação 3MF.

Classes principais na estrutura 3MF

A classe Printing3D3MFPackage representa um documento 3MF completo, e no núcleo de um documento 3MF está sua parte do modelo, representada pela classe Printing3DModel. A maioria das informações sobre um modelo 3D é armazenada definindo as propriedades da classe Printing3DModel e as propriedades de suas classes subjacentes.

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

Metadados

A parte do modelo de documento 3MF pode conter metadados na forma de pares de chave/valor de cadeias de caracteres armazenadas na propriedade Metadados. Há metadados predefinidos, mas pares personalizados podem ser adicionados como parte de uma extensão (descritos mais detalhadamente na especificação 3MF). Cabe ao receptor do pacote (um dispositivo de fabricação 3D) determinar se e como lidar com metadados, mas é uma boa prática incluir o máximo de informações possível no pacote 3MF.

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

Dados de malha

Neste guia, uma malha é um corpo de geometria tridimensional construído a partir de um único conjunto de vértices (embora não precise aparecer como um único sólido). Uma parte de malha é representada pela classe Printing3DMesh. Um objeto de malha válido deve conter informações sobre o local de todos os vértices, bem como todas as faces de triângulo que existem entre determinados conjuntos de vértices.

O método a seguir adiciona vértices a uma malha e, em seguida, fornece-lhes locais no espaço 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 próximo método define todos os triângulos a serem desenhados entre esses 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);
    }

}

Observação

Todos os triângulos devem ter seus índices definidos no sentido anti-horário (ao exibir o triângulo fora do objeto de malha), para que seus vetores de face normal apontem para fora.

Quando um objeto Printing3DMesh contiver conjuntos válidos de vértices e triângulos, ele deverá ser adicionado à propriedade Meshes do modelo. Todos os objetos Printing3DMesh em um pacote devem ser armazenados na propriedade Meshes da classe Printing3DModel , conforme mostrado aqui.

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

Criar materiais

Um modelo 3D pode armazenar dados de vários materiais. Essa convenção destina-se a tirar proveito dos dispositivos de fabricação 3D que podem usar vários materiais em uma única tarefa de impressão. Há também vários tipos de grupos de materiais, cada um capaz de dar suporte a vários materiais individuais diferentes.

Cada grupo de materiais deve ter um número de ID de referência exclusivo e cada material dentro desse grupo também deve ter uma ID exclusiva. Os diferentes objetos de malha dentro de um modelo podem referenciar os materiais.

Além disso, triângulos individuais em cada malha podem especificar materiais diferentes e materiais diferentes podem até mesmo ser representados em um único triângulo, com cada vértice triângulo tendo um material diferente atribuído a ele e o material facial calculado como o gradiente entre eles.

Primeiro, mostraremos como criar diferentes tipos de materiais em seus respectivos grupos de materiais e armazená-los como recursos no objeto modelo. Em seguida, atribuiremos materiais diferentes a malhas individuais e triângulos individuais.

Materiais de base

O tipo de material padrão é Material de base, que tem tanto um valor de Material de cor (descrito abaixo) quanto um atributo de nome que se destina a especificar o tipo de material a ser usado.

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

Observação

 O dispositivo de fabricação 3D determinará quais materiais físicos disponíveis serão mapeados para quais elementos de materiais virtuais armazenados no 3MF. O mapeamento de material não precisa ser 1:1. Se uma impressora 3D usar apenas um material, ela imprimirá todo o modelo nesse material, independentemente de quais objetos ou rostos receberam materiais diferentes.

Materiais de cores

Materiais de cor são semelhantes aos Materiais de base, mas eles não contêm um nome. Assim, não dão nenhuma instrução sobre que tipo de material deve ser usado pela máquina. Eles contêm apenas dados de cor e permitem que o computador escolha o tipo de material (o computador pode solicitar que o usuário escolha). No exemplo a seguir, os colrMat objetos do método anterior são usados por conta própria.

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

Materiais de composição

Materiais compostos instruem o dispositivo de fabricação a usar uma combinação uniforme de materiais base diferentes. Cada Grupo de Material de Composição deve fazer referência a exatamente um Grupo de Material de Base do qual retira ingredientes. Additonally, os materiais base dentro desse grupo que devem ser disponibilizados devem ser listados em uma lista Índices de Materiais , que cada Material Composto referenciará ao especificar as proporções (cada Material Composto é uma proporção de Materiais 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);

Materiais de coordenadas de textura

3MF dá suporte ao uso de imagens 2D para colorir as superfícies de modelos 3D. Dessa forma, o modelo pode transmitir muito mais dados de cor por face de triângulo (ao contrário de ter apenas um valor de cor por vértice de triângulo). Assim como os Materiais de Cor, os materiais de coordenadas de textura transmitem apenas dados de cor. Para usar uma textura 2D, um recurso de textura deve primeiro ser declarado.

Observação

Os dados de textura pertencem ao pacote 3MF em si, não à parte do modelo dentro do pacote.

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

Em seguida, devemos preencher Materiais Texture3Coord. Cada um deles faz referência a um recurso de textura e especifica um ponto em particular na imagem (em 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);

Mapear materiais para faces

Para especificar quais materiais são mapeados para quais vértices em cada triângulo, mais trabalho é necessário no objeto de malha do modelo (se um modelo contiver várias malhas, cada um deles deverá ter seus materiais atribuídos separadamente). Conforme mencionado acima, materiais são atribuídos por vértice, por triângulo. O exemplo a seguir mostra como essas informações são inseridas e interpretadas.

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 e compilação

A estrutura do componente permite que o usuário coloque mais de um objeto de malha em um modelo 3D imprimível. O objeto Printing3DComponent contém uma única malha e uma lista de referências a outros componentes. Isso é realmente uma lista de objetos Printing3DComponentWithMatrix. Os objetos Printing3DComponentWithMatrix contêm um Printing3DComponent e uma matriz de transformação que se aplica à malha e aos componentes contidos do Printing3DComponent.

Por exemplo, um modelo de um carro pode consistir em um "Corpo" Printing3DComponent que mantém a malha para o corpo do carro. O componente "Corpo" pode conter referências a quatro objetos Printing3DComponentWithMatrix diferentes, que fazem referência ao mesmo Printing3DComponent , enquanto a malha "Roda" pode conter quatro matrizes de transformação diferentes (mapeando as rodas para quatro posições diferentes no corpo do carro). Nesse cenário, a malha "Corpo" e a malha "Roda" só precisariam ser armazenadas uma vez, embora o produto final apresentasse cinco malhas no total.

Todos os objetos Printing3DComponent devem ser referenciados diretamente na propriedade Components do modelo. Um componente específico a ser usado no trabalho de impressão é armazenado na propriedade Compilar.

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

Salvar o pacote

Agora que temos um modelo, com materiais definidos e componentes, podemos salvá-lo no pacote.

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

A função a seguir garante que a textura seja especificada corretamente.

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

Daqui, podemos iniciar um trabalho de impressão dentro do aplicativo (veja Impressão 3D a do seu aplicativo) ou salvar esse Printing3D3MFPackage como um arquivo .3mf.

O método a seguir pega um Printing3D3MFPackage concluído e salva os dados em um arquivo .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);
        }
    }
}

Impressão 3D a partir de seu aplicativo
Exemplo UWP de impressão 3D