Repository: abey79/vpype

Issue:
When presented with an empty layer, the viewer fails to show anything
If (for instance) a crop causes one of the layers to be empty, a subsequent show will result in the viewing failing (hangs with a black window).
When presented with an empty layer, the viewer fails to show anything
If (for instance) a crop causes one of the layers to be empty, a subsequent show will result in the viewing failing (hangs with a black window).


Discussion:
Thanks for the feedback. Can you provide the traceback? Due to #320 I'm currently limited in my ability to run the viewer.
Traceback:
```/tmp $ vpype read blah.svg crop 6.5in 3in 3in 2in  show
qt.qpa.window: <QNSWindow: 0x7f87f9d0dd70; contentView=<QNSView: 0x7f87f9d06470; QCocoaWindow(0x7f87f98a6c80, window=QWidgetWindow(0x7f87f9af50c0, name="QGLWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
qt.qpa.window: <QNSWindow: 0x7f87f9d38720; contentView=<QNSView: 0x7f87f9d06470; QCocoaWindow(0x7f87f98a6c80, window=QWidgetWindow(0x7f87f9af50c0, name="QGLWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
qt.qpa.window: <QNSWindow: 0x7f87f9c7a310; contentView=<QNSView: 0x7f87f9c79cf0; QCocoaWindow(0x7f87f9c79c40, window=QWidgetWindow(0x7f87f9c763c0, name="QWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate```
If I may, for readability, please use triple back-quote instead of single-quote so that line breaks aren't lost.
Ah--much better.  thanks!
Thanks for the feedback. Can you provide the traceback? Due to #320 I'm currently limited in my ability to run the viewer.
Traceback:
```/tmp $ vpype read blah.svg crop 6.5in 3in 3in 2in  show
qt.qpa.window: <QNSWindow: 0x7f87f9d0dd70; contentView=<QNSView: 0x7f87f9d06470; QCocoaWindow(0x7f87f98a6c80, window=QWidgetWindow(0x7f87f9af50c0, name="QGLWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
qt.qpa.window: <QNSWindow: 0x7f87f9d38720; contentView=<QNSView: 0x7f87f9d06470; QCocoaWindow(0x7f87f98a6c80, window=QWidgetWindow(0x7f87f9af50c0, name="QGLWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
qt.qpa.window: <QNSWindow: 0x7f87f9c7a310; contentView=<QNSView: 0x7f87f9c79cf0; QCocoaWindow(0x7f87f9c79c40, window=QWidgetWindow(0x7f87f9c763c0, name="QWidgetClassWindow"))>> has active key-value observers (KVO)! These will stop working now that the window is recreated, and will result in exceptions when the observers are removed. Break in QCocoaWindow::recreateWindowIfNeeded to debug.
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/qtviewer/viewer.py", line 135, in paintGL
    self.engine.render()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 387, in render
    self._rebuild()
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/engine.py", line 444, in _rebuild
    LineCollectionPreviewPainter(
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 289, in __init__
    vertices, indices = self._build_buffers(lc)
  File "/usr/local/lib/python3.9/site-packages/vpype_viewer/_painters.py", line 338, in _build_buffers
    np.vstack([vp.as_vector(line).astype("f4") for line in lc]),
  File "<__array_function__ internals>", line 5, in vstack
  File "/usr/local/lib/python3.9/site-packages/numpy/core/shape_base.py", line 283, in vstack
    return _nx.concatenate(arrs, 0)
  File "<__array_function__ internals>", line 5, in concatenate
ValueError: need at least one array to concatenate```
If I may, for readability, please use triple back-quote instead of single-quote so that line breaks aren't lost.
Ah--much better.  thanks!

Resolving patch:
diff --git a/vpype_viewer/engine.py b/vpype_viewer/engine.py
--- a/vpype_viewer/engine.py
+++ b/vpype_viewer/engine.py
@@ -427,7 +427,11 @@ def _rebuild(self):
             color_index = 0
             for layer_id in sorted(self._document.layers):
                 layer_color: ColorType = _COLORS[color_index % len(_COLORS)]
+                color_index += 1
+
                 lc = self._document.layers[layer_id]
+                if lc.is_empty():
+                    continue
 
                 if self.view_mode == ViewMode.OUTLINE:
                     self._layer_painters[layer_id].append(
@@ -463,8 +467,6 @@ def _rebuild(self):
                         LineCollectionPointsPainter(self._ctx, lc=lc, color=layer_color)
                     )
 
-                color_index += 1
-
             page_size = self._document.page_size
             if page_size is not None:
                 self._paper_bounds_painter = PaperBoundsPainter(


Test patch:
diff --git a/tests/test_viewer.py b/tests/test_viewer.py
--- a/tests/test_viewer.py
+++ b/tests/test_viewer.py
@@ -14,6 +14,38 @@
 
 from .utils import TEST_FILE_DIRECTORY
 
+RENDER_KWARGS = [
+    pytest.param({"view_mode": ViewMode.OUTLINE}, id="outline"),
+    pytest.param({"view_mode": ViewMode.OUTLINE_COLORFUL}, id="outline_colorful"),
+    pytest.param({"view_mode": ViewMode.PREVIEW}, id="preview"),
+    pytest.param({"view_mode": ViewMode.OUTLINE, "show_points": True}, id="points"),
+    pytest.param(
+        {"view_mode": ViewMode.OUTLINE_COLORFUL, "show_points": True}, id="colorful_points"
+    ),
+    pytest.param({"view_mode": ViewMode.OUTLINE, "show_pen_up": True}, id="outline_pen_up"),
+    pytest.param({"view_mode": ViewMode.PREVIEW, "show_pen_up": True}, id="preview_pen_up"),
+    pytest.param(
+        {"view_mode": ViewMode.PREVIEW, "pen_opacity": 0.3}, id="preview_transparent"
+    ),
+    pytest.param({"view_mode": ViewMode.PREVIEW, "pen_width": 4.0}, id="preview_thick"),
+    pytest.param(
+        {"view_mode": ViewMode.OUTLINE, "show_ruler": True, "unit_type": UnitType.PIXELS},
+        id="outline_pixels",
+    ),
+    pytest.param(
+        {"view_mode": ViewMode.OUTLINE, "show_ruler": True, "unit_type": UnitType.METRIC},
+        id="outline_metric",
+    ),
+    pytest.param(
+        {
+            "view_mode": ViewMode.OUTLINE,
+            "show_ruler": True,
+            "unit_type": UnitType.IMPERIAL,
+        },
+        id="outline_imperial",
+    ),
+]
+
 
 # assert_image_similarity fixture added for automated exclusion on unsupported runner
 def test_viewer_engine_properties(assert_image_similarity):
@@ -67,44 +99,7 @@ def test_viewer_engine_properties(assert_image_similarity):
     ["misc/empty.svg", "misc/multilayer.svg", "issue_124/plotter.svg"],
     ids=lambda s: os.path.splitext(s)[0],
 )
-@pytest.mark.parametrize(
-    "render_kwargs",
-    [
-        pytest.param({"view_mode": ViewMode.OUTLINE}, id="outline"),
-        pytest.param({"view_mode": ViewMode.OUTLINE_COLORFUL}, id="outline_colorful"),
-        pytest.param({"view_mode": ViewMode.PREVIEW}, id="preview"),
-        pytest.param({"view_mode": ViewMode.OUTLINE, "show_points": True}, id="points"),
-        pytest.param(
-            {"view_mode": ViewMode.OUTLINE_COLORFUL, "show_points": True}, id="colorful_points"
-        ),
-        pytest.param(
-            {"view_mode": ViewMode.OUTLINE, "show_pen_up": True}, id="outline_pen_up"
-        ),
-        pytest.param(
-            {"view_mode": ViewMode.PREVIEW, "show_pen_up": True}, id="preview_pen_up"
-        ),
-        pytest.param(
-            {"view_mode": ViewMode.PREVIEW, "pen_opacity": 0.3}, id="preview_transparent"
-        ),
-        pytest.param({"view_mode": ViewMode.PREVIEW, "pen_width": 4.0}, id="preview_thick"),
-        pytest.param(
-            {"view_mode": ViewMode.OUTLINE, "show_ruler": True, "unit_type": UnitType.PIXELS},
-            id="outline_pixels",
-        ),
-        pytest.param(
-            {"view_mode": ViewMode.OUTLINE, "show_ruler": True, "unit_type": UnitType.METRIC},
-            id="outline_metric",
-        ),
-        pytest.param(
-            {
-                "view_mode": ViewMode.OUTLINE,
-                "show_ruler": True,
-                "unit_type": UnitType.IMPERIAL,
-            },
-            id="outline_imperial",
-        ),
-    ],
-)
+@pytest.mark.parametrize("render_kwargs", RENDER_KWARGS)
 def test_viewer(assert_image_similarity, file, render_kwargs):
     doc = vp.read_multilayer_svg(str(TEST_FILE_DIRECTORY / file), 0.4)
 
@@ -112,6 +107,14 @@ def test_viewer(assert_image_similarity, file, render_kwargs):
     assert_image_similarity(render_image(doc, (1024, 1024), **render_kwargs))
 
 
+@pytest.mark.parametrize("render_kwargs", RENDER_KWARGS)
+def test_viewer_empty_layer(assert_image_similarity, render_kwargs):
+    # Note: assert_image_similarity added to avoid running CI tests on Linux
+    doc = vp.Document()
+    doc.add(vp.LineCollection(), 1)
+    render_image(doc, (1024, 1024), **render_kwargs)
+
+
 def test_viewer_zoom_scale(assert_image_similarity):
     doc = vp.read_multilayer_svg(str(TEST_FILE_DIRECTORY / "issue_124/plotter.svg"), 0.4)
     renderer = ImageRenderer((1024, 1024))