mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-10-03 15:03:16 +08:00
Store painted texture to 3MF file
CURA-12544 Also allows having multiple texture for multiple models while painting
This commit is contained in:
parent
5873222c15
commit
f0764134cc
@ -1,12 +1,23 @@
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
|
||||
|
||||
# FIXME: When the texture UV-unwrapping is done, these two values will need to be set to a proper value (suggest 4096 for both).
|
||||
TEXTURE_WIDTH = 512
|
||||
TEXTURE_HEIGHT = 512
|
||||
|
||||
class SliceableObjectDecorator(SceneNodeDecorator):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_texture = None
|
||||
|
||||
def isSliceable(self) -> bool:
|
||||
return True
|
||||
|
||||
def getPaintTexture(self, create_if_required: bool = True):
|
||||
if self._paint_texture is None and create_if_required:
|
||||
self._paint_texture = OpenGL.getInstance().createTexture(TEXTURE_WIDTH, TEXTURE_HEIGHT)
|
||||
return self._paint_texture
|
||||
|
||||
def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
|
||||
return type(self)()
|
||||
|
@ -58,6 +58,8 @@ catalog = i18nCatalog("cura")
|
||||
|
||||
MODEL_PATH = "3D/3dmodel.model"
|
||||
PACKAGE_METADATA_PATH = "Cura/packages.json"
|
||||
TEXTURES_PATH = "3D/Textures"
|
||||
MODEL_RELATIONS_PATH = "3D/_rels/3dmodel.model.rels"
|
||||
|
||||
class ThreeMFWriter(MeshWriter):
|
||||
def __init__(self):
|
||||
@ -110,7 +112,10 @@ class ThreeMFWriter(MeshWriter):
|
||||
transformation = Matrix(),
|
||||
exported_settings: Optional[Dict[str, Set[str]]] = None,
|
||||
center_mesh = False,
|
||||
scene: Savitar.Scene = None):
|
||||
scene: Savitar.Scene = None,
|
||||
archive: zipfile.ZipFile = None,
|
||||
model_relations_element: ET.Element = None,
|
||||
content_types_element: ET.Element = None):
|
||||
"""Convenience function that converts an Uranium SceneNode object to a SavitarSceneNode
|
||||
|
||||
:returns: Uranium Scene node.
|
||||
@ -153,9 +158,33 @@ class ThreeMFWriter(MeshWriter):
|
||||
else:
|
||||
savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tobytes())
|
||||
|
||||
texture = um_node.callDecoration("getPaintTexture")
|
||||
uv_coordinates_array = mesh_data.getUVCoordinatesAsByteArray()
|
||||
if uv_coordinates_array is not None and len(uv_coordinates_array) > 0:
|
||||
savitar_node.getMeshData().setUVCoordinatesPerVertexAsBytes(uv_coordinates_array, scene)
|
||||
if texture is not None and archive is not None and uv_coordinates_array is not None and len(uv_coordinates_array) > 0:
|
||||
texture_image = texture.getImage()
|
||||
if texture_image is not None:
|
||||
texture_path = f"{TEXTURES_PATH}/{id(um_node)}.png"
|
||||
|
||||
texture_buffer = QBuffer()
|
||||
texture_buffer.open(QBuffer.OpenModeFlag.ReadWrite)
|
||||
texture_image.save(texture_buffer, "PNG")
|
||||
|
||||
texture_file = zipfile.ZipInfo(texture_path)
|
||||
# Don't try to compress texture file, because the PNG is pretty much as compact as it will get
|
||||
archive.writestr(texture_file, texture_buffer.data())
|
||||
|
||||
savitar_node.getMeshData().setUVCoordinatesPerVertexAsBytes(uv_coordinates_array, texture_path, scene)
|
||||
|
||||
# Add texture relation to model relations file
|
||||
if model_relations_element is not None:
|
||||
ET.SubElement(model_relations_element, "Relationship",
|
||||
Target=texture_path, Id=f"rel{len(model_relations_element)+1}",
|
||||
Type="http://schemas.microsoft.com/3dmanufacturing/2013/01/3dtexture")
|
||||
|
||||
if content_types_element is not None:
|
||||
ET.SubElement(content_types_element, "Override", PartName=texture_path,
|
||||
ContentType="application/vnd.ms-package.3dmanufacturing-3dmodeltexture")
|
||||
|
||||
|
||||
# Handle per object settings (if any)
|
||||
stack = um_node.callDecoration("getStack")
|
||||
@ -193,7 +222,10 @@ class ThreeMFWriter(MeshWriter):
|
||||
continue
|
||||
savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node,
|
||||
exported_settings = exported_settings,
|
||||
scene = scene)
|
||||
scene = scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types_element)
|
||||
if savitar_child_node is not None:
|
||||
savitar_node.addChild(savitar_child_node)
|
||||
|
||||
@ -255,6 +287,9 @@ class ThreeMFWriter(MeshWriter):
|
||||
# Create Metadata/_rels/model_settings.config.rels
|
||||
metadata_relations_element = self._makeRelationsTree()
|
||||
|
||||
# Create model relations
|
||||
model_relations_element = self._makeRelationsTree()
|
||||
|
||||
# Let the variant add its specific files
|
||||
variant.add_extra_files(archive, metadata_relations_element)
|
||||
|
||||
@ -327,14 +362,20 @@ class ThreeMFWriter(MeshWriter):
|
||||
transformation_matrix,
|
||||
exported_model_settings,
|
||||
center_mesh = True,
|
||||
scene = savitar_scene)
|
||||
scene = savitar_scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
else:
|
||||
savitar_node = self._convertUMNodeToSavitarNode(node,
|
||||
transformation_matrix,
|
||||
exported_model_settings,
|
||||
scene = savitar_scene)
|
||||
scene = savitar_scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
|
||||
@ -346,6 +387,8 @@ class ThreeMFWriter(MeshWriter):
|
||||
self._storeElementTree(archive, "_rels/.rels", relations_element)
|
||||
if len(metadata_relations_element) > 0:
|
||||
self._storeElementTree(archive, "Metadata/_rels/model_settings.config.rels", metadata_relations_element)
|
||||
if len(model_relations_element) > 0:
|
||||
self._storeElementTree(archive, MODEL_RELATIONS_PATH, model_relations_element)
|
||||
except Exception as error:
|
||||
Logger.logException("e", "Error writing zip file")
|
||||
self.setInformation(str(error))
|
||||
|
@ -19,25 +19,21 @@ class PaintView(View):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_shader = None
|
||||
self._paint_texture = None
|
||||
|
||||
# FIXME: When the texture UV-unwrapping is done, these two values will need to be set to a proper value (suggest 4096 for both).
|
||||
self._tex_width = 512
|
||||
self._tex_height = 512
|
||||
self._current_paint_texture = None
|
||||
|
||||
def _checkSetup(self):
|
||||
if not self._paint_shader:
|
||||
shader_filename = os.path.join(PluginRegistry.getInstance().getPluginPath("PaintTool"), "paint.shader")
|
||||
self._paint_shader = OpenGL.getInstance().createShaderProgram(shader_filename)
|
||||
if not self._paint_texture:
|
||||
self._paint_texture = OpenGL.getInstance().createTexture(self._tex_width, self._tex_height)
|
||||
self._paint_shader.setTexture(0, self._paint_texture)
|
||||
|
||||
def addStroke(self, stroke_image: QImage, start_x: int, start_y: int) -> None:
|
||||
self._paint_texture.setSubImage(stroke_image, start_x, start_y)
|
||||
if self._current_paint_texture is not None:
|
||||
self._current_paint_texture.setSubImage(stroke_image, start_x, start_y)
|
||||
|
||||
def getUvTexDimensions(self):
|
||||
return self._tex_width, self._tex_height
|
||||
if self._current_paint_texture is not None:
|
||||
return self._current_paint_texture.getWidth(), self._current_paint_texture.getHeight()
|
||||
return 0, 0
|
||||
|
||||
def beginRendering(self) -> None:
|
||||
renderer = self.getRenderer()
|
||||
@ -48,4 +44,7 @@ class PaintView(View):
|
||||
node = Selection.getAllSelectedObjects()[0]
|
||||
if node is None:
|
||||
return
|
||||
|
||||
self._current_paint_texture = node.callDecoration("getPaintTexture")
|
||||
self._paint_shader.setTexture(0, self._current_paint_texture)
|
||||
paint_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData(), normal_transformation=node.getCachedNormalMatrix())
|
||||
|
@ -1,26 +1 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "Colorblind Assist Dark",
|
||||
"inherits": "cura-dark"
|
||||
},
|
||||
|
||||
"colors": {
|
||||
"x_axis": [212, 0, 0, 255],
|
||||
"y_axis": [64, 64, 255, 255],
|
||||
|
||||
"model_overhang": [200, 0, 255, 255],
|
||||
|
||||
"xray": [26, 26, 62, 255],
|
||||
"xray_error": [255, 0, 0, 255],
|
||||
|
||||
"layerview_inset_0": [255, 64, 0, 255],
|
||||
"layerview_inset_x": [0, 156, 128, 255],
|
||||
"layerview_skin": [255, 255, 86, 255],
|
||||
"layerview_support": [255, 255, 0, 255],
|
||||
|
||||
"layerview_infill": [0, 255, 255, 255],
|
||||
"layerview_support_infill": [0, 200, 200, 255],
|
||||
|
||||
"layerview_move_retraction": [0, 100, 255, 255]
|
||||
}
|
||||
}
|
||||
{"metadata": {"name": "Colorblind Assist Dark", "inherits": "cura-dark"}, "colors": {"x_axis": [212, 0, 0, 255], "y_axis": [64, 64, 255, 255], "model_overhang": [200, 0, 255, 255], "xray": [26, 26, 62, 255], "xray_error": [255, 0, 0, 255], "layerview_inset_0": [255, 64, 0, 255], "layerview_inset_x": [0, 156, 128, 255], "layerview_skin": [255, 255, 86, 255], "layerview_support": [255, 255, 0, 255], "layerview_infill": [0, 255, 255, 255], "layerview_support_infill": [0, 200, 200, 255], "layerview_move_retraction": [0, 100, 255, 255], "main_window_header_background": [192, 199, 65, 255]}}
|
File diff suppressed because one or more lines are too long
@ -1,29 +1 @@
|
||||
{
|
||||
"metadata": {
|
||||
"name": "Colorblind Assist Light",
|
||||
"inherits": "cura-light"
|
||||
},
|
||||
|
||||
"colors": {
|
||||
|
||||
"x_axis": [200, 0, 0, 255],
|
||||
"y_axis": [64, 64, 255, 255],
|
||||
"model_overhang": [200, 0, 255, 255],
|
||||
"model_selection_outline": [12, 169, 227, 255],
|
||||
|
||||
"xray_error_dark": [255, 0, 0, 255],
|
||||
"xray_error_light": [255, 255, 0, 255],
|
||||
"xray": [26, 26, 62, 255],
|
||||
"xray_error": [255, 0, 0, 255],
|
||||
|
||||
"layerview_inset_0": [255, 64, 0, 255],
|
||||
"layerview_inset_x": [0, 156, 128, 255],
|
||||
"layerview_skin": [255, 255, 86, 255],
|
||||
"layerview_support": [255, 255, 0, 255],
|
||||
|
||||
"layerview_infill": [0, 255, 255, 255],
|
||||
"layerview_support_infill": [0, 200, 200, 255],
|
||||
|
||||
"layerview_move_retraction": [0, 100, 255, 255]
|
||||
}
|
||||
}
|
||||
{"metadata": {"name": "Colorblind Assist Light", "inherits": "cura-light"}, "colors": {"x_axis": [200, 0, 0, 255], "y_axis": [64, 64, 255, 255], "model_overhang": [200, 0, 255, 255], "model_selection_outline": [12, 169, 227, 255], "xray_error_dark": [255, 0, 0, 255], "xray_error_light": [255, 255, 0, 255], "xray": [26, 26, 62, 255], "xray_error": [255, 0, 0, 255], "layerview_inset_0": [255, 64, 0, 255], "layerview_inset_x": [0, 156, 128, 255], "layerview_skin": [255, 255, 86, 255], "layerview_support": [255, 255, 0, 255], "layerview_infill": [0, 255, 255, 255], "layerview_support_infill": [0, 200, 200, 255], "layerview_move_retraction": [0, 100, 255, 255], "main_window_header_background": [192, 199, 65, 255]}}
|
File diff suppressed because one or more lines are too long
@ -1,16 +0,0 @@
|
||||
[
|
||||
[ 62, 33, 55, 255],
|
||||
[126, 196, 193, 255],
|
||||
[126, 196, 193, 255],
|
||||
[215, 155, 125, 255],
|
||||
[228, 148, 58, 255],
|
||||
[192, 199, 65, 255],
|
||||
[157, 48, 59, 255],
|
||||
[140, 143, 174, 255],
|
||||
[ 23, 67, 75, 255],
|
||||
[ 23, 67, 75, 255],
|
||||
[154, 99, 72, 255],
|
||||
[112, 55, 127, 255],
|
||||
[100, 125, 52, 255],
|
||||
[210, 100, 113, 255]
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user