Compare commits

...

4 Commits

Author SHA1 Message Date
pre-commit-ci[bot] 0ae458e6e1 [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
2026-03-12 13:04:59 +00:00
Chris Hennes 71a2a2c801 Tests: Add TestOffscreenRenderer 2026-03-11 23:05:22 -05:00
Chris Hennes 4f85ddac22 Gui: Add missing PyCXX calls 2026-03-11 23:05:06 -05:00
Fasteroid e2ce1a4d52 add SoQtOffscreenRenderer::writeToBuffer( ) 2026-03-11 23:04:11 -05:00
5 changed files with 136 additions and 0 deletions
+29
View File
@@ -24,6 +24,7 @@
#include <Base/Interpreter.h>
#include "SoQtOffscreenRendererPy.h"
#include <QBuffer>
using namespace Gui;
@@ -166,6 +167,33 @@ Py::Object SoQtOffscreenRendererPy::writeToImage(const Py::Tuple& args)
}
PYCXX_VARARGS_METHOD_DECL(SoQtOffscreenRendererPy, writeToImage)
Py::Object SoQtOffscreenRendererPy::writeToBuffer(const Py::Tuple& args)
{
const char* format = "PNG";
int quality = -1;
if (!PyArg_ParseTuple(args.ptr(), "|si", &format, &quality)) {
throw Py::Exception();
}
QImage img;
renderer.writeToImage(img);
QByteArray byteArray;
QBuffer buffer(&byteArray);
buffer.open(QIODevice::WriteOnly);
if (!img.save(&buffer, format, quality)) {
throw Py::RuntimeError("Failed to save image to buffer");
}
buffer.close();
return Py::Bytes(byteArray.data(), byteArray.size());
}
PYCXX_VARARGS_METHOD_DECL(SoQtOffscreenRendererPy, writeToBuffer)
Py::Object SoQtOffscreenRendererPy::getWriteImageFiletypeInfo()
{
QStringList list = renderer.getWriteImageFiletypeInfo();
@@ -209,6 +237,7 @@ void SoQtOffscreenRendererPy::init_type()
);
PYCXX_ADD_VARARGS_METHOD(render, render, "render(node)");
PYCXX_ADD_VARARGS_METHOD(writeToImage, writeToImage, "writeToImage(string)");
PYCXX_ADD_VARARGS_METHOD(writeToBuffer, writeToBuffer, "writeToBuffer([string, int]) -> bytes");
PYCXX_ADD_NOARGS_METHOD(
getWriteImageFiletypeInfo,
getWriteImageFiletypeInfo,
+1
View File
@@ -55,6 +55,7 @@ public:
Py::Object render(const Py::Tuple&);
Py::Object writeToImage(const Py::Tuple&);
Py::Object writeToBuffer(const Py::Tuple&);
Py::Object getWriteImageFiletypeInfo();
private:
+1
View File
@@ -17,6 +17,7 @@ SET(Test_SRCS
testmakeWireString.py
TestPythonSyntax.py
TestPerf.py
TestOffscreenRenderer.py
TestTreeSelection.py
)
+1
View File
@@ -98,4 +98,5 @@ FreeCAD.__unit_test__ += [
"Menu.MenuDeleteCases",
"Menu.MenuCreateCases",
"GuiDocument",
"TestOffscreenRenderer",
]
+104
View File
@@ -0,0 +1,104 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
"""Tests for SoQtOffscreenRenderer, including writeToBuffer."""
import unittest
import FreeCAD
import FreeCADGui
class TestOffscreenRendererWriteToBuffer(unittest.TestCase):
"""Tests for the writeToBuffer method of SoQtOffscreenRenderer."""
def setUp(self):
from pivy import coin
self.coin = coin
# Build a minimal scene: camera + light + a simple shape
self.root = coin.SoSeparator()
cam = coin.SoOrthographicCamera()
light = coin.SoDirectionalLight()
cone = coin.SoCone()
self.root.addChild(cam)
self.root.addChild(light)
self.root.addChild(cone)
width, height = 64, 64
viewport = coin.SbViewportRegion(width, height)
cam.viewAll(self.root, viewport)
self.root.ref()
self.renderer = FreeCADGui.SoQtOffscreenRenderer(width, height)
self.renderer.setBackgroundColor(1.0, 1.0, 1.0)
self.renderer.render(self.root)
def tearDown(self):
self.root.unref()
def testWriteToBufferReturnsBytesDefaultPNG(self):
"""writeToBuffer() with no arguments returns non-empty bytes."""
result = self.renderer.writeToBuffer()
self.assertIsInstance(result, bytes)
self.assertGreater(len(result), 0)
def testWriteToBufferPNGSignature(self):
"""writeToBuffer() default output starts with the PNG signature."""
result = self.renderer.writeToBuffer()
png_signature = b"\x89PNG\r\n\x1a\n"
self.assertTrue(
result.startswith(png_signature), "Output does not start with PNG signature"
)
def testWriteToBufferExplicitPNG(self):
"""writeToBuffer('PNG') produces valid PNG data."""
result = self.renderer.writeToBuffer("PNG")
png_signature = b"\x89PNG\r\n\x1a\n"
self.assertTrue(result.startswith(png_signature))
def testWriteToBufferBMP(self):
"""writeToBuffer('BMP') produces data starting with the BMP signature."""
result = self.renderer.writeToBuffer("BMP")
self.assertIsInstance(result, bytes)
self.assertTrue(result.startswith(b"BM"), "Output does not start with BMP signature")
def testWriteToBufferJPEG(self):
"""writeToBuffer('JPEG') produces data starting with the JPEG SOI marker."""
result = self.renderer.writeToBuffer("JPEG")
self.assertIsInstance(result, bytes)
jpeg_soi = b"\xff\xd8\xff"
self.assertTrue(result.startswith(jpeg_soi), "Output does not start with JPEG SOI marker")
def testWriteToBufferWithQuality(self):
"""writeToBuffer('PNG', quality) accepts a quality parameter."""
result = self.renderer.writeToBuffer("PNG", 50)
self.assertIsInstance(result, bytes)
self.assertGreater(len(result), 0)
def testWriteToBufferInvalidFormatRaises(self):
"""writeToBuffer with an unsupported format raises RuntimeError."""
with self.assertRaises(RuntimeError):
self.renderer.writeToBuffer("NOTAFORMAT")
def testWriteToBufferConsistentOutput(self):
"""Two calls to writeToBuffer produce identical output."""
result1 = self.renderer.writeToBuffer("BMP")
result2 = self.renderer.writeToBuffer("BMP")
self.assertEqual(result1, result2)
def testWriteToBufferMatchesWriteToImage(self):
"""writeToBuffer('PNG') produces the same content as writeToImage for PNG."""
import tempfile
import os
buf_data = self.renderer.writeToBuffer("PNG")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp:
tmp_path = tmp.name
try:
self.renderer.writeToImage(tmp_path)
with open(tmp_path, "rb") as f:
file_data = f.read()
self.assertEqual(buf_data, file_data)
finally:
os.unlink(tmp_path)