產生 3MF 套件Generate a 3MF package

重要 APIImportant APIs

本指南描述 3D 製造格式文件的結構以及如何使用 Windows.Graphics.Printing3D API 來建立和操作文件。This guide describes the structure of the 3D Manufacturing Format document and how it can be created and manipulated with the Windows.Graphics.Printing3D API.

什麼是 3MF?What is 3MF?

3D 製造格式 (3MF) 是一組慣例,基於製造 (3D 列印) 而使用 XML 來描述 3D 模型的外觀和結構。The 3D Manufacturing Format is a set of conventions for using XML to describe the appearance and structure of 3D models for the purpose of manufacturing (3D printing). 它定義一組組件 (一些是必要組件,一些是選用組件) 與其關係,目標是將所有必要資訊提供給 3D 製造裝置。It defines a set of parts (some required and some optional) and their relationships, with the goal of providing all necessary information to a 3D manufacturing device. 遵守 3D 製造格式的資料集可以儲存為副檔名為 .3mf 的檔案。A data set that adheres to the 3D Manufacturing Format can be saved as a file with the .3mf extension.

在 Windows 10 中,Windows.Graphics.Printing3D 命名空間中的 Printing3D3MFPackage 類別與單一 .3mf 檔案類似,而其他類別則對應到檔案中的特定 XML 元素。In Windows 10, the Printing3D3MFPackage class in the Windows.Graphics.Printing3D namespace is analogous to a single .3mf file, and other classes map to the particular XML elements in the file. 本指南描述如何透過程式設計方式建立和設定 3MF 文件的每個主要組件、如何使用 3MF 材質延伸,以及 Printing3D3MFPackage 物件如何轉換和儲存為 .3mf 檔案。This guide describes how each of the main parts of a 3MF document can be created and set programmatically, how the 3MF Materials Extension can be utilized, and how a Printing3D3MFPackage object can be converted and saved as a .3mf file. 如需 3MF 或 3MF 材質延伸標準的詳細資訊,請參閱 3MF 規格For more information on the standards of 3MF or the 3MF Materials Extension, see the 3MF Specification.

3MF 結構中的核心類別Core classes in the 3MF structure

Printing3D3MFPackage 類別代表完整 3MF 文件,而 3MF 文件的核心是其模型組件 (以 Printing3DModel 類別表示)。The Printing3D3MFPackage class represents a complete 3MF document, and at the core of a 3MF document is its model part, represented by the Printing3DModel class. 大部分我們希望指定的 3D 模型相關資訊,都將透過設定 Printing3DModel 類別屬性和其基本類別屬性來加以儲存。Most of the information we wish to specify about a 3D model will be stored by setting the properties of the Printing3DModel class and the properties of their underlying classes.

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


3MF 文件的模型組件可以保留中繼資料,並以字串的機碼/值組形式儲存在 Metadata 屬性中。The model part of a 3MF document can hold metadata in the form of key/value pairs of strings stored in the Metadata property. 會有一些預先定義的中繼資料名稱,但其他組可以新增為延伸的一部分 (詳述於 3MF 規格中)。There are a number of predefined names of metadata, but other pairs can be added as part of an extension (described in more detail in the 3MF specification). 是由套件的接收者 (3D 製造裝置) 判斷是否處理中繼資料和其作法,但最好在 3MF 套件中包括最多的基本資訊:It is up to the receiver of the package (a 3D manufacturing device) to determine whether and how to handle metadata, but it is good practice to include as much basic info as possible in the 3MF package:

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

網格資料Mesh data

在本指南的內容中,網格是透過一組頂點所建構的 3D 幾何主體 (但它不需要顯示為單一純色)。In the context of this guide, a mesh is a body of 3-dimensional geometry constructed from a single set of vertices (though it does not have to appear as a single solid). 網格部分是以 Printing3DMesh 類別表示。A mesh part is represented by the Printing3DMesh class. 有效的網格物件必須包含其所有頂點位置的資訊,以及存在於某些組頂點之間的所有三角形表面位置的資訊。A valid mesh object must contain information about the location of all of its vertices as well as all the triangle faces that exist between certain sets of vertices.

下列方法會新增網格的頂點,然後提供它們在 3D 空間中的位置︰The following method adds vertices to a mesh and then gives them locations in 3D space:

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;

下一種方法定義要跨這些頂點繪製的所有三角形︰The next method defines all of the triangles to be drawn across these vertices:

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



所有三角形都必須以逆時鐘順序定義其索引 (從網格物件外部檢視三角形時),使其 face-normal 向量指向外部。All triangles must have their indices defined in counter-clockwise order (when viewing the triangle from outside of the mesh object), so that their face-normal vectors point outward.

Printing3DMesh 物件包含一組有效的頂點和三角形時,應該將它新增至模型的 Meshes 屬性。When a Printing3DMesh object contains valid sets of vertices and triangles, it should then be added to the model's Meshes property. 套件中的所有 Printing3DMesh 物件都必須儲存至 Printing3DModel 類別的 Meshes 屬性。All Printing3DMesh objects in a package must be stored under the Meshes property of the Printing3DModel class.

// add the mesh to the model

建立材質Create materials

3D 模型可以保留多個材質的資料。A 3D model can hold data for multiple materials. 這個慣例是要利用可對單一列印工作使用多個材質的 3D 製造裝置。This convention is intended to take advantage of 3D manufacturing devices that can use multiple materials on a single print job. 也有多種類型的材質群組,各可以支援數個不同的個別材質。There are also multiple types of material gropus, each one capable of supporting a number of different individual materals. 每個材質群組都必須有唯一的參考識別碼,而且該群組內的每一種材質也必須有唯一識別碼。Each material group must have a unique reference id number, and each material within that group must also have a unique id.

模型內的不同網格物件接著可以參考這些材質。The different mesh objects within a model can then reference these materials. 此外,每個網格上的個別三角形可以指定不同的材質。Furthermore, individual triangles on each mesh can specify different materials. 甚至,不同的材質可在單一三角形內表示,而且每個三角形頂點都已獲指派不同的材質,而且表面材質計算為其中的漸層。Further still, different materials can even be represented within a single triangle, with each triangle vertex having a different material assigned to it and the face material calculated as the gradient between them.

本指南會先示範如何在其各自的材質群組內建立不同類型的材質,並將其儲存為模型物件上的資源。This guide will first show how to create different kinds of materials within their respective material groups and store them as resources on the model object. 接著,我們會將不同的材質指派給個別網格和個別三角形。Then, we will go about assigning different materials to individual meshes and individual triangles.

基本材質Base materials

預設材質類型是 [基本材質],其具有 [色彩材質] 值 (如下所述) 以及用來指定要使用的材質類型的 name 屬性。The default material type is Base Material, which has both a Color Material value (described below) and a name attribute that is intended to specify the type of material to use.

// 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
// material group index 1

// add material group to the basegroups property of the model


 3D 製造裝置將決定哪些可用的實體材質對應到 3MF 中儲存的哪些虛擬材質元素。 The 3D manufacturing device will determine which available physical materials map to which virtual material elements stored in the 3MF. 材質對應不一定要是 1:1︰如果 3D 印表機只使用一個材質,則會使用該材質列印整個模型 (不管物件或表面已獲指派不同的材質)。Material mapping doesn't have to be 1:1: if a 3D printer only uses one material, it will print the whole model in that material, regardless of which objects or faces were assigned different materials.

色彩材質Color materials

色彩材質基本材質類似,但不包含名稱。Color Materials are similar to Base Materials, but they do not contain a name. 因此,它們不會提供有關電腦應該使用的材質類型的指示。Thus, they give no instructions as to what type of material should be used by the machine. 它們只會保留色彩資料,並讓電腦選擇材質類型 (而電腦接著可能會提示使用者進行選擇)。They hold only color data, and let the machine choose the material type (and the machine may then prompt the user to choose). 在下面的程式碼中,會自行使用先前方法中的 colrMat 物件。In the code below, the colrMat objects from the previous method are used on their own.

// add ColorMaterials to the Color Material Group (with id 2)
var colorGroup = new Printing3DColorMaterialGroup(2);

// add the previous ColorMaterial objects to this ColorMaterialGroup

// add colorGroup to the ColorGroups property on the model

複合材質Composite materials

複合材質只會指示製造裝置使用不同基底材質的統一混合。Composite Materials simply instruct the manufacturing device to use a uniform mixture of different Base Materials. 每個複合材質群組都只能參考一個用來繪製組成部分的基本材質群組Each Composite Material Group must reference exactly one Base Material Group from which to draw ingredients. 此外,此群組內要設為可用的基本材質必須列在材質索引清單中,而每個複合材質在指定比例時都會參考該清單 (每個複合材質都只是基本材質的比例)。Additonally, the Base Materials within this group that are to be made available must be listed out in a Material Indices list, which each Composite Material will then reference when specifying the ratios (every Composite Material is simply a ratio of Base Materials).

// CompositeGroups
// create new composite material group with id = 3
var compositeGroup = new Printing3DCompositeMaterialGroup(3);

// indices point to base materials in BaseMaterialGroup with id =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

var compMat3 = new Printing3DCompositeMaterial();
// fraction adds to 1.0

var compMat4 = new Printing3DCompositeMaterial();
// fraction adds to 1.0

// add composites to group

// add group to model

紋理座標材質Texture coordinate materials

3MF 支援使用 2D 影像來將 3D 模型的表面著色。3MF supports the use of 2D images to color the surfaces of 3D models. 因此,模型的每個三角形表面可以傳送更多色彩資料 (而不是每個三角形頂點只有一個色彩值)。This way, the model can convey much more color data per triangle face (as opposed to having just one color value per triangle vertex). 色彩材質類似,紋理座標材質只會傳送色彩資料。Like Color Materials, texture coordinate materials only convery color data. 若要使用 2D 紋理,必須先宣告紋理資源︰To use a 2D texture, a texture resource must first be declared:

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

// assign this texture resource to a Printing3DModelTexture
var modelTexture = new Printing3DModelTexture();
modelTexture.TextureResource = texResource;


紋理資料屬於 3MF 套件本身,而不屬於套件內的模型組件。Texture data belongs to the 3MF Package itself, not to the model part within the package.

接下來,我們必須填寫 Texture3Coord 材質Next, we must fill out Texture3Coord Materials. 所有這些項目都參考紋理資源,並指定影像上的特定點 (UV 座標)。Each of these references a texture resource and specifies a particular point on the image (in UV coordinates).

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

var tex2CoordMaterial2 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial2.U = 1.0;
tex2CoordMaterial2.V = 1.0;

var tex2CoordMaterial3 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial3.U = 0.0;
tex2CoordMaterial3.V = 0.0;

var tex2CoordMaterial4 = new Printing3DTexture2CoordMaterial();
tex2CoordMaterial4.U = 1.0;
tex2CoordMaterial4.V = 0.0;

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

將材質對應到表面Map materials to faces

為了規定哪些材質對應到每個三角形上的哪些頂點,我們必須對我們模型的網格物件執行一些其他工作 (如果模型包含多個網格,則它們必須各有其分別指派的材質)。In order to dictate which materials are mapped to which vertices on each triangle, we must do some more work on the mesh object of our model (if a model contains multiple meshes, they must each have their materials assigned separately). 如上所述,會根據每個頂點、每個三角形來指派材質。As mentioned above, materials are assigned per-vertex, per-triangle. 請參閱下面的程式碼,以查看如何輸入和解譯這項資訊。Refer to the code below to see how this information is entered and interpreted.

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

元件和建置Components and build

元件結構可讓使用者在可列印的 3D 模型中放入多個網格物件。The component structure allows the user to place more than one mesh object in a printable 3D model. Printing3DComponent 物件包含單一網格以及其他元件的參考清單。A Printing3DComponent object contains a single mesh and a list of references to other components. 這實際上是 Printing3DComponentWithMatrix 物件的清單。This is actually a list of Printing3DComponentWithMatrix objects. Printing3DComponentWithMatrix 物件各包含一個 Printing3DComponent,而且重要的是套用至網格的轉換矩陣以及 Printing3DComponent 的內含元件。Printing3DComponentWithMatrix objects each contain a Printing3DComponent and, importantly, a transform matrix that applies to the mesh and contained components of said Printing3DComponent.

例如,汽車模型可能包含保留汽車主體網格的 "Body" Printing3DComponentFor example, a model of a car might consist of a "Body" Printing3DComponent that holds the mesh for the car's body. "Body" 元件接著可能會包含對四個不同 Printing3DComponentWithMatrix 物件的參考,這四個物件都參考具有 "Wheel" 網格的相同 Printing3DComponent,並且包含四個不同的轉換矩陣 (將車輪對應到汽車主體的四個不同位置)。The "Body" component may then contain references to four different Printing3DComponentWithMatrix objects, which all reference the same Printing3DComponent with the "Wheel" mesh and contain four different transform matrices (mapping the wheels to four different positions on the car's body). 在這個案例中,"Body" 網格和 "Wheel" 網格各只需要儲存一次,即使最終產品共具備五個網格也是一樣。In this scenario, the "Body" mesh and "Wheel" mesh would each only need to be stored once, even though the final product would feature five meshes in total.

所有 Printing3DComponent 物件都必須在模型的 Components 屬性中直接參考。All Printing3DComponent objects must be directly referenced in the model's Components property. 要用在列印工作的一個特定元件儲存在 Build 屬性中。The one particular component that is to be used in the printing job is stored in the Build Property.

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

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

儲存套件Save package

現在,我們已經有一個具有已定義材質和元件的模型,可以將它儲存到套件中。Now that we have a model, with defined materials and components, we can save it to the package.

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

此函式可確保已正確指定紋理。This function ensures the texture is specified correctly.

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

    await writer.StoreAsync();
    await writer.FlushAsync();
    return outputStream;

接下來,我們可以起始應用程式內的列印工作 (請參閱從應用程式進行 3D 列印),或將此 Printing3D3MFPackage 儲存為 .3mf 檔案。From here, we can either initiate a print job within the app (see 3D printing from your app), or save this Printing3D3MFPackage as a .3mf file.

下列方法採用已完成的 Printing3D3MFPackage,並將其資料儲存至 .3mf 檔案。The following method takes a finished Printing3D3MFPackage and saves its data to a .3mf file.

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) {

    // save the 3MF Package to an IRandomAccessStream
    using (var stream = await localPackage.SaveAsync()) {
        // go to the beginning of the stream

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

從 app 進行 3D 列印3D printing from your app
3D 列印 UWP 範例3D printing UWP sample