123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137 |
- # Copyright (c) 2022 Ultimaker B.V.
- # Cura is released under the terms of the LGPLv3 or higher.
- from PyQt5.QtCore import QTimer # To refresh not too often.
- from typing import Optional, TYPE_CHECKING
- import numpy
- from cura.CuraApplication import CuraApplication
- from cura.CuraView import CuraView
- from UM.Mesh.MeshData import MeshData
- from UM.PluginRegistry import PluginRegistry
- from .StructureNode import StructureNode
- if TYPE_CHECKING:
- import pyArcus
- class StructureView(CuraView):
- """
- View the structure types.
- """
- _color_map = {
- 0: "layerview_inset_x",
- 1: "layerview_skin",
- 2: "layerview_infill",
- 3: "layerview_support",
- 4: "layerview_ghost",
- 5: "layerview_skirt"
- }
- """
- Mapping which color theme entry to display for each type of structure.
- The numbers here should match the structure types in Cura.proto under StructurePolygon.Type
- """
- _refresh_cooldown = 0.1 # In seconds, minimum time between refreshes of the rendered mesh.
- def __init__(self):
- super().__init__(parent = None, use_empty_menu_placeholder = True)
- self._scene_node = None # type: Optional[StructureNode] # All structure data will be under this node. Will be generated on first message received (since there is no scene yet at init).
- self._capacity = 3 * 1000000 # Start with some allocation to prevent having to reallocate all the time. Preferably a multiple of 3 (for triangles).
- self._vertices = numpy.ndarray((self._capacity, 3), dtype = numpy.single)
- self._indices = numpy.arange(self._capacity, dtype = numpy.int32).reshape((int(self._capacity / 3), 3)) # Since we're using a triangle list, the indices are simply increasing linearly.
- self._normals = numpy.repeat(numpy.array([[0.0, 1.0, 0.0]], dtype = numpy.single), self._capacity, axis = 0) # All normals are pointing up (to positive Y).
- self._colors = numpy.repeat(numpy.array([[0.0, 0.0, 0.0, 1.0]], dtype = numpy.single), self._capacity, axis = 0) # No colors yet.
- self._layers = numpy.repeat(-1, self._capacity) # To mask out certain layers for layer view.
- self._current_index = 0 # type: int # Where to add new data.
- self._refresh_timer = QTimer()
- self._refresh_timer.setInterval(1000 * self._refresh_cooldown)
- self._refresh_timer.setSingleShot(True)
- self._refresh_timer.timeout.connect(self._updateScene)
- plugin_registry = PluginRegistry.getInstance()
- self._enabled = "StructureView" not in plugin_registry.getDisabledPlugins() # Don't influence performance if this plug-in is disabled.
- if self._enabled:
- engine = plugin_registry.getPluginObject("CuraEngineBackend")
- engine.structurePolygonReceived.connect(self._onStructurePolygonReceived) # type: ignore
- CuraApplication.getInstance().initializationFinished.connect(self._createSceneNode)
- def _createSceneNode(self):
- scene = CuraApplication.getInstance().getController().getScene()
- if not self._scene_node:
- self._scene_node = StructureNode(parent = scene.getRoot())
- scene.sceneChanged.connect(self._clear)
- def _onStructurePolygonReceived(self, message: "pyArcus.PythonMessage") -> None:
- """
- Store the structure polygon in the scene node's mesh data when we receive one.
- :param message: A message received from CuraEngine containing a structure polygon.
- """
- num_vertices = int(len(message.points) / 4 / 3) # Number of bytes, / 4 for bytes per float, / 3 for X, Y and Z coordinates.
- to = self._current_index + num_vertices
- if to >= self._capacity:
- self._reallocate(self._current_index + num_vertices)
- # Fill the existing buffers with data in our region.
- vertex_data = numpy.frombuffer(message.points, dtype = numpy.single)
- vertex_data = numpy.reshape(vertex_data, newshape = (num_vertices, 3))
- self._vertices[self._current_index:to] = vertex_data
- self._colors[self._current_index:to] = CuraApplication.getInstance().getTheme().getColor(self._color_map.get(message.type, "layerview_none")).getRgbF()
- self._layers[self._current_index:to] = message.layer_index
- self._current_index += num_vertices
- if not self._refresh_timer.isActive(): # Don't refresh if the previous refresh was too recent.
- self._refresh_timer.start()
- def _reallocate(self, minimum_capacity: int) -> None:
- """
- Increase capacity to be able to hold at least a given amount of vertices.
- """
- new_capacity = self._capacity
- while minimum_capacity > new_capacity:
- new_capacity *= 2
- self._vertices.resize((new_capacity, 3))
- self._indices = numpy.arange(new_capacity, dtype = numpy.int32).reshape((int(new_capacity / 3), 3))
- self._normals = numpy.repeat(numpy.array([[0.0, 1.0, 0.0]], dtype = numpy.single), new_capacity, axis = 0)
- self._colors.resize((new_capacity, 4))
- self._layers.resize((new_capacity, ))
- self._capacity = new_capacity
- def _updateScene(self) -> None:
- """
- After receiving new data, makes sure that the data gets visualised in the 3D scene.
- """
- if not self._scene_node:
- return
- self._scene_node.setMeshData(MeshData(
- vertices = self._vertices[0:self._current_index],
- normals = self._normals[0:self._current_index],
- indices = self._indices[0:int(self._current_index / 3)],
- colors = self._colors[0:self._current_index]
- ))
- def _clear(self, source: "SceneNode") -> None:
- """
- Removes the structure data from the scene when the scene changes.
- :param source: The scene node that changed.
- """
- scene = CuraApplication.getInstance().getController().getScene()
- if not source.callDecoration("isSliceable") and source != scene.getRoot():
- return
- if source.callDecoration("getBuildPlateNumber") is None: # Not on the build plate.
- return
- if not source.callDecoration("isGroup"):
- mesh_data = source.getMeshData()
- if mesh_data is None or mesh_data.getVertices() is None:
- return
- if self._current_index != 0:
- self._current_index = 0
- self._updateScene()
|