Share via


產生 3D 製造格式套件

本指南說明 3D Manufacturing Format (3MF) 檔案類型的結構,以及如何使用 Windows.Graphics.Printing3D API 來建立和操作它。

重要 API

什麼是 3D 製造格式?

3MF 是一組使用 XML 來描述製造 3D 模型 (3D 列印) 的外觀和結構的約定。 它定義了 3D 製造裝置的一組部件 (必要和選擇性) 及其關聯性。 符合 3MF 的資料集可以儲存為副檔名為 .3mf 的檔案。

Windows.Graphics.Printing3D 命名空間中的 Printing3D3MFPackage 類別類似於單一 .3mf 檔案,而其他類別則對應到 .3mf 檔案中的特定 XML 元素。 本指南介紹如何以程式設計方式建立和設定 3MF 檔案的每個主要部分、如何使用 3MF 材料擴充,以及如何將 Printing3D3MFPackage 物件轉換並儲存為 .3mf 檔案。 有關 3MF 或 3MF 材料擴充標準的更多資訊,請參閱 3MF 規範

3MF 結構中的核心類別

Printing3D3MFPackage 類別表示完整的 3MF 文件,3MF 文件的核心是其模型部分,由 Printing3DModel 類別表示。 有關 3D 模型的大部分資訊是透過設定 Printing3DModel 類別的屬性,及其基礎類別的屬性來儲存。

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

中繼資料

3MF 文件的模型部分可以保存中繼資料,其格式為儲存在 Metadata 屬性中的索引鍵/值組字串。 有預先定義的中繼資料,但可以新增自訂組作為擴充的一部分 (3MF 規範中有更詳細的描述)。 由套件的接收者 (3D 製造裝置) 決定是否以及如何處理中繼資料,但最好在 3MF 包中包含盡可能多的資訊。

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

網格資料

在本指南中,網格是由一組頂點建構的 3 維幾何體 (儘管它不必顯示為單一實體)。 網格部件由 Printing3DMesh 類別表示。 有效的網格物件必須包含所有頂點,以及某些頂點集之間存在的所有三角形面的位置的資訊。

以下方法將頂點新增至網格,然後為它們提供 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;
}

下一個方法定義了要跨這些頂點繪製的所有三角形:

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

}

注意

所有三角形都必須以逆時針順序定義其索引 (當從網格物件外部查看三角形時),以便它們的面法線向量指向外側。

當 Printing3DMesh 物件包含有效的頂點和三角形集時,應將其新增至模型的 Meshes 屬性中。 套件中的所有 Printing3DMesh 物件必須儲存在 Printing3DModel 類別的 Meshes 屬性下,如下所示。

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

建立材料

3D 模型可以保存多種材料的資料。 此約定旨在利用可在單一列印作業中使用多種材料的 3D 製造裝置。 還有多種類型的材料群組,每種材料群組都能夠支援多種不同的個別材料。

每個材料群組必須有一個唯一的參考識別碼號,而該群組內的每個材料也必須有一個唯一的識別碼。 然後,模型中的不同網格物件可以參考這些材料。

此外,每個網格上的各個三角形可以指定不同的材料,甚至可以在單個三角形內表示不同的材料,每個三角形頂點都分配有不同的材料,並且面材料計算為它們之間的梯度。

首先,我們將示範如何在各自的材料群組中建立不同類型的材料,並將其儲存為模型物件上的資源。 然後,我們會將不同的材料指派給個別網格和個別三角形。

基礎材料

預設材料類型是 Base Material,其具有 Color Material 值 (如下所述) 和用於指定要使用的材料類型的名稱屬性。

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

注意

 3D 製造裝置將確定哪些可用實體材料對應到 3MF 中儲存的哪些虛擬材料元素。 材料對應不一定是 1:1。 如果 3D 列印機僅使用一種材料,它將使用該材料列印整個模型,無論哪些物件或面被分配了不同的材料。

色彩材質

色彩材料基礎材料類似,但它們不包含名稱。 因此,它們不會提供機器應該使用何種材料類型的指示。 它們僅保存色彩資料,並讓機器選擇材料類型 (機器可能會提示使用者選擇)。 在下列範例中,會單獨使用先前方法中的 colrMat 物件。

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

複合材料

複合材料會指示製造裝置使用不同基礎材料的均勻混合物。 每個複合材料群組必須準確參考一個從中提取成分的基礎材料群組。 此外,該群組中要提供的基礎材料必須列在材料索引清單中,每種複合材料在指定比率時將參考該清單 (每種複合材料都是基礎材料的比例)。

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

紋理座標材料

3MF 支援使用 2D 影像為 3D 模型的表面著色。 這樣,模型可以為每個三角形面傳達更多的色彩資料 (而不是每個三角形頂點只有一個色彩值)。 與色彩材料一樣,紋理座標材料僅傳達色彩資料。 要使用 2D 紋理,必須先宣告紋理資源。

注意

紋理資料屬於 3MF 套件本身,而不屬於套件內的模型部件。

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

接下來,我們必須填寫 Texture3Coord Materials。 其中每一個都參考紋理資源並指定影像上的特定點 (在 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);

將材料對應到面

為了指定哪些材料對應到每個三角形上的哪些頂點,需要對模型的網格物件進行更多工作 (如果模型包含多個網格,則必須為每個網格單獨分配材料)。 如上所述,材料是依頂點、依三角形指派的。 以下範例顯示如何輸入和解釋此資訊。

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

元件和建置

元件結構允許使用者在可列印 3D 模型中放置多個網格物件。 Printing3DComponent 物件包含單一網格,和對其他元件的參考清單。 這其實是 Printing3DComponentWithMatrix 物件的清單。 Printing3DComponentWithMatrix 物件每個都包含 Printing3DComponent 和套用至網格的轉換矩陣,以及 Printing3DComponent 所包含的元件。

例如,汽車模型可能由「Body」Printing3DComponent 組成,該元件保存汽車車身的網格。 然後,「Body」元件可能包含對四個不同Printing3DComponentWithMatrix 物件的參考,這些物件都參考相同的 Printing3DComponent,而「Wheel」網格可能包含四個不同的轉換矩陣 (將車輪對應到車身上的四個不同位置)。 在這種情況下,「Body」網格和「Wheel」網格各只需要儲存一次,即使最終產品總共有五個網格。

所有 Printing3DComponent 物件都必須直接在模型的 Components 屬性中參考。 列印作業中要使用的一個特定元件儲存在 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);

儲存套件

現在我們有了一個具有定義的材料和元件的模型,我們可以將其保存到套件中。

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

以下函式可確保正確指定紋理。

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

從這裡,我們可以在應用程式中啟動列印作業 (請參閱從您的應用程式進行 3D 列印),或將此 Printing3D3MFPackage 儲存為 .3mf 檔案。

以下方法會取得完成的 Printing3D3MFPackage,並將其資料儲存到 .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);
        }
    }
}

從應用程式進行 3D 列印
3D 列印 UWP 樣本