다음을 통해 공유


3D 제조 형식 패키지 생성하기

이 가이드는 3D 제조 형식(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 문서를 나타내며, Printing3DModel 클래스로 표현되는 모델 부분이 3MF 문서의 핵심입니다. 3D 모델에 대한 대부분의 정보는 Printing3DModel 클래스의 속성 및 기본 클래스의 속성을 설정하여 저장됩니다.

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

메타데이터

3MF 문서의 모델 부분은 메타데이터 속성에 저장된 문자열의 키/값 쌍 형식으로 메타데이터를 보유할 수 있습니다. 미리 정의된 메타데이터가 있지만 사용자 지정 쌍을 확장의 일부로 추가할 수 있습니다(상세 설명은 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 제조 디바이스를 활용하기 위한 규칙입니다. 여러 형식의 재료 그룹도 있으며, 각각 다양한 개별 재료를 지원할 수 있습니다.

각 재료 그룹에는 고유한 참조 ID 번호가 있어야 하며, 해당 그룹 내의 각 재료에도 고유한 ID가 있어야 합니다. 모델 내의 다른 메시 개체는 재료를 참조할 수 있습니다.

또한 각 메시의 개별 삼각형은 서로 다른 재료를 지정할 수 있으며, 각 삼각형 꼭짓점은 서로 다른 재료를 할당하고 얼굴 재료는 둘 사이의 그라데이션으로 계산하여 단일 삼각형 내에서도 나타낼 수 있습니다.

먼저 각 재료 그룹 내에서 다양한 종류의 재료를 만들고 모델 개체에 리소스로 저장하는 방법을 보여 줍니다. 그런 다음 개별 메시 및 개별 삼각형에 다른 재료를 할당합니다.

기본 재료

기본 재료 형식은 기본 재료이며, 색 재료 값(아래에 설명됨)과 사용할 재료의 형식을 지정하기 위한 이름 특성이 모두 있습니다.

// 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 재료를 작성해야 합니다. 이러한 각 항목은 텍스처 리소스를 참조하고 이미지의 특정 지점(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 개체에는 각각 Printing3DComponentPrinting3DComponent의 메시 및 포함된 구성 요소에 적용되는 변환 매트릭스가 포함됩니다.

예를 들어 자동차 모델은 차량 본문의 메시를 보유하는 "Body" Printing3DComponent로 구성될 수 있습니다. "Body" 구성 요소에는 4개의 다른 Printing3DComponentWithMatrix 개체에 대한 참조가 포함될 수 있으며 이 개체는 모두 동일한 Printing3DComponent를 참조하는 반면 "Wheel" 메시에는 4개의 서로 다른 변환 매트릭스가 포함될 수 있습니다(Wheel을 차량 Body의 4가지 위치로 매핑함). 이 시나리오에서 최종 제품에 총 5개의 메시가 포함되어 있더라도 "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 샘플