mirror of
https://github.com/Ultimaker/Cura.git
synced 2026-02-15 08:59:32 -07:00
Merge remote-tracking branch 'origin/CURA-12544_saving-and-loading-painted-files-in-Cura' into CURA-12528_unwrap_uvs
This commit is contained in:
commit
14e49c5c73
27 changed files with 1182 additions and 1099 deletions
|
|
@ -80,9 +80,13 @@ class LayerDataBuilder(MeshBuilder):
|
|||
material_colors = numpy.zeros((line_dimensions.shape[0], 4), dtype=numpy.float32)
|
||||
for extruder_nr in range(material_color_map.shape[0]):
|
||||
material_colors[extruders == extruder_nr] = material_color_map[extruder_nr]
|
||||
# Set material_colors with indices where line_types (also numpy array) == MoveCombingType
|
||||
material_colors[line_types == LayerPolygon.MoveCombingType] = colors[line_types == LayerPolygon.MoveCombingType]
|
||||
material_colors[line_types == LayerPolygon.MoveRetractionType] = colors[line_types == LayerPolygon.MoveRetractionType]
|
||||
# Set material_colors with indices where line_types (also numpy array) == MoveUnretractedType
|
||||
material_colors[line_types == LayerPolygon.MoveUnretractedType] = colors[line_types == LayerPolygon.MoveUnretractedType]
|
||||
material_colors[line_types == LayerPolygon.MoveRetractedType] = colors[line_types == LayerPolygon.MoveRetractedType]
|
||||
material_colors[line_types == LayerPolygon.MoveWhileRetractingType] = colors[
|
||||
line_types == LayerPolygon.MoveWhileRetractingType]
|
||||
material_colors[line_types == LayerPolygon.MoveWhileUnretractingType] = colors[
|
||||
line_types == LayerPolygon.MoveWhileUnretractingType]
|
||||
|
||||
attributes = {
|
||||
"line_dimensions": {
|
||||
|
|
|
|||
|
|
@ -19,15 +19,21 @@ class LayerPolygon:
|
|||
SkirtType = 5
|
||||
InfillType = 6
|
||||
SupportInfillType = 7
|
||||
MoveCombingType = 8
|
||||
MoveRetractionType = 9
|
||||
MoveUnretractedType = 8
|
||||
MoveRetractedType = 9
|
||||
SupportInterfaceType = 10
|
||||
PrimeTowerType = 11
|
||||
__number_of_types = 12
|
||||
MoveWhileRetractingType = 12
|
||||
MoveWhileUnretractingType = 13
|
||||
__number_of_types = 14
|
||||
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType,
|
||||
numpy.arange(__number_of_types) == MoveCombingType),
|
||||
numpy.arange(__number_of_types) == MoveRetractionType)
|
||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.logical_or(
|
||||
numpy.arange(__number_of_types) == NoneType,
|
||||
numpy.arange(__number_of_types) == MoveUnretractedType),
|
||||
numpy.logical_or(
|
||||
numpy.arange(__number_of_types) == MoveRetractedType,
|
||||
numpy.arange(__number_of_types) == MoveWhileRetractingType)),
|
||||
numpy.arange(__number_of_types) == MoveWhileUnretractingType)
|
||||
|
||||
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray,
|
||||
line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
|
||||
|
|
@ -269,10 +275,12 @@ class LayerPolygon:
|
|||
theme.getColor("layerview_skirt").getRgbF(), # SkirtType
|
||||
theme.getColor("layerview_infill").getRgbF(), # InfillType
|
||||
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
|
||||
theme.getColor("layerview_move_combing").getRgbF(), # MoveUnretractedType
|
||||
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractedType
|
||||
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
|
||||
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
|
||||
theme.getColor("layerview_prime_tower").getRgbF(), # PrimeTowerType
|
||||
theme.getColor("layerview_move_while_retracting").getRgbF(), # MoveWhileRetracting
|
||||
theme.getColor("layerview_move_while_unretracting").getRgbF(), # MoveWhileUnretracting
|
||||
])
|
||||
|
||||
return cls.__color_map
|
||||
|
|
|
|||
|
|
@ -1,12 +1,34 @@
|
|||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
import copy
|
||||
|
||||
from typing import Optional
|
||||
|
||||
import UM.View.GL.Texture
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.Texture import Texture
|
||||
|
||||
|
||||
# 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) -> Optional[UM.View.GL.Texture.Texture]:
|
||||
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 setPaintTexture(self, texture: UM.View.GL.Texture) -> None:
|
||||
self._paint_texture = texture
|
||||
|
||||
def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
|
||||
return type(self)()
|
||||
copied_decorator = SliceableObjectDecorator()
|
||||
copied_decorator.setPaintTexture(copy.deepcopy(self.getPaintTexture()))
|
||||
return copied_decorator
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ from typing import List, Optional, Union, TYPE_CHECKING, cast
|
|||
|
||||
import pySavitar as Savitar
|
||||
import numpy
|
||||
from PyQt6.QtGui import QImage
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.Matrix import Matrix
|
||||
|
|
@ -18,6 +19,8 @@ from UM.Scene.GroupDecorator import GroupDecorator
|
|||
from UM.Scene.SceneNode import SceneNode # For typing.
|
||||
from UM.Scene.SceneNodeSettings import SceneNodeSettings
|
||||
from UM.Util import parseBool
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.View.GL.Texture import Texture
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Machines.ContainerTree import ContainerTree
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
|
|
@ -94,14 +97,14 @@ class ThreeMFReader(MeshReader):
|
|||
return temp_mat
|
||||
|
||||
@staticmethod
|
||||
def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "", archive: zipfile.ZipFile = None) -> Optional[SceneNode]:
|
||||
def _convertSavitarNodeToUMNode(savitar_node: Savitar.SceneNode, file_name: str = "", archive: zipfile.ZipFile = None, scene: Savitar.Scene = None) -> Optional[SceneNode]:
|
||||
"""Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
|
||||
|
||||
:returns: Scene node.
|
||||
"""
|
||||
try:
|
||||
node_name = savitar_node.getName()
|
||||
node_id = savitar_node.getId()
|
||||
node_id = str(savitar_node.getId())
|
||||
except AttributeError:
|
||||
Logger.log("e", "Outdated version of libSavitar detected! Please update to the newest version!")
|
||||
node_name = ""
|
||||
|
|
@ -131,12 +134,19 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.setTransformation(transformation)
|
||||
mesh_builder = MeshBuilder()
|
||||
|
||||
data = numpy.fromstring(savitar_node.getMeshData().getFlatVerticesAsBytes(), dtype=numpy.float32)
|
||||
mesh_data = savitar_node.getMeshData()
|
||||
|
||||
vertices_data = numpy.fromstring(mesh_data.getFlatVerticesAsBytes(), dtype=numpy.float32)
|
||||
vertices = numpy.resize(vertices_data, (int(vertices_data.size / 3), 3))
|
||||
|
||||
texture_path = mesh_data.getTexturePath(scene)
|
||||
uv_data = numpy.fromstring(mesh_data.getUVCoordinatesPerVertexAsBytes(scene), dtype=numpy.float32)
|
||||
uv_coordinates = numpy.resize(uv_data, (int(uv_data.size / 2), 2))
|
||||
|
||||
vertices = numpy.resize(data, (int(data.size / 3), 3))
|
||||
mesh_builder.setVertices(vertices)
|
||||
mesh_builder.calculateNormals(fast=True)
|
||||
mesh_builder.setMeshId(node_id)
|
||||
mesh_builder.setUVCoordinates(uv_coordinates)
|
||||
if file_name:
|
||||
# The filename is used to give the user the option to reload the file if it is changed on disk
|
||||
# It is only set for the root node of the 3mf file
|
||||
|
|
@ -150,7 +160,7 @@ class ThreeMFReader(MeshReader):
|
|||
um_node.setMeshData(mesh_data)
|
||||
|
||||
for child in savitar_node.getChildren():
|
||||
child_node = ThreeMFReader._convertSavitarNodeToUMNode(child, archive=archive)
|
||||
child_node = ThreeMFReader._convertSavitarNodeToUMNode(child, archive=archive, scene=scene)
|
||||
if child_node:
|
||||
um_node.addChild(child_node)
|
||||
|
||||
|
|
@ -222,6 +232,14 @@ class ThreeMFReader(MeshReader):
|
|||
# affects (auto) slicing
|
||||
sliceable_decorator = SliceableObjectDecorator()
|
||||
um_node.addDecorator(sliceable_decorator)
|
||||
|
||||
if texture_path != "" and archive is not None:
|
||||
texture_data = archive.open(texture_path).read()
|
||||
texture_image = QImage.fromData(texture_data, "PNG")
|
||||
texture = Texture(OpenGL.getInstance())
|
||||
texture.setImage(texture_image)
|
||||
sliceable_decorator.setPaintTexture(texture)
|
||||
|
||||
return um_node
|
||||
|
||||
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
|
||||
|
|
@ -239,7 +257,7 @@ class ThreeMFReader(MeshReader):
|
|||
CuraApplication.getInstance().getController().getScene().setMetaDataEntry(key, value)
|
||||
|
||||
for node in scene_3mf.getSceneNodes():
|
||||
um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name, archive)
|
||||
um_node = ThreeMFReader._convertSavitarNodeToUMNode(node, file_name, archive, scene_3mf)
|
||||
if um_node is None:
|
||||
continue
|
||||
|
||||
|
|
@ -339,7 +357,7 @@ class ThreeMFReader(MeshReader):
|
|||
# Convert the scene to scene nodes
|
||||
nodes = []
|
||||
for savitar_node in scene.getSceneNodes():
|
||||
scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name")
|
||||
scene_node = ThreeMFReader._convertSavitarNodeToUMNode(savitar_node, "file_name", scene=scene)
|
||||
if scene_node is None:
|
||||
continue
|
||||
nodes.append(scene_node)
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
@ -109,7 +111,11 @@ class ThreeMFWriter(MeshWriter):
|
|||
def _convertUMNodeToSavitarNode(um_node,
|
||||
transformation = Matrix(),
|
||||
exported_settings: Optional[Dict[str, Set[str]]] = None,
|
||||
center_mesh = False):
|
||||
center_mesh = False,
|
||||
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.
|
||||
|
|
@ -150,7 +156,35 @@ class ThreeMFWriter(MeshWriter):
|
|||
if indices_array is not None:
|
||||
savitar_node.getMeshData().setFacesFromBytes(indices_array)
|
||||
else:
|
||||
savitar_node.getMeshData().setFacesFromBytes(numpy.arange(mesh_data.getVertices().size / 3, dtype=numpy.int32).tostring())
|
||||
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 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")
|
||||
|
|
@ -187,7 +221,11 @@ class ThreeMFWriter(MeshWriter):
|
|||
if child_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
|
||||
continue
|
||||
savitar_child_node = ThreeMFWriter._convertUMNodeToSavitarNode(child_node,
|
||||
exported_settings = exported_settings)
|
||||
exported_settings = exported_settings,
|
||||
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)
|
||||
|
||||
|
|
@ -249,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)
|
||||
|
||||
|
|
@ -320,13 +361,21 @@ class ThreeMFWriter(MeshWriter):
|
|||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(root_child,
|
||||
transformation_matrix,
|
||||
exported_model_settings,
|
||||
center_mesh = True)
|
||||
center_mesh = True,
|
||||
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)
|
||||
exported_model_settings,
|
||||
scene = savitar_scene,
|
||||
archive = archive,
|
||||
model_relations_element = model_relations_element,
|
||||
content_types_element = content_types)
|
||||
if savitar_node:
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
|
||||
|
|
@ -338,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))
|
||||
|
|
@ -500,7 +551,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
def sceneNodesToString(scene_nodes: [SceneNode]) -> str:
|
||||
savitar_scene = Savitar.Scene()
|
||||
for scene_node in scene_nodes:
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True)
|
||||
savitar_node = ThreeMFWriter._convertUMNodeToSavitarNode(scene_node, center_mesh = True, scene = savitar_scene)
|
||||
savitar_scene.addSceneNode(savitar_node)
|
||||
parser = Savitar.ThreeMFParser()
|
||||
scene_string = parser.sceneToString(savitar_scene)
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ message Polygon {
|
|||
SkirtType = 5;
|
||||
InfillType = 6;
|
||||
SupportInfillType = 7;
|
||||
MoveCombingType = 8;
|
||||
MoveRetractionType = 9;
|
||||
MoveUnretractedType = 8;
|
||||
MoveRetractedType = 9;
|
||||
SupportInterfaceType = 10;
|
||||
PrimeTowerType = 11;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -133,7 +133,10 @@ class FlavorParser:
|
|||
if i > 0:
|
||||
line_feedrates[i - 1] = point[3]
|
||||
line_types[i - 1] = point[5]
|
||||
if point[5] in [LayerPolygon.MoveCombingType, LayerPolygon.MoveRetractionType]:
|
||||
if point[5] in [LayerPolygon.MoveUnretractedType,
|
||||
LayerPolygon.MoveRetractedType,
|
||||
LayerPolygon.MoveWhileRetractingType,
|
||||
LayerPolygon.MoveWhileUnretractingType]:
|
||||
line_widths[i - 1] = 0.1
|
||||
line_thicknesses[i - 1] = 0.0 # Travels are set as zero thickness lines
|
||||
else:
|
||||
|
|
@ -196,7 +199,7 @@ class FlavorParser:
|
|||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
|
||||
self._previous_extrusion_value = new_extrusion_value
|
||||
else:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType]) # retraction
|
||||
e[self._extruder_number] = new_extrusion_value
|
||||
|
||||
# Only when extruding we can determine the latest known "layer height" which is the difference in height between extrusions
|
||||
|
|
@ -205,9 +208,9 @@ class FlavorParser:
|
|||
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
|
||||
self._previous_z = z
|
||||
elif self._previous_extrusion_value > e[self._extruder_number]:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractedType])
|
||||
else:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
return self._position(x, y, z, f, e)
|
||||
|
||||
|
||||
|
|
@ -419,7 +422,7 @@ class FlavorParser:
|
|||
self._createPolygon(self._current_layer_thickness, current_path, self._extruder_offsets.get(self._extruder_number, [0, 0]))
|
||||
current_path.clear()
|
||||
# Start the new layer at the end position of the last layer
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
|
||||
# When using a raft, the raft layers are stored as layers < 0, it mimics the same behavior
|
||||
# as in ProcessSlicedLayersJob
|
||||
|
|
@ -461,9 +464,9 @@ class FlavorParser:
|
|||
|
||||
# When changing tool, store the end point of the previous path, then process the code and finally
|
||||
# add another point with the new position of the head.
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
current_position = self.processTCode(global_stack, T, line, current_position, current_path)
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
current_path.append([current_position.x, current_position.y, current_position.z, current_position.f, current_position.e[self._extruder_number], LayerPolygon.MoveUnretractedType])
|
||||
|
||||
if line.startswith("M"):
|
||||
M = self._getInt(line, "M")
|
||||
|
|
|
|||
286
plugins/PaintTool/PaintTool.py
Normal file
286
plugins/PaintTool/PaintTool.py
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QImage, QPainter, QColor, QBrush, QPen
|
||||
from typing import cast, Dict, List, Optional, Tuple
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, MouseEvent, KeyEvent
|
||||
from UM.Logger import Logger
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Tool import Tool
|
||||
|
||||
from cura.PickingPass import PickingPass
|
||||
from .PaintView import PaintView
|
||||
|
||||
|
||||
class PaintTool(Tool):
|
||||
"""Provides the tool to paint meshes."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._picking_pass: Optional[PickingPass] = None
|
||||
|
||||
self._shortcut_key: Qt.Key = Qt.Key.Key_P
|
||||
|
||||
self._node_cache: Optional[SceneNode] = None
|
||||
self._mesh_transformed_cache = None
|
||||
self._cache_dirty: bool = True
|
||||
|
||||
self._color_str_to_rgba: Dict[str, List[int]] = {
|
||||
"A": [192, 0, 192, 255],
|
||||
"B": [232, 128, 0, 255],
|
||||
"C": [0, 255, 0, 255],
|
||||
"D": [255, 255, 255, 255],
|
||||
}
|
||||
|
||||
self._brush_size: int = 10
|
||||
self._brush_color: str = "A"
|
||||
self._brush_shape: str = "A"
|
||||
self._brush_pen: QPen = self._createBrushPen()
|
||||
|
||||
self._mouse_held: bool = False
|
||||
self._ctrl_held: bool = False
|
||||
self._shift_held: bool = False
|
||||
|
||||
self._last_text_coords: Optional[Tuple[int, int]] = None
|
||||
|
||||
def _createBrushPen(self) -> QPen:
|
||||
pen = QPen()
|
||||
pen.setWidth(self._brush_size)
|
||||
color = self._color_str_to_rgba[self._brush_color]
|
||||
pen.setColor(QColor(color[0], color[1], color[2], color[3]))
|
||||
match self._brush_shape:
|
||||
case "A":
|
||||
pen.setCapStyle(Qt.PenCapStyle.SquareCap)
|
||||
case "B":
|
||||
pen.setCapStyle(Qt.PenCapStyle.RoundCap)
|
||||
return pen
|
||||
|
||||
def _createStrokeImage(self, x0: float, y0: float, x1: float, y1: float) -> Tuple[QImage, Tuple[int, int]]:
|
||||
xdiff = int(x1 - x0)
|
||||
ydiff = int(y1 - y0)
|
||||
|
||||
half_brush_size = self._brush_size // 2
|
||||
start_x = int(min(x0, x1) - half_brush_size)
|
||||
start_y = int(min(y0, y1) - half_brush_size)
|
||||
|
||||
stroke_image = QImage(abs(xdiff) + self._brush_size, abs(ydiff) + self._brush_size, QImage.Format.Format_RGBA8888)
|
||||
stroke_image.fill(QColor(0,0,0,0))
|
||||
|
||||
painter = QPainter(stroke_image)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setPen(self._brush_pen)
|
||||
if xdiff == 0 and ydiff == 0:
|
||||
painter.drawPoint(int(x0 - start_x), int(y0 - start_y))
|
||||
else:
|
||||
painter.drawLine(int(x0 - start_x), int(y0 - start_y), int(x1 - start_x), int(y1 - start_y))
|
||||
painter.end()
|
||||
|
||||
return stroke_image, (start_x, start_y)
|
||||
|
||||
def setPaintType(self, paint_type: str) -> None:
|
||||
Logger.warning(f"TODO: Implement paint-types ({paint_type}).")
|
||||
pass # FIXME: ... and also please call `self._brush_pen = self._createBrushPen()` (see other funcs).
|
||||
|
||||
def setBrushSize(self, brush_size: float) -> None:
|
||||
if brush_size != self._brush_size:
|
||||
self._brush_size = int(brush_size)
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def setBrushColor(self, brush_color: str) -> None:
|
||||
if brush_color != self._brush_color:
|
||||
self._brush_color = brush_color
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def setBrushShape(self, brush_shape: str) -> None:
|
||||
if brush_shape != self._brush_shape:
|
||||
self._brush_shape = brush_shape
|
||||
self._brush_pen = self._createBrushPen()
|
||||
|
||||
def undoStackAction(self, redo_instead: bool) -> bool:
|
||||
paintview = Application.getInstance().getController().getActiveView()
|
||||
if paintview is None or paintview.getPluginId() != "PaintTool":
|
||||
return False
|
||||
paintview = cast(PaintView, paintview)
|
||||
if redo_instead:
|
||||
paintview.redoStroke()
|
||||
else:
|
||||
paintview.undoStroke()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
if len(nodes) > 0:
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(nodes[0])
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _get_intersect_ratio_via_pt(a: numpy.ndarray, pt: numpy.ndarray, b: numpy.ndarray, c: numpy.ndarray) -> float:
|
||||
# compute the intersection of (param) A - pt with (param) B - (param) C
|
||||
|
||||
# compute unit vectors of directions of lines A and B
|
||||
udir_a = a - pt
|
||||
udir_a /= numpy.linalg.norm(udir_a)
|
||||
udir_b = b - c
|
||||
udir_b /= numpy.linalg.norm(udir_b)
|
||||
|
||||
# find unit direction vector for line C, which is perpendicular to lines A and B
|
||||
udir_res = numpy.cross(udir_b, udir_a)
|
||||
udir_res /= numpy.linalg.norm(udir_res)
|
||||
|
||||
# solve system of equations
|
||||
rhs = b - a
|
||||
lhs = numpy.array([udir_a, -udir_b, udir_res]).T
|
||||
solved = numpy.linalg.solve(lhs, rhs)
|
||||
|
||||
# get the ratio
|
||||
intersect = ((a + solved[0] * udir_a) + (b + solved[1] * udir_b)) * 0.5
|
||||
return numpy.linalg.norm(pt - intersect) / numpy.linalg.norm(a - intersect)
|
||||
|
||||
def _nodeTransformChanged(self, *args) -> None:
|
||||
self._cache_dirty = True
|
||||
|
||||
def _getTexCoordsFromClick(self, node: SceneNode, x: int, y: int) -> Optional[Tuple[float, float]]:
|
||||
face_id = self._selection_pass.getFaceIdAtPosition(x, y)
|
||||
if face_id < 0 or face_id >= node.getMeshData().getFaceCount():
|
||||
return None
|
||||
|
||||
pt = self._picking_pass.getPickedPosition(x, y).getData()
|
||||
|
||||
va, vb, vc = self._mesh_transformed_cache.getFaceNodes(face_id)
|
||||
ta, tb, tc = node.getMeshData().getFaceUvCoords(face_id)
|
||||
|
||||
# 'Weight' of each vertex that would produce point pt, so we can generate the texture coordinates from the uv ones of the vertices.
|
||||
# See (also) https://mathworld.wolfram.com/BarycentricCoordinates.html
|
||||
wa = PaintTool._get_intersect_ratio_via_pt(va, pt, vb, vc)
|
||||
wb = PaintTool._get_intersect_ratio_via_pt(vb, pt, vc, va)
|
||||
wc = PaintTool._get_intersect_ratio_via_pt(vc, pt, va, vb)
|
||||
wt = wa + wb + wc
|
||||
wa /= wt
|
||||
wb /= wt
|
||||
wc /= wt
|
||||
texcoords = wa * ta + wb * tb + wc * tc
|
||||
return texcoords
|
||||
|
||||
def event(self, event: Event) -> bool:
|
||||
"""Handle mouse and keyboard events.
|
||||
|
||||
:param event: The event to handle.
|
||||
:return: Whether this event has been caught by this tool (True) or should
|
||||
be passed on (False).
|
||||
"""
|
||||
super().event(event)
|
||||
|
||||
controller = Application.getInstance().getController()
|
||||
nodes = Selection.getAllSelectedObjects()
|
||||
if len(nodes) <= 0:
|
||||
return False
|
||||
node = nodes[0]
|
||||
|
||||
# Make sure the displayed values are updated if the bounding box of the selected mesh(es) changes
|
||||
if event.type == Event.ToolActivateEvent:
|
||||
controller.setActiveStage("PrepareStage")
|
||||
controller.setActiveView("PaintTool") # Because that's the plugin-name, and the view is registered to it.
|
||||
return True
|
||||
|
||||
if event.type == Event.ToolDeactivateEvent:
|
||||
controller.setActiveStage("PrepareStage")
|
||||
controller.setActiveView("SolidView")
|
||||
return True
|
||||
|
||||
if event.type == Event.KeyPressEvent:
|
||||
evt = cast(KeyEvent, event)
|
||||
if evt.key == KeyEvent.ControlKey:
|
||||
self._ctrl_held = True
|
||||
return True
|
||||
if evt.key == KeyEvent.ShiftKey:
|
||||
self._shift_held = True
|
||||
return True
|
||||
return False
|
||||
|
||||
if event.type == Event.KeyReleaseEvent:
|
||||
evt = cast(KeyEvent, event)
|
||||
if evt.key == KeyEvent.ControlKey:
|
||||
self._ctrl_held = False
|
||||
return True
|
||||
if evt.key == KeyEvent.ShiftKey:
|
||||
self._shift_held = False
|
||||
return True
|
||||
if evt.key == Qt.Key.Key_L and self._ctrl_held:
|
||||
# NOTE: Ctrl-L is used here instead of Ctrl-Z, as the latter is the application-wide one that takes precedence.
|
||||
return self.undoStackAction(self._shift_held)
|
||||
return False
|
||||
|
||||
if event.type == Event.MouseReleaseEvent and self._controller.getToolsEnabled():
|
||||
if MouseEvent.LeftButton not in cast(MouseEvent, event).buttons:
|
||||
return False
|
||||
self._mouse_held = False
|
||||
self._last_text_coords = None
|
||||
return True
|
||||
|
||||
is_moved = event.type == Event.MouseMoveEvent
|
||||
is_pressed = event.type == Event.MousePressEvent
|
||||
if (is_moved or is_pressed) and self._controller.getToolsEnabled():
|
||||
if is_moved and not self._mouse_held:
|
||||
return False
|
||||
|
||||
evt = cast(MouseEvent, event)
|
||||
if is_pressed:
|
||||
if MouseEvent.LeftButton not in evt.buttons:
|
||||
return False
|
||||
else:
|
||||
self._mouse_held = True
|
||||
|
||||
paintview = controller.getActiveView()
|
||||
if paintview is None or paintview.getPluginId() != "PaintTool":
|
||||
return False
|
||||
paintview = cast(PaintView, paintview)
|
||||
|
||||
if not self._selection_pass:
|
||||
return False
|
||||
|
||||
camera = self._controller.getScene().getActiveCamera()
|
||||
if not camera:
|
||||
return False
|
||||
|
||||
if node != self._node_cache:
|
||||
if self._node_cache is not None:
|
||||
self._node_cache.transformationChanged.disconnect(self._nodeTransformChanged)
|
||||
self._node_cache = node
|
||||
self._node_cache.transformationChanged.connect(self._nodeTransformChanged)
|
||||
if self._cache_dirty:
|
||||
self._cache_dirty = False
|
||||
self._mesh_transformed_cache = self._node_cache.getMeshDataTransformed()
|
||||
if not self._mesh_transformed_cache:
|
||||
return False
|
||||
|
||||
evt = cast(MouseEvent, event)
|
||||
|
||||
if not self._picking_pass:
|
||||
self._picking_pass = PickingPass(camera.getViewportWidth(), camera.getViewportHeight())
|
||||
self._picking_pass.render()
|
||||
|
||||
self._selection_pass.renderFacesMode()
|
||||
|
||||
texcoords = self._getTexCoordsFromClick(node, evt.x, evt.y)
|
||||
if texcoords is None:
|
||||
return False
|
||||
if self._last_text_coords is None:
|
||||
self._last_text_coords = texcoords
|
||||
|
||||
w, h = paintview.getUvTexDimensions()
|
||||
sub_image, (start_x, start_y) = self._createStrokeImage(
|
||||
self._last_text_coords[0] * w,
|
||||
self._last_text_coords[1] * h,
|
||||
texcoords[0] * w,
|
||||
texcoords[1] * h
|
||||
)
|
||||
paintview.addStroke(sub_image, start_x, start_y)
|
||||
|
||||
self._last_text_coords = texcoords
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
return True
|
||||
|
||||
return False
|
||||
215
plugins/PaintTool/PaintTool.qml
Normal file
215
plugins/PaintTool/PaintTool.qml
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright (c) 2025 UltiMaker
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
import UM 1.7 as UM
|
||||
|
||||
Item
|
||||
{
|
||||
id: base
|
||||
width: childrenRect.width
|
||||
height: childrenRect.height
|
||||
UM.I18nCatalog { id: catalog; name: "cura"}
|
||||
|
||||
ColumnLayout
|
||||
{
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: paintTypeA
|
||||
|
||||
text: catalog.i18nc("@action:button", "Paint Type A")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Buildplate")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setPaintType", "A")
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: paintTypeB
|
||||
|
||||
text: catalog.i18nc("@action:button", "Paint Type B")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("BlackMagic")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setPaintType", "B")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: colorButtonA
|
||||
|
||||
text: catalog.i18nc("@action:button", "Color A")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Eye")
|
||||
color: "purple"
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "A")
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: colorButtonB
|
||||
|
||||
text: catalog.i18nc("@action:button", "Color B")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Eye")
|
||||
color: "orange"
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "B")
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: colorButtonC
|
||||
|
||||
text: catalog.i18nc("@action:button", "Color C")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Eye")
|
||||
color: "green"
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "C")
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: colorButtonD
|
||||
|
||||
text: catalog.i18nc("@action:button", "Color D")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("Eye")
|
||||
color: "ghostwhite"
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushColor", "D")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: shapeSquareButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Square Brush")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("MeshTypeNormal")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushShape", "A")
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: shapeCircleButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Round Brush")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("CircleOutline")
|
||||
color: UM.Theme.getColor("icon")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("setBrushShape", "B")
|
||||
}
|
||||
|
||||
UM.Slider
|
||||
{
|
||||
id: shapeSizeSlider
|
||||
|
||||
from: 1
|
||||
to: 40
|
||||
value: 10
|
||||
|
||||
onPressedChanged: function(pressed)
|
||||
{
|
||||
if(! pressed)
|
||||
{
|
||||
UM.Controller.triggerActionWithData("setBrushSize", shapeSizeSlider.value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout
|
||||
{
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: undoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Undo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowReset")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("undoStackAction", false)
|
||||
}
|
||||
|
||||
UM.ToolbarButton
|
||||
{
|
||||
id: redoButton
|
||||
|
||||
text: catalog.i18nc("@action:button", "Redo Stroke")
|
||||
toolItem: UM.ColorImage
|
||||
{
|
||||
source: UM.Theme.getIcon("ArrowDoubleCircleRight")
|
||||
}
|
||||
property bool needBorder: true
|
||||
|
||||
z: 2
|
||||
|
||||
onClicked: UM.Controller.triggerActionWithData("undoStackAction", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
98
plugins/PaintTool/PaintView.py
Normal file
98
plugins/PaintTool/PaintView.py
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from typing import Optional, List, Tuple
|
||||
|
||||
from PyQt6.QtGui import QImage, QColor, QPainter
|
||||
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||
from UM.View.GL.Texture import Texture
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.View.GL.OpenGL import OpenGL
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class PaintView(View):
|
||||
"""View for model-painting."""
|
||||
|
||||
UNDO_STACK_SIZE = 1024
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._paint_shader: Optional[ShaderProgram] = None
|
||||
self._current_paint_texture: Optional[Texture] = None
|
||||
|
||||
self._stroke_undo_stack: List[Tuple[QImage, int, int]] = []
|
||||
self._stroke_redo_stack: List[Tuple[QImage, int, int]] = []
|
||||
|
||||
self._force_opaque_mask = QImage(2, 2, QImage.Format.Format_Mono)
|
||||
self._force_opaque_mask.fill(1)
|
||||
|
||||
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)
|
||||
|
||||
def _forceOpaqueDeepCopy(self, image: QImage):
|
||||
res = QImage(image.width(), image.height(), QImage.Format.Format_RGBA8888)
|
||||
res.fill(QColor(255, 255, 255, 255))
|
||||
painter = QPainter(res)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
|
||||
painter.setCompositionMode(QPainter.CompositionMode.CompositionMode_SourceOver)
|
||||
painter.drawImage(0, 0, image)
|
||||
painter.end()
|
||||
res.setAlphaChannel(self._force_opaque_mask.scaled(image.width(), image.height()))
|
||||
return res
|
||||
|
||||
def addStroke(self, stroke_image: QImage, start_x: int, start_y: int) -> None:
|
||||
if self._current_paint_texture is None:
|
||||
return
|
||||
|
||||
self._stroke_redo_stack.clear()
|
||||
if len(self._stroke_undo_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
self._stroke_undo_stack.pop(0)
|
||||
undo_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(stroke_image, start_x, start_y))
|
||||
if undo_image is not None:
|
||||
self._stroke_undo_stack.append((undo_image, start_x, start_y))
|
||||
|
||||
def _applyUndoStacksAction(self, from_stack: List[Tuple[QImage, int, int]], to_stack: List[Tuple[QImage, int, int]]) -> bool:
|
||||
if len(from_stack) <= 0 or self._current_paint_texture is None:
|
||||
return False
|
||||
from_image, x, y = from_stack.pop()
|
||||
to_image = self._forceOpaqueDeepCopy(self._current_paint_texture.setSubImage(from_image, x, y))
|
||||
if to_image is None:
|
||||
return False
|
||||
if len(to_stack) >= PaintView.UNDO_STACK_SIZE:
|
||||
to_stack.pop(0)
|
||||
to_stack.append((to_image, x, y))
|
||||
return True
|
||||
|
||||
def undoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_undo_stack, self._stroke_redo_stack)
|
||||
|
||||
def redoStroke(self) -> bool:
|
||||
return self._applyUndoStacksAction(self._stroke_redo_stack, self._stroke_undo_stack)
|
||||
|
||||
def getUvTexDimensions(self):
|
||||
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()
|
||||
self._checkSetup()
|
||||
paint_batch = renderer.createRenderBatch(shader=self._paint_shader)
|
||||
renderer.addRenderBatch(paint_batch)
|
||||
|
||||
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())
|
||||
30
plugins/PaintTool/__init__.py
Normal file
30
plugins/PaintTool/__init__.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright (c) 2025 UltiMaker
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import PaintTool
|
||||
from . import PaintView
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"tool": {
|
||||
"name": i18n_catalog.i18nc("@action:button", "Paint"),
|
||||
"description": i18n_catalog.i18nc("@info:tooltip", "Paint Model"),
|
||||
"icon": "Visual",
|
||||
"tool_panel": "PaintTool.qml",
|
||||
"weight": 0
|
||||
},
|
||||
"view": {
|
||||
"name": i18n_catalog.i18nc("@item:inmenu", "Paint view"),
|
||||
"weight": 0,
|
||||
"visible": False
|
||||
}
|
||||
}
|
||||
|
||||
def register(app):
|
||||
return {
|
||||
"tool": PaintTool.PaintTool(),
|
||||
"view": PaintView.PaintView()
|
||||
}
|
||||
142
plugins/PaintTool/paint.shader
Normal file
142
plugins/PaintTool/paint.shader
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
[shaders]
|
||||
vertex =
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewMatrix;
|
||||
uniform highp mat4 u_projectionMatrix;
|
||||
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
attribute highp vec4 a_vertex;
|
||||
attribute highp vec4 a_normal;
|
||||
attribute highp vec2 a_uvs;
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
varying highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert;
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
|
||||
v_uvs = a_uvs;
|
||||
}
|
||||
|
||||
fragment =
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform mediump vec4 u_diffuseColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform mediump float u_opacity;
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
varying highp vec3 v_vertex;
|
||||
varying highp vec3 v_normal;
|
||||
varying highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0);
|
||||
final_color += (n_dot_l * u_diffuseColor);
|
||||
|
||||
final_color.a = u_opacity;
|
||||
|
||||
lowp vec4 texture = texture2D(u_texture, v_uvs);
|
||||
final_color = mix(final_color, texture, texture.a);
|
||||
|
||||
gl_FragColor = final_color;
|
||||
}
|
||||
|
||||
vertex41core =
|
||||
#version 410
|
||||
uniform highp mat4 u_modelMatrix;
|
||||
uniform highp mat4 u_viewMatrix;
|
||||
uniform highp mat4 u_projectionMatrix;
|
||||
|
||||
uniform highp mat4 u_normalMatrix;
|
||||
|
||||
in highp vec4 a_vertex;
|
||||
in highp vec4 a_normal;
|
||||
in highp vec2 a_uvs;
|
||||
|
||||
out highp vec3 v_vertex;
|
||||
out highp vec3 v_normal;
|
||||
out highp vec2 v_uvs;
|
||||
|
||||
void main()
|
||||
{
|
||||
vec4 world_space_vert = u_modelMatrix * a_vertex;
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * world_space_vert;
|
||||
|
||||
v_vertex = world_space_vert.xyz;
|
||||
v_normal = (u_normalMatrix * normalize(a_normal)).xyz;
|
||||
|
||||
v_uvs = a_uvs;
|
||||
}
|
||||
|
||||
fragment41core =
|
||||
#version 410
|
||||
uniform mediump vec4 u_ambientColor;
|
||||
uniform mediump vec4 u_diffuseColor;
|
||||
uniform highp vec3 u_lightPosition;
|
||||
uniform highp vec3 u_viewPosition;
|
||||
uniform mediump float u_opacity;
|
||||
uniform sampler2D u_texture;
|
||||
|
||||
in highp vec3 v_vertex;
|
||||
in highp vec3 v_normal;
|
||||
in highp vec2 v_uvs;
|
||||
out vec4 frag_color;
|
||||
|
||||
void main()
|
||||
{
|
||||
mediump vec4 final_color = vec4(0.0);
|
||||
|
||||
/* Ambient Component */
|
||||
final_color += u_ambientColor;
|
||||
|
||||
highp vec3 normal = normalize(v_normal);
|
||||
highp vec3 light_dir = normalize(u_lightPosition - v_vertex);
|
||||
|
||||
/* Diffuse Component */
|
||||
highp float n_dot_l = clamp(dot(normal, light_dir), 0.0, 1.0);
|
||||
final_color += (n_dot_l * u_diffuseColor);
|
||||
|
||||
final_color.a = u_opacity;
|
||||
|
||||
lowp vec4 texture = texture(u_texture, v_uvs);
|
||||
final_color = mix(final_color, texture, texture.a);
|
||||
|
||||
frag_color = final_color;
|
||||
}
|
||||
|
||||
[defaults]
|
||||
u_ambientColor = [0.3, 0.3, 0.3, 1.0]
|
||||
u_diffuseColor = [1.0, 1.0, 1.0, 1.0]
|
||||
u_opacity = 0.5
|
||||
u_texture = 0
|
||||
|
||||
[bindings]
|
||||
u_modelMatrix = model_matrix
|
||||
u_viewMatrix = view_matrix
|
||||
u_projectionMatrix = projection_matrix
|
||||
u_normalMatrix = normal_matrix
|
||||
u_lightPosition = light_0_position
|
||||
u_viewPosition = camera_position
|
||||
|
||||
[attributes]
|
||||
a_vertex = vertex
|
||||
a_normal = normal
|
||||
a_uvs = uv0
|
||||
8
plugins/PaintTool/plugin.json
Normal file
8
plugins/PaintTool/plugin.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Paint Tools",
|
||||
"author": "UltiMaker",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the paint tools.",
|
||||
"api": 8,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -203,9 +203,9 @@ class SimulationPass(RenderPass):
|
|||
self._layer_shader.setUniformValue("u_next_vertex", not_a_vector)
|
||||
self._layer_shader.setUniformValue("u_last_line_ratio", 1.0)
|
||||
|
||||
# The first line does not have a previous line: add a MoveCombingType in front for start detection
|
||||
# The first line does not have a previous line: add a MoveUnretractedType in front for start detection
|
||||
# this way the first start of the layer can also be drawn
|
||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveUnretractedType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||
# Remove the last element
|
||||
prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
|
||||
layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
|
||||
|
|
|
|||
|
|
@ -608,8 +608,10 @@ class SimulationView(CuraView):
|
|||
visible_line_types.append(LayerPolygon.SupportInterfaceType)
|
||||
visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added
|
||||
if self.getShowTravelMoves():
|
||||
visible_line_types.append(LayerPolygon.MoveCombingType)
|
||||
visible_line_types.append(LayerPolygon.MoveRetractionType)
|
||||
visible_line_types.append(LayerPolygon.MoveUnretractedType)
|
||||
visible_line_types.append(LayerPolygon.MoveRetractedType)
|
||||
visible_line_types.append(LayerPolygon.MoveWhileRetractingType)
|
||||
visible_line_types.append(LayerPolygon.MoveWhileUnretractingType)
|
||||
|
||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||
layer_data = node.callDecoration("getLayerData")
|
||||
|
|
|
|||
|
|
@ -227,29 +227,52 @@ Cura.ExpandableComponent
|
|||
id: typesLegendModel
|
||||
Component.onCompleted:
|
||||
{
|
||||
const travelsTypesModel = [
|
||||
{
|
||||
label: catalog.i18nc("@label", "Unretracted"),
|
||||
colorId: "layerview_move_combing"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Retracted"),
|
||||
colorId: "layerview_move_retraction"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Retracting"),
|
||||
colorId: "layerview_move_while_retracting"
|
||||
},
|
||||
{
|
||||
label: catalog.i18nc("@label", "Unretracting"),
|
||||
colorId: "layerview_move_while_unretracting"
|
||||
}
|
||||
];
|
||||
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Travels"),
|
||||
initialValue: viewSettings.show_travel_moves,
|
||||
preference: "layerview/show_travel_moves",
|
||||
colorId: "layerview_move_combing"
|
||||
colorId: "layerview_move_combing",
|
||||
subTypesModel: travelsTypesModel
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Helpers"),
|
||||
initialValue: viewSettings.show_helpers,
|
||||
preference: "layerview/show_helpers",
|
||||
colorId: "layerview_support"
|
||||
colorId: "layerview_support",
|
||||
subTypesModel: []
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Shell"),
|
||||
initialValue: viewSettings.show_skin,
|
||||
preference: "layerview/show_skin",
|
||||
colorId: "layerview_inset_0"
|
||||
colorId: "layerview_inset_0",
|
||||
subTypesModel: []
|
||||
});
|
||||
typesLegendModel.append({
|
||||
label: catalog.i18nc("@label", "Infill"),
|
||||
initialValue: viewSettings.show_infill,
|
||||
preference: "layerview/show_infill",
|
||||
colorId: "layerview_infill"
|
||||
colorId: "layerview_infill",
|
||||
subTypesModel: []
|
||||
});
|
||||
if (! UM.SimulationView.compatibilityMode)
|
||||
{
|
||||
|
|
@ -257,7 +280,8 @@ Cura.ExpandableComponent
|
|||
label: catalog.i18nc("@label", "Starts"),
|
||||
initialValue: viewSettings.show_starts,
|
||||
preference: "layerview/show_starts",
|
||||
colorId: "layerview_starts"
|
||||
colorId: "layerview_starts",
|
||||
subTypesModel: []
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -273,6 +297,7 @@ Cura.ExpandableComponent
|
|||
|
||||
Rectangle
|
||||
{
|
||||
id: rectangleColor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: legendModelCheckBox.right
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
|
|
@ -281,6 +306,58 @@ Cura.ExpandableComponent
|
|||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
visible: viewSettings.show_legend
|
||||
|
||||
MouseArea
|
||||
{
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
acceptedButtons: Qt.NoButton
|
||||
enabled: subTypesModel.count > 0
|
||||
|
||||
onEntered: tooltip.show()
|
||||
onExited: tooltip.hide()
|
||||
|
||||
UM.ToolTip
|
||||
{
|
||||
id: tooltip
|
||||
delay: 0
|
||||
width: subTypesColumn.implicitWidth + 2 * UM.Theme.getSize("thin_margin").width
|
||||
height: subTypesColumn.implicitHeight + 2 * UM.Theme.getSize("thin_margin").width
|
||||
|
||||
contentItem: Column
|
||||
{
|
||||
id: subTypesColumn
|
||||
padding: 0
|
||||
spacing: UM.Theme.getSize("layerview_row_spacing").height
|
||||
|
||||
Repeater
|
||||
{
|
||||
model: subTypesModel
|
||||
UM.Label
|
||||
{
|
||||
text: label
|
||||
|
||||
height: UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("default_lining").height
|
||||
width: UM.Theme.getSize("layerview_menu_size").width
|
||||
color: UM.Theme.getColor("setting_control_text")
|
||||
Rectangle
|
||||
{
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.right: parent.right
|
||||
|
||||
width: UM.Theme.getSize("layerview_legend_size").width
|
||||
height: UM.Theme.getSize("layerview_legend_size").height
|
||||
|
||||
color: UM.Theme.getColor(model.colorId)
|
||||
|
||||
border.width: UM.Theme.getSize("default_lining").width
|
||||
border.color: UM.Theme.getColor("lining")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UM.Label
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ vertex =
|
|||
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||
// shade the color depending on the extruder index
|
||||
v_color = a_color;
|
||||
// 8 and 9 are travel moves
|
||||
if ((a_line_type != 8.0) && (a_line_type != 9.0)) {
|
||||
// 8, 9, 12 and 13 are travel moves
|
||||
if ((a_line_type != 8.0) && (a_line_type != 9.0) && (a_line_type != 12.0) && (a_line_type != 13.0)) {
|
||||
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
}
|
||||
|
||||
|
|
@ -48,7 +48,9 @@ fragment =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
@ -100,7 +102,7 @@ vertex41core =
|
|||
{
|
||||
gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * a_vertex;
|
||||
v_color = a_color;
|
||||
if ((a_line_type != 8) && (a_line_type != 9)) {
|
||||
if ((a_line_type != 8) && (a_line_type != 9) && (a_line_type != 12) && (a_line_type != 13)) {
|
||||
v_color = (a_extruder == u_active_extruder) ? v_color : vec4(u_shade_factor * v_color.rgb, v_color.a);
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +122,9 @@ fragment41core =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,22 +228,26 @@ geometry41core =
|
|||
{
|
||||
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||
|
||||
vec4 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||
// Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position
|
||||
vec3 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz;
|
||||
vec4 g_vertex_offset_horz;
|
||||
vec3 g_vertex_normal_vert;
|
||||
vec4 g_vertex_offset_vert;
|
||||
vec3 g_vertex_normal_horz_head;
|
||||
vec4 g_vertex_offset_horz_head;
|
||||
vec3 g_axial_plan_vector;
|
||||
vec3 g_radial_plan_vector;
|
||||
|
||||
float size_x;
|
||||
float size_y;
|
||||
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) &&
|
||||
(v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) {
|
||||
return;
|
||||
}
|
||||
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
// See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10) || v_line_type[0] == 11)) {
|
||||
|
|
@ -256,7 +260,7 @@ geometry41core =
|
|||
return;
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
|
|
@ -264,26 +268,47 @@ geometry41core =
|
|||
}
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position; //Actual movement exhibited by the line.
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z)); //Lengthwise normal vector pointing backwards.
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector pointing backwards.
|
||||
g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line.
|
||||
|
||||
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x)); //Normal vector pointing right.
|
||||
if (g_vertex_delta == vec3(0.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_vertex_delta.y == 0.0)
|
||||
{
|
||||
// vector is in the horizontal plan, radial vector is a simple rotation around Y axis
|
||||
g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x);
|
||||
}
|
||||
else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0)
|
||||
{
|
||||
// delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views
|
||||
g_radial_plan_vector = vec3(1.0, 0.0, -1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delta vector is completely 3D
|
||||
g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan
|
||||
g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right.
|
||||
}
|
||||
|
||||
g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector
|
||||
|
||||
g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right.
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right.
|
||||
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector.
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness.
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) { //Travel or retraction moves.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) { //Travel or retraction moves.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
|
||||
// Travels: flat plane with pointy ends
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
|
|
@ -308,8 +333,8 @@ geometry41core =
|
|||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex.
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex.
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head); //Line end, tip.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip.
|
||||
|
||||
// All normal lines are rendered as 3d tubes.
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
|
@ -328,14 +353,14 @@ geometry41core =
|
|||
// left side
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
@ -343,14 +368,14 @@ geometry41core =
|
|||
// right side
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
|
|||
|
|
@ -95,22 +95,26 @@ geometry41core =
|
|||
{
|
||||
highp mat4 viewProjectionMatrix = u_projectionMatrix * u_viewMatrix;
|
||||
|
||||
vec4 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz; // horizontal and vertical in respect to layers
|
||||
vec4 g_vertex_offset_horz; // vec4 to match gl_in[x].gl_Position
|
||||
// Vertices are declared as vec4 so that they can be used for calculations with gl_in[x].gl_Position
|
||||
vec3 g_vertex_delta;
|
||||
vec3 g_vertex_normal_horz;
|
||||
vec4 g_vertex_offset_horz;
|
||||
vec3 g_vertex_normal_vert;
|
||||
vec4 g_vertex_offset_vert;
|
||||
vec3 g_vertex_normal_horz_head;
|
||||
vec4 g_vertex_offset_horz_head;
|
||||
vec3 g_axial_plan_vector;
|
||||
vec3 g_radial_plan_vector;
|
||||
|
||||
float size_x;
|
||||
float size_y;
|
||||
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) && (v_line_type[0] != 8) && (v_line_type[0] != 9)) {
|
||||
if ((v_extruder_opacity[0][int(mod(v_extruder[0], 4))][v_extruder[0] / 4] == 0.0) &&
|
||||
(v_line_type[0] != 8) && (v_line_type[0] != 9) && (v_line_type[0] != 12) && (v_line_type[0] != 13)) {
|
||||
return;
|
||||
}
|
||||
// See LayerPolygon; 8 is MoveCombingType, 9 is RetractionType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9))) {
|
||||
// See LayerPolygon; 8 is MoveUnretractedType, 9 is RetractionType, 12 is MoveWhileRetractingType, 13 is MoveWhileUnretractingType
|
||||
if ((u_show_travel_moves == 0) && ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13))) {
|
||||
return;
|
||||
}
|
||||
if ((u_show_helpers == 0) && ((v_line_type[0] == 4) || (v_line_type[0] == 5) || (v_line_type[0] == 7) || (v_line_type[0] == 10))) {
|
||||
|
|
@ -123,7 +127,7 @@ geometry41core =
|
|||
return;
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
// fixed size for movements
|
||||
size_x = 0.05;
|
||||
} else {
|
||||
|
|
@ -131,93 +135,114 @@ geometry41core =
|
|||
}
|
||||
size_y = v_line_dim[1].y / 2 + 0.01;
|
||||
|
||||
g_vertex_delta = gl_in[1].gl_Position - gl_in[0].gl_Position;
|
||||
g_vertex_normal_horz_head = normalize(vec3(-g_vertex_delta.x, -g_vertex_delta.y, -g_vertex_delta.z));
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0);
|
||||
g_vertex_delta = (gl_in[1].gl_Position - gl_in[0].gl_Position).xyz; //Actual movement exhibited by the line.
|
||||
|
||||
g_vertex_normal_horz = normalize(vec3(g_vertex_delta.z, g_vertex_delta.y, -g_vertex_delta.x));
|
||||
if (g_vertex_delta == vec3(0.0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //size * g_vertex_normal_horz;
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0);
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0);
|
||||
if (g_vertex_delta.y == 0.0)
|
||||
{
|
||||
// vector is in the horizontal plan, radial vector is a simple rotation around Y axis
|
||||
g_radial_plan_vector = vec3(g_vertex_delta.z, 0.0, -g_vertex_delta.x);
|
||||
}
|
||||
else if(g_vertex_delta.x == 0.0 && g_vertex_delta.z == 0.0)
|
||||
{
|
||||
// delta vector is purely vertical, display the line rotated vertically so that it is visible in front and side views
|
||||
g_radial_plan_vector = vec3(1.0, 0.0, -1.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// delta vector is completely 3D
|
||||
g_axial_plan_vector = vec3(g_vertex_delta.x, 0.0, g_vertex_delta.z); // Vector projected in the horizontal plan
|
||||
g_radial_plan_vector = cross(g_vertex_delta, g_axial_plan_vector); // Radial vector in the horizontal plan, pointing right.
|
||||
}
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9)) {
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
g_vertex_normal_horz_head = normalize(g_vertex_delta); //Lengthwise normal vector
|
||||
g_vertex_offset_horz_head = vec4(g_vertex_normal_horz_head * size_x, 0.0); //Lengthwise offset vector
|
||||
|
||||
g_vertex_normal_horz = normalize(g_radial_plan_vector); //Normal vector pointing right.
|
||||
g_vertex_offset_horz = vec4(g_vertex_normal_horz * size_x, 0.0); //Offset vector pointing right.
|
||||
|
||||
g_vertex_normal_vert = vec3(0.0, 1.0, 0.0); //Upwards normal vector.
|
||||
g_vertex_offset_vert = vec4(g_vertex_normal_vert * size_y, 0.0); //Upwards offset vector. Goes up by half the layer thickness.
|
||||
|
||||
if ((v_line_type[0] == 8) || (v_line_type[0] == 9) || (v_line_type[0] == 12) || (v_line_type[0] == 13)) {
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 va_up = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 va_down = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head + g_vertex_offset_vert);
|
||||
vec4 vb_down = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
vec4 vb_up = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz + g_vertex_offset_vert);
|
||||
|
||||
// Travels: flat plane with pointy ends
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_head);
|
||||
//And reverse so that the line is also visible from the back side.
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_up);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_down);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_up);
|
||||
|
||||
EndPrimitive();
|
||||
} else {
|
||||
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz);
|
||||
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz);
|
||||
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert);
|
||||
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert);
|
||||
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz);
|
||||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz);
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert);
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert);
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz_head);
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz_head);
|
||||
vec4 va_m_horz = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz); //Line start, left vertex.
|
||||
vec4 vb_m_horz = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_horz); //Line end, left vertex.
|
||||
vec4 va_p_vert = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_vert); //Line start, top vertex.
|
||||
vec4 vb_p_vert = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_vert); //Line end, top vertex.
|
||||
vec4 va_p_horz = viewProjectionMatrix * (gl_in[0].gl_Position + g_vertex_offset_horz); //Line start, right vertex.
|
||||
vec4 vb_p_horz = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz); //Line end, right vertex.
|
||||
vec4 va_m_vert = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_vert); //Line start, bottom vertex.
|
||||
vec4 vb_m_vert = viewProjectionMatrix * (gl_in[1].gl_Position - g_vertex_offset_vert); //Line end, bottom vertex.
|
||||
vec4 va_head = viewProjectionMatrix * (gl_in[0].gl_Position - g_vertex_offset_horz_head); //Line start, tip.
|
||||
vec4 vb_head = viewProjectionMatrix * (gl_in[1].gl_Position + g_vertex_offset_horz_head); //Line end, tip.
|
||||
|
||||
// All normal lines are rendered as 3d tubes.
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// left side
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_vert, va_p_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[0], g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[0], -g_vertex_normal_horz, va_m_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], g_vertex_normal_horz, va_p_horz);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_vert, va_m_vert);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz_head, va_head);
|
||||
myEmitVertex(v_vertex[0], v_color[1], -g_vertex_normal_horz, va_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
// right side
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_vert, vb_p_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz, vb_m_horz);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_vert, vb_m_vert);
|
||||
myEmitVertex(v_vertex[1], v_color[1], -g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz_head, vb_head);
|
||||
myEmitVertex(v_vertex[1], v_color[1], g_vertex_normal_horz, vb_p_horz);
|
||||
|
||||
EndPrimitive();
|
||||
|
|
|
|||
|
|
@ -48,8 +48,10 @@ fragment =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5))
|
||||
{ // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
{
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
@ -124,7 +126,9 @@ fragment41core =
|
|||
|
||||
void main()
|
||||
{
|
||||
if ((u_show_travel_moves == 0) && (v_line_type >= 7.5) && (v_line_type <= 9.5)) { // actually, 8 and 9
|
||||
// travel moves: 8, 9, 12, 13
|
||||
if ((u_show_travel_moves == 0) && (((v_line_type >= 7.5) && (v_line_type <= 9.5)) ||
|
||||
((v_line_type >= 11.5) && (v_line_type <= 13.5)))) {
|
||||
// discard movements
|
||||
discard;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
# Copyright (c) 2021 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os.path
|
||||
from UM.View.View import View
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Resources import Resources
|
||||
from PyQt6.QtGui import QOpenGLContext, QDesktopServices, QImage
|
||||
from PyQt6.QtCore import QSize, QUrl
|
||||
from PyQt6.QtGui import QDesktopServices, QImage
|
||||
from PyQt6.QtCore import QUrl
|
||||
|
||||
import numpy as np
|
||||
import time
|
||||
|
|
@ -36,11 +35,13 @@ class SolidView(View):
|
|||
"""Standard view for mesh models."""
|
||||
|
||||
_show_xray_warning_preference = "view/show_xray_warning"
|
||||
_show_overhang_preference = "view/show_overhang"
|
||||
_paint_active_preference = "view/paint_active"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
application = Application.getInstance()
|
||||
application.getPreferences().addPreference("view/show_overhang", True)
|
||||
application.getPreferences().addPreference(self._show_overhang_preference, True)
|
||||
application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._enabled_shader = None
|
||||
self._disabled_shader = None
|
||||
|
|
@ -212,7 +213,7 @@ class SolidView(View):
|
|||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if Application.getInstance().getPreferences().getValue("view/show_overhang"):
|
||||
if Application.getInstance().getPreferences().getValue(self._show_overhang_preference):
|
||||
# Make sure the overhang angle is valid before passing it to the shader
|
||||
if self._support_angle >= 0 and self._support_angle <= 90:
|
||||
self._enabled_shader.setUniformValue("u_overhangAngle", math.cos(math.radians(90 - self._support_angle)))
|
||||
|
|
|
|||
|
|
@ -9311,6 +9311,32 @@
|
|||
"default_value": true,
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"retraction_during_travel_ratio":
|
||||
{
|
||||
"label": "Retraction During Travel Move",
|
||||
"description": "<html>The ratio of retraction performed during the travel move, with the remainder completed while the nozzle is stationary, before traveling<ul><li>When 0, the entire retraction is performed while stationary, before the travel begins</li><li>When 100, the entire retraction is performed during the travel move, bypassing the stationary phase</li></ul></html>",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"prime_during_travel_ratio":
|
||||
{
|
||||
"label": "Prime During Travel Move",
|
||||
"description": "<html>The ratio of priming performed during the travel move, with the remainder completed while the nozzle is stationary, after traveling<ul><li>When 0, the entire priming is performed while stationary, after the travel ends</li><li>When 100, the entire priming is performed during the travel move, allowing the print to start immediately</li></ul></html>",
|
||||
"unit": "%",
|
||||
"type": "float",
|
||||
"default_value": 0,
|
||||
"minimum_value": 0,
|
||||
"maximum_value": 100,
|
||||
"enabled": "retraction_enable and not machine_firmware_retract and machine_gcode_flavor != \"UltiGCode\" and machine_gcode_flavor != \"BFB\"",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"scarf_joint_seam_length":
|
||||
{
|
||||
"label": "Scarf Seam Length",
|
||||
|
|
|
|||
|
|
@ -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…
Add table
Add a link
Reference in a new issue