@volter/blender-engine 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/browser/session.py +47 -8
- package/browser/volter_three.py +47 -8
- package/package.json +1 -1
package/browser/session.py
CHANGED
|
@@ -1898,7 +1898,7 @@ def _vertical_extent(cam, width, height):
|
|
|
1898
1898
|
return math.degrees(2.0 * math.atan(extent / (2.0 * float(cam.lens))))
|
|
1899
1899
|
|
|
1900
1900
|
|
|
1901
|
-
def _photograph(depsgraph, width, height):
|
|
1901
|
+
def _photograph(depsgraph, width, height, linear=False):
|
|
1902
1902
|
"""three.js IS the renderer: the render is a photograph of the scene the
|
|
1903
1903
|
engine holds, taken through the scene's own camera at `scene.render`'s
|
|
1904
1904
|
exact resolution. `bpy.ops.render.render` semantics are the contract."""
|
|
@@ -1946,13 +1946,17 @@ def _photograph(depsgraph, width, height):
|
|
|
1946
1946
|
# refused BY NAME on the other side (`blender-runtime-host.ts`).
|
|
1947
1947
|
if look not in ("None", "AgX - Base Contrast"):
|
|
1948
1948
|
render["look"] = look
|
|
1949
|
+
# The capture's scene-referred frame rides along when asked for: a render
|
|
1950
|
+
# result is what a render leaves, and it holds scene-linear pixels.
|
|
1951
|
+
if linear:
|
|
1952
|
+
render["linear"] = True
|
|
1949
1953
|
answer = SESSION.present(
|
|
1950
1954
|
{"position": position, "target": target, "up": up, "render": render}
|
|
1951
1955
|
)
|
|
1952
1956
|
if not isinstance(answer, dict) or "base64" not in answer:
|
|
1953
1957
|
raise RuntimeError("The renderer did not answer with a photograph")
|
|
1954
1958
|
_assert_photographed_from(answer.get("camera"), position, target, up)
|
|
1955
|
-
return base64.b64decode(answer["base64"]), render
|
|
1959
|
+
return base64.b64decode(answer["base64"]), render, answer
|
|
1956
1960
|
|
|
1957
1961
|
|
|
1958
1962
|
def _assert_photographed_from(reported, position, target, up):
|
|
@@ -2013,19 +2017,54 @@ class VolterRenderEngine(bpy.types.RenderEngine):
|
|
|
2013
2017
|
width = max(1, int(scene.render.resolution_x * scale))
|
|
2014
2018
|
height = max(1, int(scene.render.resolution_y * scale))
|
|
2015
2019
|
try:
|
|
2016
|
-
png, _render = _photograph(depsgraph, width, height)
|
|
2020
|
+
png, _render, answer = _photograph(depsgraph, width, height, linear=True)
|
|
2017
2021
|
except Exception as error: # noqa: BLE001 - reported to Blender as a render error
|
|
2018
2022
|
self.report({"ERROR"}, str(error))
|
|
2019
2023
|
return
|
|
2020
|
-
path = os.path.join(ROOT, "render-%d.png" % int(time.time() * 1000))
|
|
2021
|
-
with open(path, "wb") as fh:
|
|
2022
|
-
fh.write(png)
|
|
2023
2024
|
result = self.begin_result(0, 0, width, height)
|
|
2024
2025
|
try:
|
|
2025
|
-
result
|
|
2026
|
-
except (RuntimeError, AttributeError) as error:
|
|
2026
|
+
_fill_result(self, result, answer, png, width, height)
|
|
2027
|
+
except (RuntimeError, AttributeError, ValueError) as error:
|
|
2027
2028
|
self.report({"ERROR"}, "Render result could not take the photograph: %s" % error)
|
|
2028
2029
|
self.end_result(result)
|
|
2030
|
+
|
|
2031
|
+
|
|
2032
|
+
def _fill_result(engine, result, answer, png, width, height):
|
|
2033
|
+
"""THE RENDER RESULT HOLDS SCENE-LINEAR PIXELS, and Blender runs the scene's
|
|
2034
|
+
view transform over them wherever it shows or saves one (`write_still`, the
|
|
2035
|
+
Image Editor), as it does over Cycles'. The photograph's PNG is the display
|
|
2036
|
+
image -- the transform already ran -- so loading it put every render
|
|
2037
|
+
through AgX or Filmic twice. Measured 2026-09-28 by the model editor's
|
|
2038
|
+
render view: a background 53 against the viewport's 59, a shadow's blue 2
|
|
2039
|
+
against 7. The same capture's linear frame goes in instead: half floats,
|
|
2040
|
+
RGBA, bottom row first, which is Blender's own row order. Linear 0.005,
|
|
2041
|
+
0.05, 0.5 and 4.0 then save under AgX as 8, 58, 170 and 234 (the PNG
|
|
2042
|
+
path saved 3, 53, 160 and 190), and under Standard as 15, 64, 188 and 255,
|
|
2043
|
+
as the PNG path did.
|
|
2044
|
+
|
|
2045
|
+
A renderer that sends no linear frame gets the old path, and says so."""
|
|
2046
|
+
linear = answer.get("linearBase64") if isinstance(answer, dict) else None
|
|
2047
|
+
if linear and answer.get("linearWidth") == width and answer.get("linearHeight") == height:
|
|
2048
|
+
import numpy
|
|
2049
|
+
|
|
2050
|
+
pixels = numpy.frombuffer(base64.b64decode(linear), dtype=numpy.float16)
|
|
2051
|
+
if pixels.size != width * height * 4:
|
|
2052
|
+
raise ValueError(
|
|
2053
|
+
"the linear frame holds %d values for a %dx%d render" % (pixels.size, width, height)
|
|
2054
|
+
)
|
|
2055
|
+
result.layers[0].passes["Combined"].rect.foreach_set(pixels.astype(numpy.float32))
|
|
2056
|
+
return
|
|
2057
|
+
engine.report(
|
|
2058
|
+
{"WARNING"},
|
|
2059
|
+
"The renderer sent no scene-linear frame; the render result is its display image, "
|
|
2060
|
+
"which the view transform will run over again",
|
|
2061
|
+
)
|
|
2062
|
+
path = os.path.join(ROOT, "render-%d.png" % int(time.time() * 1000))
|
|
2063
|
+
with open(path, "wb") as fh:
|
|
2064
|
+
fh.write(png)
|
|
2065
|
+
try:
|
|
2066
|
+
result.layers[0].load_from_file(path)
|
|
2067
|
+
finally:
|
|
2029
2068
|
try:
|
|
2030
2069
|
os.unlink(path)
|
|
2031
2070
|
except OSError:
|
package/browser/volter_three.py
CHANGED
|
@@ -280,7 +280,7 @@ def _vertical_extent(cam, width, height):
|
|
|
280
280
|
return math.degrees(2.0 * math.atan(extent / (2.0 * float(cam.lens))))
|
|
281
281
|
|
|
282
282
|
|
|
283
|
-
def _photograph(depsgraph, width, height):
|
|
283
|
+
def _photograph(depsgraph, width, height, linear=False):
|
|
284
284
|
"""three.js IS the renderer: the render is a photograph of the scene the
|
|
285
285
|
engine holds, taken through the scene's own camera at `scene.render`'s
|
|
286
286
|
exact resolution. `bpy.ops.render.render` semantics are the contract."""
|
|
@@ -326,13 +326,17 @@ def _photograph(depsgraph, width, height):
|
|
|
326
326
|
# view itself -- the same table as no look at all.
|
|
327
327
|
if look not in ("None", "AgX - Base Contrast"):
|
|
328
328
|
render["look"] = look
|
|
329
|
+
# The capture's scene-referred frame rides along when asked for: a render
|
|
330
|
+
# result is what a render leaves, and it holds scene-linear pixels.
|
|
331
|
+
if linear:
|
|
332
|
+
render["linear"] = True
|
|
329
333
|
answer = _present(
|
|
330
334
|
{"position": position, "target": target, "up": up, "render": render}
|
|
331
335
|
)
|
|
332
336
|
if not isinstance(answer, dict) or "base64" not in answer:
|
|
333
337
|
raise RuntimeError("The renderer did not answer with a photograph")
|
|
334
338
|
_assert_photographed_from(answer.get("camera"), position, target, up)
|
|
335
|
-
return base64.b64decode(answer["base64"]), render
|
|
339
|
+
return base64.b64decode(answer["base64"]), render, answer
|
|
336
340
|
|
|
337
341
|
|
|
338
342
|
def _assert_photographed_from(reported, position, target, up):
|
|
@@ -391,19 +395,54 @@ class VolterRenderEngine(bpy.types.RenderEngine):
|
|
|
391
395
|
width = max(1, int(scene.render.resolution_x * scale))
|
|
392
396
|
height = max(1, int(scene.render.resolution_y * scale))
|
|
393
397
|
try:
|
|
394
|
-
png, _render = _photograph(depsgraph, width, height)
|
|
398
|
+
png, _render, answer = _photograph(depsgraph, width, height, linear=True)
|
|
395
399
|
except Exception as error: # noqa: BLE001 - reported to Blender as a render error
|
|
396
400
|
self.report({"ERROR"}, str(error))
|
|
397
401
|
return
|
|
398
|
-
path = os.path.join(ROOT, "render-%d.png" % int(time.time() * 1000))
|
|
399
|
-
with open(path, "wb") as fh:
|
|
400
|
-
fh.write(png)
|
|
401
402
|
result = self.begin_result(0, 0, width, height)
|
|
402
403
|
try:
|
|
403
|
-
result
|
|
404
|
-
except (RuntimeError, AttributeError) as error:
|
|
404
|
+
_fill_result(self, result, answer, png, width, height)
|
|
405
|
+
except (RuntimeError, AttributeError, ValueError) as error:
|
|
405
406
|
self.report({"ERROR"}, "Render result could not take the photograph: %s" % error)
|
|
406
407
|
self.end_result(result)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def _fill_result(engine, result, answer, png, width, height):
|
|
411
|
+
"""THE RENDER RESULT HOLDS SCENE-LINEAR PIXELS, and Blender runs the scene's
|
|
412
|
+
view transform over them wherever it shows or saves one (`write_still`, the
|
|
413
|
+
Image Editor), as it does over Cycles'. The photograph's PNG is the display
|
|
414
|
+
image -- the transform already ran -- so loading it put every render
|
|
415
|
+
through AgX or Filmic twice. Measured 2026-09-28 by the model editor's
|
|
416
|
+
render view: a background 53 against the viewport's 59, a shadow's blue 2
|
|
417
|
+
against 7. The same capture's linear frame goes in instead: half floats,
|
|
418
|
+
RGBA, bottom row first, which is Blender's own row order. Linear 0.005,
|
|
419
|
+
0.05, 0.5 and 4.0 then save under AgX as 8, 58, 170 and 234 (the PNG
|
|
420
|
+
path saved 3, 53, 160 and 190), and under Standard as 15, 64, 188 and 255,
|
|
421
|
+
as the PNG path did.
|
|
422
|
+
|
|
423
|
+
A renderer that sends no linear frame gets the old path, and says so."""
|
|
424
|
+
linear = answer.get("linearBase64") if isinstance(answer, dict) else None
|
|
425
|
+
if linear and answer.get("linearWidth") == width and answer.get("linearHeight") == height:
|
|
426
|
+
import numpy
|
|
427
|
+
|
|
428
|
+
pixels = numpy.frombuffer(base64.b64decode(linear), dtype=numpy.float16)
|
|
429
|
+
if pixels.size != width * height * 4:
|
|
430
|
+
raise ValueError(
|
|
431
|
+
"the linear frame holds %d values for a %dx%d render" % (pixels.size, width, height)
|
|
432
|
+
)
|
|
433
|
+
result.layers[0].passes["Combined"].rect.foreach_set(pixels.astype(numpy.float32))
|
|
434
|
+
return
|
|
435
|
+
engine.report(
|
|
436
|
+
{"WARNING"},
|
|
437
|
+
"The renderer sent no scene-linear frame; the render result is its display image, "
|
|
438
|
+
"which the view transform will run over again",
|
|
439
|
+
)
|
|
440
|
+
path = os.path.join(ROOT, "render-%d.png" % int(time.time() * 1000))
|
|
441
|
+
with open(path, "wb") as fh:
|
|
442
|
+
fh.write(png)
|
|
443
|
+
try:
|
|
444
|
+
result.layers[0].load_from_file(path)
|
|
445
|
+
finally:
|
|
407
446
|
try:
|
|
408
447
|
os.unlink(path)
|
|
409
448
|
except OSError:
|