mirror of
https://github.com/gumyr/build123d.git
synced 2025-12-06 02:30:55 -08:00
Added docs for 3mf/stl mesher
This commit is contained in:
parent
dcf71b6fef
commit
a058b45ca2
3 changed files with 182 additions and 103 deletions
|
|
@ -34,6 +34,7 @@ __all__ = [
|
|||
"Keep",
|
||||
"Kind",
|
||||
"LengthMode",
|
||||
"MeshType",
|
||||
"Mode",
|
||||
"PositionMode",
|
||||
"Select",
|
||||
|
|
|
|||
|
|
@ -163,6 +163,18 @@ class LengthMode(Enum):
|
|||
return f"<{self.__class__.__name__}.{self.name}>"
|
||||
|
||||
|
||||
class MeshType(Enum):
|
||||
"""3MF mesh types typically for 3D printing"""
|
||||
|
||||
OTHER = auto()
|
||||
MODEL = auto()
|
||||
SUPPORT = auto()
|
||||
SOLIDSUPPORT = auto()
|
||||
|
||||
def __repr__(self):
|
||||
return f"<{self.__class__.__name__}.{self.name}>"
|
||||
|
||||
|
||||
class PositionMode(Enum):
|
||||
"""Position along curve mode"""
|
||||
|
||||
|
|
|
|||
|
|
@ -1,90 +1,83 @@
|
|||
# pylint: skip-file
|
||||
"""++
|
||||
|
||||
Copyright (C) 2019 3MF Consortium (Original Author)
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
This file has been generated by the Automatic Component Toolkit (ACT) version 1.6.0-develop.
|
||||
|
||||
Abstract: This is an autogenerated Python application that demonstrates the
|
||||
usage of the Python bindings of the 3MF Library
|
||||
|
||||
Interface version: 2.2.0
|
||||
|
||||
"""
|
||||
"""
|
||||
Creating a 3MF object involves constructing a valid 3D model conforming to
|
||||
the 3MF specification. The resource hierarchy represents the various
|
||||
components that make up a 3MF object. The main components required to create
|
||||
a 3MF object are:
|
||||
build123d exporter/import for 3MF and STL
|
||||
|
||||
Wrapper: The wrapper is the highest-level component representing the
|
||||
entire 3MF model. It serves as a container for all other resources and
|
||||
provides access to the complete 3D model. The wrapper is the starting point
|
||||
for creating and managing the 3MF model.
|
||||
name: mesher.py
|
||||
by: Gumyr
|
||||
date: Aug 9th 2023
|
||||
|
||||
Model: The model is a core component that contains the geometric and
|
||||
non-geometric resources of the 3D object. It represents the actual 3D
|
||||
content, including geometry, materials, colors, textures, and other model
|
||||
information.
|
||||
desc:
|
||||
This module provides the Mesher class that implements exporting and importing
|
||||
both 3MF and STL mesh files. It uses the 3MF Consortium's Lib3MF library
|
||||
(see https://github.com/3MFConsortium/lib3mf).
|
||||
|
||||
Resources: Within the model, various resources are used to define
|
||||
different aspects of the 3D object. Some essential resources are:
|
||||
Creating a 3MF object involves constructing a valid 3D model conforming to
|
||||
the 3MF specification. The resource hierarchy represents the various
|
||||
components that make up a 3MF object. The main components required to create
|
||||
a 3MF object are:
|
||||
|
||||
a. Mesh: The mesh resource defines the geometry of the 3D object. It
|
||||
contains a collection of vertices, triangles, and other geometric
|
||||
information that describes the shape.
|
||||
Wrapper: The wrapper is the highest-level component representing the
|
||||
entire 3MF model. It serves as a container for all other resources and
|
||||
provides access to the complete 3D model. The wrapper is the starting point
|
||||
for creating and managing the 3MF model.
|
||||
|
||||
b. Components: Components allow you to define complex structures by
|
||||
combining multiple meshes together. They are useful for hierarchical
|
||||
assemblies and instances.
|
||||
Model: The model is a core component that contains the geometric and
|
||||
non-geometric resources of the 3D object. It represents the actual 3D
|
||||
content, including geometry, materials, colors, textures, and other model
|
||||
information.
|
||||
|
||||
c. Materials: Materials define the appearance properties of the
|
||||
surfaces, such as color, texture, or surface finish.
|
||||
Resources: Within the model, various resources are used to define
|
||||
different aspects of the 3D object. Some essential resources are:
|
||||
|
||||
d. Textures: Textures are images applied to the surfaces of the 3D
|
||||
object to add detail and realism.
|
||||
a. Mesh: The mesh resource defines the geometry of the 3D object. It
|
||||
contains a collection of vertices, triangles, and other geometric
|
||||
information that describes the shape.
|
||||
|
||||
e. Colors: Colors represent color information used in the 3D model,
|
||||
which can be applied to vertices or faces.
|
||||
b. Components: Components allow you to define complex structures by
|
||||
combining multiple meshes together. They are useful for hierarchical
|
||||
assemblies and instances.
|
||||
|
||||
Build Items: Build items are the instances of resources used in the 3D
|
||||
model. They specify the usage of resources within the model. For example, a
|
||||
build item can refer to a specific mesh, material, and transformation to
|
||||
represent an instance of an object.
|
||||
c. Materials: Materials define the appearance properties of the
|
||||
surfaces, such as color, texture, or surface finish.
|
||||
|
||||
Metadata: Metadata provides additional information about the model, such
|
||||
as author, creation date, and custom properties.
|
||||
d. Textures: Textures are images applied to the surfaces of the 3D
|
||||
object to add detail and realism.
|
||||
|
||||
Attachments: Attachments can include additional files or data associated
|
||||
with the 3MF object, such as texture images or other external resources.
|
||||
e. Colors: Colors represent color information used in the 3D model,
|
||||
which can be applied to vertices or faces.
|
||||
|
||||
Build Items: Build items are the instances of resources used in the 3D
|
||||
model. They specify the usage of resources within the model. For example, a
|
||||
build item can refer to a specific mesh, material, and transformation to
|
||||
represent an instance of an object.
|
||||
|
||||
Metadata: Metadata provides additional information about the model, such
|
||||
as author, creation date, and custom properties.
|
||||
|
||||
Attachments: Attachments can include additional files or data associated
|
||||
with the 3MF object, such as texture images or other external resources.
|
||||
|
||||
When creating a 3MF object, you typically start with the wrapper and then
|
||||
create or import the necessary resources, such as meshes, materials, and
|
||||
textures, to define the 3D content. You then organize the model using build
|
||||
items, specifying how the resources are used in the scene. Additionally, you
|
||||
can add metadata and attachments as needed to complete the 3MF object.
|
||||
|
||||
license:
|
||||
|
||||
Copyright 2023 Gumyr
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
When creating a 3MF object, you typically start with the wrapper and then
|
||||
create or import the necessary resources, such as meshes, materials, and
|
||||
textures, to define the 3D content. You then organize the model using build
|
||||
items, specifying how the resources are used in the scene. Additionally, you
|
||||
can add metadata and attachments as needed to complete the 3MF object.
|
||||
"""
|
||||
|
||||
|
||||
|
|
@ -96,8 +89,9 @@ import uuid
|
|||
import warnings
|
||||
from typing import Iterable, Union
|
||||
|
||||
from build123d import *
|
||||
from build123d import Shape, downcast
|
||||
from build123d.build_enums import MeshType, Unit
|
||||
from build123d.geometry import Color, Vector
|
||||
from build123d.topology import downcast, Compound, Shape, Shell, Solid
|
||||
from OCP.BRep import BRep_Tool
|
||||
from OCP.BRepBuilderAPI import (
|
||||
BRepBuilderAPI_MakeFace,
|
||||
|
|
@ -114,7 +108,16 @@ from py_lib3mf import Lib3MF
|
|||
from ocp_vscode import *
|
||||
|
||||
|
||||
class Mesh3MF:
|
||||
class Mesher:
|
||||
"""Mesher
|
||||
|
||||
Tool for exporting and import meshed objects stored in 3MF or STL files.
|
||||
|
||||
Args:
|
||||
unit (Unit, optional): model units. Defaults to Unit.MM.
|
||||
"""
|
||||
|
||||
# Translate b3d Units to Lib3MF ModelUnits
|
||||
map_b3d_to_3mf_unit = {
|
||||
Unit.MC: Lib3MF.ModelUnit.MicroMeter,
|
||||
Unit.MM: Lib3MF.ModelUnit.MilliMeter,
|
||||
|
|
@ -123,37 +126,51 @@ class Mesh3MF:
|
|||
Unit.FT: Lib3MF.ModelUnit.Foot,
|
||||
Unit.M: Lib3MF.ModelUnit.Meter,
|
||||
}
|
||||
# Translate Lib3MF ModelUnits to b3d Units
|
||||
map_3mf_to_b3d_unit = {v: k for k, v in map_b3d_to_3mf_unit.items()}
|
||||
|
||||
# Translate b3d MeshTypes to 3MF ObjectType
|
||||
map_b3d_mesh_type_3mf = {
|
||||
MeshType.OTHER: Lib3MF.ObjectType.Other,
|
||||
MeshType.MODEL: Lib3MF.ObjectType.Model,
|
||||
MeshType.SUPPORT: Lib3MF.ObjectType.Support,
|
||||
MeshType.SOLIDSUPPORT: Lib3MF.ObjectType.SolidSupport,
|
||||
}
|
||||
# Translate 3MF ObjectType to b3d MeshTypess
|
||||
map_3mf_to_b3d_mesh_type = {v: k for k, v in map_b3d_mesh_type_3mf.items()}
|
||||
|
||||
def __init__(self, unit: Unit = Unit.MM):
|
||||
self.unit = unit
|
||||
self.tessellations = None
|
||||
libpath = os.path.dirname(Lib3MF.__file__)
|
||||
self.wrapper = Lib3MF.Wrapper(os.path.join(libpath, "lib3mf"))
|
||||
self.model = self.wrapper.CreateModel()
|
||||
self.model.SetUnit(Mesh3MF.map_b3d_to_3mf_unit[unit])
|
||||
self.model.SetUnit(Mesher.map_b3d_to_3mf_unit[unit])
|
||||
self.meshes: list[Lib3MF.MeshObject] = []
|
||||
# self.mesh.MultiPropertyLayer
|
||||
|
||||
@property
|
||||
def model_unit(self) -> Unit:
|
||||
"""Unit used in the model"""
|
||||
return self.unit
|
||||
|
||||
@property
|
||||
def triangle_counts(self) -> list[int]:
|
||||
"""Number of triangles in each of the model's meshes"""
|
||||
return [m.GetTriangleCount() for m in self.meshes]
|
||||
|
||||
@property
|
||||
def vertex_counts(self) -> list[int]:
|
||||
"""Number of vertices in each of the models's meshes"""
|
||||
return [m.GetVertexCount() for m in self.meshes]
|
||||
|
||||
@property
|
||||
def mesh_count(self) -> int:
|
||||
"""Number of meshes in the model"""
|
||||
mesh_iterator: Lib3MF.MeshObjectIterator = self.model.GetMeshObjects()
|
||||
return mesh_iterator.Count()
|
||||
|
||||
@property
|
||||
def library_version(self) -> str:
|
||||
"""3MF Consortium Lib#MF version"""
|
||||
major, minor, micro = self.wrapper.GetLibraryVersion()
|
||||
return f"{major}.{minor}.{micro}"
|
||||
|
||||
|
|
@ -165,6 +182,17 @@ class Mesh3MF:
|
|||
metadata_type: str,
|
||||
must_preserve: bool,
|
||||
):
|
||||
"""add_meta_data
|
||||
|
||||
Add meta data to the models
|
||||
|
||||
Args:
|
||||
name_space (str): categorizer of different metadata entries
|
||||
name (str): metadata label
|
||||
value (str): metadata content
|
||||
metadata_type (str): metadata trype
|
||||
must_preserve (bool): metadata must not be removed if unused
|
||||
"""
|
||||
# Get an existing meta data group if there is one
|
||||
mdg = self.model.GetMetaDataGroup()
|
||||
if mdg is None:
|
||||
|
|
@ -176,6 +204,9 @@ class Mesh3MF:
|
|||
mdg.AddMetaData(name_space, name, value, metadata_type, must_preserve)
|
||||
|
||||
def add_code_to_metadata(self):
|
||||
"""Add the code calling this method to the 3MF metadata with the custom
|
||||
name space `build123d`, name equal to the base file name and the type
|
||||
as `python`"""
|
||||
caller_file = sys._getframe().f_back.f_code.co_filename
|
||||
code_file = open(caller_file, "r") # open code file in read mode
|
||||
source_code = code_file.read() # read whole file to a string
|
||||
|
|
@ -190,6 +221,7 @@ class Mesh3MF:
|
|||
)
|
||||
|
||||
def get_meta_data(self) -> list[str]:
|
||||
"""Retrieve all of the metadata"""
|
||||
meta_data_group = self.model.GetMetaDataGroup()
|
||||
meta_data_contents = []
|
||||
for i in range(meta_data_group.GetMetaDataCount()):
|
||||
|
|
@ -201,6 +233,7 @@ class Mesh3MF:
|
|||
return meta_data_contents
|
||||
|
||||
def get_meta_data_by_key(self, name_space: str, name: str) -> list[str]:
|
||||
"""Retrive the metadata value and type for the provided name space and name"""
|
||||
meta_data_group = self.model.GetMetaDataGroup()
|
||||
meta_data_contents = []
|
||||
meta_data = meta_data_group.GetMetaDataByKey(name_space, name)
|
||||
|
|
@ -208,25 +241,45 @@ class Mesh3MF:
|
|||
meta_data_contents.append(f"Value: {meta_data.GetValue()}")
|
||||
return meta_data_contents
|
||||
|
||||
def get_mesh_properties(self, mesh: Lib3MF.MeshObject) -> str:
|
||||
newline = "\n"
|
||||
properties = f"Name: {mesh.GetName()}{newline}"
|
||||
properties += f"Part Number: {mesh.GetPartNumber()}{newline}"
|
||||
properties += f"Type: {Lib3MF.ObjectType(mesh.GetType()).name}{newline}"
|
||||
uuid_valid, uuid_value = mesh.GetUUID()
|
||||
if uuid_valid:
|
||||
properties += f"UUID: {uuid_value}s{newline}"
|
||||
def get_mesh_properties(self) -> list[str]:
|
||||
"""Retrieve the properties from all the meshes"""
|
||||
properties = []
|
||||
for mesh in self.meshes:
|
||||
properties += f"Name: {mesh.GetName()}"
|
||||
properties += f"Part Number: {mesh.GetPartNumber()}"
|
||||
properties += f"Type: {Mesher.map_3mf_to_b3d_mesh_type[Lib3MF.ObjectType(mesh.GetType())].name}"
|
||||
uuid_valid, uuid_value = mesh.GetUUID()
|
||||
if uuid_valid:
|
||||
properties += f"UUID: {uuid_value}"
|
||||
|
||||
def add_shape(
|
||||
self,
|
||||
shape: Union[Shape, Iterable[Shape]],
|
||||
linear_deflection: float = 0.5,
|
||||
angular_deflection: float = 0.5,
|
||||
object_type: Lib3MF.ObjectType = Lib3MF.ObjectType.Model,
|
||||
mesh_type: MeshType = MeshType.MODEL,
|
||||
part_number: str = None,
|
||||
uuid: uuid = None,
|
||||
):
|
||||
def is_facet_forward(
|
||||
"""add_shape
|
||||
|
||||
Add a shape to the 3MF/STL file.
|
||||
|
||||
Args:
|
||||
shape (Union[Shape, Iterable[Shape]]): build123d object
|
||||
linear_deflection (float, optional): mesh control for edges. Defaults to 0.5.
|
||||
angular_deflection (float, optional): mesh control for non-planar surfaces. Defaults to 0.5.
|
||||
mesh_type (MeshType, optional): 3D printing use of mesh. Defaults to MeshType.MODEL.
|
||||
part_number (str, optional): part #. Defaults to None.
|
||||
uuid (uuid, optional): uuid from uuid package. Defaults to None.
|
||||
|
||||
Rasises:
|
||||
RuntimeError: 3mf mesh is invalid
|
||||
Warning: Degenerate shape skipped
|
||||
Warning: 3mf mesh is not manifold
|
||||
"""
|
||||
|
||||
def _is_facet_forward(
|
||||
points: tuple[gp_Pnt, gp_Pnt, gp_Pnt], shape_center: Vector
|
||||
) -> bool:
|
||||
# Create the facet
|
||||
|
|
@ -299,7 +352,7 @@ class Mesh3MF:
|
|||
mesh_3mf: Lib3MF.MeshObject = self.model.AddMeshObject()
|
||||
|
||||
# Add the meta data
|
||||
mesh_3mf.SetType(object_type)
|
||||
mesh_3mf.SetType(Mesher.map_b3d_mesh_type_3mf[mesh_type])
|
||||
if shape.label:
|
||||
mesh_3mf.SetName(shape.label)
|
||||
if part_number:
|
||||
|
|
@ -329,7 +382,7 @@ class Mesh3MF:
|
|||
)
|
||||
order = (
|
||||
[0, 2, 1]
|
||||
if not is_facet_forward(triangle_points, shape_center)
|
||||
if not _is_facet_forward(triangle_points, shape_center)
|
||||
else [0, 1, 2]
|
||||
)
|
||||
# order = [2, 1, 0] # Creates an invalid mesh
|
||||
|
|
@ -377,7 +430,8 @@ class Mesh3MF:
|
|||
components = self.model.AddComponentsObject()
|
||||
components.AddComponent(mesh_3mf, self.wrapper.GetIdentityTransform())
|
||||
|
||||
def get_shape(self, mesh_3mf: Lib3MF.MeshObject) -> Union[Shell, Solid]:
|
||||
def _get_shape(self, mesh_3mf: Lib3MF.MeshObject) -> Shape:
|
||||
"""Build build123d object from lib3mf mesh"""
|
||||
# Extract all the vertices
|
||||
gp_pnts = [gp_Pnt(*p.Coordinates[0:3]) for p in mesh_3mf.GetVertices()]
|
||||
|
||||
|
|
@ -406,13 +460,24 @@ class Mesh3MF:
|
|||
|
||||
return shape_obj
|
||||
|
||||
def read(self, file_name: str) -> list[Union[Shell, Solid]]:
|
||||
def read(self, file_name: str) -> list[Shape]:
|
||||
"""read
|
||||
|
||||
Args:
|
||||
file_name (str): file path
|
||||
|
||||
Raises:
|
||||
ValueError: Unknown file format - must be 3mf or stl
|
||||
|
||||
Returns:
|
||||
list[Shape]: build123d shapes extracted from mesh file
|
||||
"""
|
||||
input_file_format = file_name.split(".")[-1].lower()
|
||||
if input_file_format not in ["3mf", "stl"]:
|
||||
raise ValueError(f"Unknown file format {input_file_format}")
|
||||
reader = self.model.QueryReader(input_file_format)
|
||||
reader.ReadFromFile(file_name)
|
||||
self.unit = Mesh3MF.map_3mf_to_b3d_unit[self.model.GetUnit()]
|
||||
self.unit = Mesher.map_3mf_to_b3d_unit[self.model.GetUnit()]
|
||||
|
||||
# Extract 3MF meshes and translate to OCP meshes
|
||||
mesh_iterator: Lib3MF.MeshObjectIterator = self.model.GetMeshObjects()
|
||||
|
|
@ -422,7 +487,7 @@ class Mesh3MF:
|
|||
self.meshes.append(mesh_iterator.GetCurrentMeshObject())
|
||||
shapes = []
|
||||
for mesh in self.meshes:
|
||||
shape = self.get_shape(mesh)
|
||||
shape = self._get_shape(mesh)
|
||||
shape.label = mesh.GetName()
|
||||
triangle_properties = mesh.GetAllTriangleProperties()
|
||||
color_indices = []
|
||||
|
|
@ -445,14 +510,15 @@ class Mesh3MF:
|
|||
|
||||
return shapes
|
||||
|
||||
def _get_meshes(self):
|
||||
mesh_iterator: Lib3MF.MeshObjectIterator = self.model.GetMeshObjects()
|
||||
self.meshes: list[Lib3MF.MeshObject]
|
||||
for _i in range(mesh_iterator.Count()):
|
||||
mesh_iterator.MoveNext()
|
||||
self.meshes.append(mesh_iterator.GetCurrentMeshObject())
|
||||
|
||||
def write(self, file_name: str):
|
||||
"""_summary_
|
||||
|
||||
Args:
|
||||
file_name (str): file path
|
||||
|
||||
Raises:
|
||||
ValueError: Unknown file format - must be 3mf or stl
|
||||
"""
|
||||
output_file_format = file_name.split(".")[-1].lower()
|
||||
if output_file_format not in ["3mf", "stl"]:
|
||||
raise ValueError(f"Unknown file format {output_file_format}")
|
||||
Loading…
Add table
Add a link
Reference in a new issue