@@ -101,6 +101,10 @@ def __init__(self, parent = None) -> None:
101101 self ._cumulative_line_duration_layer : Optional [int ] = None
102102 self ._cumulative_line_duration : List [float ] = []
103103
104+ # Cache for layer heights to avoid recalculating on every query
105+ self ._layer_heights_cache : dict [int , float ] = {}
106+ self ._layer_heights_cache_node_id : Optional [int ] = None # Track which node's data is cached
107+
104108 self ._global_container_stack : Optional [ContainerStack ] = None
105109 self ._proxy = None
106110
@@ -289,6 +293,88 @@ def getLayerData(self) -> Optional["LayerData"]:
289293 return layer_data .getLayer (self .getCurrentLayer ())
290294 return None
291295
296+ def _calculateLayerHeightsCache (self ) -> None :
297+ """Calculate and cache heights for all layers.
298+
299+ This method iterates through all layers once and stores their heights in a cache.
300+ Handles both sliced data (microns) and loaded gcode (millimeters).
301+ For layer 0 from gcode, uses thickness instead of height due to incorrect Z coordinates.
302+ Only recalculates if the layer data source has changed.
303+ """
304+ scene = self .getController ().getScene ()
305+ from cura .Scene .GCodeListDecorator import GCodeListDecorator
306+
307+ for node in DepthFirstIterator (scene .getRoot ()): # type: ignore
308+ layer_data = node .callDecoration ("getLayerData" )
309+ if not layer_data :
310+ continue
311+
312+ # Check if we already have cached data for this layer_data object
313+ # Use id of the layer_data itself, not the node, since node might be reused
314+ current_layer_data_id = id (layer_data )
315+ if self ._layer_heights_cache_node_id == current_layer_data_id and self ._layer_heights_cache :
316+ # Cache is still valid, no need to recalculate
317+ return
318+ # Cache is invalid or empty, recalculate
319+ self ._layer_heights_cache .clear ()
320+ self ._layer_heights_cache_node_id = current_layer_data_id
321+
322+ has_gcode_decorator = node .getDecorator (GCodeListDecorator ) is not None
323+
324+ # Process all layers at once
325+ for layer_id in layer_data .getLayers ():
326+ layer = layer_data .getLayer (layer_id )
327+ if not layer :
328+ continue
329+
330+ # If node has GCodeListDecorator, heights are already in millimeters (from gcode)
331+ if has_gcode_decorator :
332+ # Special case for layer 0: FlavorParser may get wrong Z coordinate (startup position)
333+ # Use thickness instead, which represents the actual layer height
334+ if layer_id == 0 and layer .thickness > 0 :
335+ self ._layer_heights_cache [layer_id ] = layer .thickness
336+ else :
337+ self ._layer_heights_cache [layer_id ] = layer .height
338+ # Otherwise, heights are in microns (backend/slicing), convert to mm
339+ else :
340+ self ._layer_heights_cache [layer_id ] = layer .height / 1000.0
341+
342+ # We found layer data and cached it, no need to continue searching
343+ return
344+
345+ # No layer data found - clear the cache
346+ if self ._layer_heights_cache_node_id is not None :
347+ self ._layer_heights_cache .clear ()
348+ self ._layer_heights_cache_node_id = None
349+
350+ def _getLayerHeight (self , layer_number : int ) -> float :
351+ """Helper method to get the height of a specific layer in millimeters from cache.
352+
353+ :param layer_number: The layer number to get the height for.
354+ :return: The layer height in millimeters, or 0.0 if no data is available.
355+ """
356+ return self ._layer_heights_cache .get (layer_number , 0.0 )
357+
358+ def getCurrentLayerHeight (self ) -> float :
359+ """Get the height (z-coordinate) of the current layer in millimeters.
360+
361+ This returns the actual height from the layer data, which already takes into account:
362+ - Initial layer height (layer_height_0)
363+ - Adaptive layer heights
364+ - Regular layer height
365+ - Raft layers
366+
367+ Returns 0.0 if no layer data is available.
368+ """
369+ return self ._layer_heights_cache .get (self .getCurrentLayer (), 0.0 )
370+
371+ def getMinimumLayerHeight (self ) -> float :
372+ """Get the height (z-coordinate) of the minimum layer in millimeters.
373+
374+ Returns 0.0 if no layer data is available.
375+ """
376+ return self ._layer_heights_cache .get (self .getMinimumLayer (), 0.0 )
377+
292378 def getMinimumPath (self ) -> int :
293379 return self ._minimum_path_num
294380
@@ -304,6 +390,7 @@ def _onSceneChanged(self, node: "SceneNode") -> None:
304390 if node .getMeshData () is None :
305391 return
306392 self .setActivity (False )
393+ self ._calculateLayerHeightsCache ()
307394 self .calculateColorSchemeLimits ()
308395 self .calculateMaxLayers ()
309396 self .calculateMaxPathsOnLayer (self ._current_layer_num )
@@ -718,6 +805,7 @@ def event(self, event) -> bool:
718805 Application .getInstance ().getPreferences ().preferenceChanged .connect (self ._onPreferencesChanged )
719806 self ._controller .getScene ().getRoot ().childrenChanged .connect (self ._onSceneChanged )
720807
808+ self ._calculateLayerHeightsCache ()
721809 self .calculateColorSchemeLimits ()
722810 self .calculateMaxLayers ()
723811 self .calculateMaxPathsOnLayer (self ._current_layer_num )
0 commit comments