@volter/blender-engine 0.1.127 → 0.1.128
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/blender-emscripten-engine.mts +32 -13
- package/browser/blender-engine.mts +2 -2
- package/browser/blender-wali-engine.mts +1 -1
- package/browser/protocol.ts +2 -0
- package/browser/runtime.ts +1 -1
- package/browser/session.py +65 -17
- package/browser/three/blender-display-NOTICE.txt +45 -0
- package/browser/three/blender-display-shaders.json +1 -0
- package/browser/three/blender-display-shaders.lut +0 -0
- package/browser/three/blender-display-transform.ts +139 -0
- package/browser/three/blender-graph-material.ts +34 -7
- package/browser/three/blender-node-graph.ts +15 -1
- package/browser/three/blender-object-info-materials.ts +75 -0
- package/browser/three/blender-physical-material.ts +3 -2
- package/browser/three/blender-runtime-armature.ts +1 -1
- package/browser/three/blender-runtime-lighting.ts +3 -1
- package/browser/three/blender-runtime-view.ts +16 -1
- package/browser/three/blender-single-scattering-sky.ts +128 -0
- package/browser/three/blender-sky.ts +19 -15
- package/browser/three/presenter.ts +8 -55
- package/browser/three/world-field-sampler.ts +2 -0
- package/browser/volter_three.py +4 -5
- package/browser/worker.ts +45 -12
- package/package.json +1 -1
|
@@ -151,7 +151,13 @@ function moduleFiles(module: BlenderModule): BlenderFiles {
|
|
|
151
151
|
*/
|
|
152
152
|
const ARTIFACT_CACHE = 'volter-blender-artifacts';
|
|
153
153
|
|
|
154
|
-
|
|
154
|
+
/**
|
|
155
|
+
* THE CACHE SPEEDS AN OPEN UP; IT NEVER DECIDES WHETHER BLENDER OPENS. A Cache Storage that cannot
|
|
156
|
+
* open or answer (measured 2026-10-07, headless Chrome on Windows: `caches.open` threw "Unexpected
|
|
157
|
+
* internal error", and the model failed to open on it) means the artifact is fetched from the editor
|
|
158
|
+
* as it would be with no digest; bytes that cannot be kept are used anyway. `note` says which, per artifact, as page output (not an unresolved console entry).
|
|
159
|
+
*/
|
|
160
|
+
async function cachedArtifact(file: string, digest: string | undefined, checkpoint: () => Promise<void>, note: (text: string) => void): Promise<Response> {
|
|
155
161
|
const bounded = (response: Response): Response => response.ok && response.body
|
|
156
162
|
? new Response(checkpointStream(response.body, checkpoint), { status: response.status, headers: response.headers })
|
|
157
163
|
: response;
|
|
@@ -159,18 +165,29 @@ async function cachedArtifact(file: string, digest: string | undefined, checkpoi
|
|
|
159
165
|
const fetchArtifact = () => startupOperation(url, 'fetch artifact', () => fetch(url));
|
|
160
166
|
if (!digest || typeof caches === 'undefined') return bounded(await fetchArtifact());
|
|
161
167
|
const key = `${url}?sha256=${digest}`;
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
|
|
168
|
+
const reason = (error: unknown) => (error instanceof Error ? error.message : String(error));
|
|
169
|
+
const opened = await startupOperation(url, 'open artifact cache', () => caches.open(ARTIFACT_CACHE))
|
|
170
|
+
.then(async (cache) => ({ cache, hit: await startupOperation(url, 'read artifact cache', () => cache.match(key)) }))
|
|
171
|
+
.catch((error: unknown) => {
|
|
172
|
+
note(`Blender's artifact cache is unavailable (${reason(error)}); ${file} is fetched from the editor instead.`);
|
|
173
|
+
return null;
|
|
174
|
+
});
|
|
175
|
+
if (!opened) return bounded(await fetchArtifact());
|
|
176
|
+
if (opened.hit) return bounded(opened.hit);
|
|
177
|
+
const { cache } = opened;
|
|
165
178
|
const response = bounded(await fetchArtifact());
|
|
166
179
|
if (!response.ok) return response;
|
|
167
|
-
// One build's bytes per file: an older build's are dropped as this one lands.
|
|
168
|
-
await startupOperation(url, 'prune previous artifact cache entries', async () => {
|
|
169
|
-
for (const old of await cache.keys()) if (old.url.startsWith(`${url}?sha256=`) && old.url !== key) await cache.delete(old);
|
|
170
|
-
});
|
|
171
180
|
const body = await startupOperation(url, 'read artifact body for cache', () => response.blob());
|
|
172
181
|
const kept = new Response(body, { headers: { 'content-type': response.headers.get('content-type') ?? 'application/octet-stream' } });
|
|
173
|
-
|
|
182
|
+
try {
|
|
183
|
+
// One build's bytes per file: an older build's are dropped as this one lands.
|
|
184
|
+
await startupOperation(url, 'prune previous artifact cache entries', async () => {
|
|
185
|
+
for (const old of await cache.keys()) if (old.url.startsWith(`${url}?sha256=`) && old.url !== key) await cache.delete(old);
|
|
186
|
+
});
|
|
187
|
+
await startupOperation(url, 'write artifact cache', () => cache.put(key, kept.clone()));
|
|
188
|
+
} catch (error) {
|
|
189
|
+
note(`Blender's artifact cache could not keep ${file} (${reason(error)}); the next open fetches it again.`);
|
|
190
|
+
}
|
|
174
191
|
return kept;
|
|
175
192
|
}
|
|
176
193
|
|
|
@@ -187,7 +204,9 @@ export async function startEmscriptenBlenderEngine(
|
|
|
187
204
|
const glueUrl = artifactUrl('blender_browser.js');
|
|
188
205
|
const digests = status.digests ?? {};
|
|
189
206
|
const checkpoint = async (phase: string): Promise<void> => { await options.ask({ checkpoint: phase }); };
|
|
190
|
-
|
|
207
|
+
// Page output, not an editor-console condition: nothing in the project can resolve the browser's cache.
|
|
208
|
+
const noteCache = (text: string) => options.log('log', text);
|
|
209
|
+
const artifact = (file: string) => cachedArtifact(file, digests[file], () => checkpoint(`artifact/${file}`), noteCache);
|
|
191
210
|
// The `.data` package is handed to the glue whole (`getPreloadedPackage`),
|
|
192
211
|
// so it is read before the module starts; the wasm streams in beside it.
|
|
193
212
|
const [factory, preloaded] = await Promise.all([
|
|
@@ -354,7 +373,7 @@ export async function startEmscriptenBlenderEngine(
|
|
|
354
373
|
const files = moduleFiles(module);
|
|
355
374
|
FS.chmod('/bw/datafiles', 0o755);
|
|
356
375
|
await checkpoint('python-ready');
|
|
357
|
-
await mountEssentials(files, digests['essentials.bin'], checkpoint);
|
|
376
|
+
await mountEssentials(files, digests['essentials.bin'], checkpoint, noteCache);
|
|
358
377
|
const { request } = openSessionChannel(files, options);
|
|
359
378
|
|
|
360
379
|
return {
|
|
@@ -381,11 +400,11 @@ export async function startEmscriptenBlenderEngine(
|
|
|
381
400
|
/** Assets are data, not a second engine. Ship them separately so a data update
|
|
382
401
|
* does not relink the 86 MB Wasm binary. Both payload and per-file bounds are
|
|
383
402
|
* checked before anything enters Blender's filesystem. */
|
|
384
|
-
async function mountEssentials(files: BlenderFiles, digest: string | undefined, checkpoint: (phase: string) => Promise<void
|
|
403
|
+
async function mountEssentials(files: BlenderFiles, digest: string | undefined, checkpoint: (phase: string) => Promise<void>, noteCache: (text: string) => void): Promise<void> {
|
|
385
404
|
const indexUrl = artifactUrl('essentials.json');
|
|
386
405
|
const payloadUrl = artifactUrl('essentials.bin');
|
|
387
406
|
const [indexResponse, payloadResponse] = await Promise.all([
|
|
388
|
-
startupOperation(indexUrl, 'fetch Essentials index', () => fetch(indexUrl)), cachedArtifact('essentials.bin', digest, () => checkpoint('artifact/essentials.bin')),
|
|
407
|
+
startupOperation(indexUrl, 'fetch Essentials index', () => fetch(indexUrl)), cachedArtifact('essentials.bin', digest, () => checkpoint('artifact/essentials.bin'), noteCache),
|
|
389
408
|
]);
|
|
390
409
|
if (!indexResponse.ok || !payloadResponse.ok)
|
|
391
410
|
throw new Error(`Blender Essentials assets are missing (${indexUrl}: HTTP ${indexResponse.status}; ${payloadUrl}: HTTP ${payloadResponse.status})`);
|
|
@@ -172,7 +172,7 @@ export interface BlenderEngine {
|
|
|
172
172
|
readonly bootMs: number;
|
|
173
173
|
/** The engine's memory in bytes, now. Linear memory on the standalone skew;
|
|
174
174
|
* null where the skew cannot see the module's memory from the host, which
|
|
175
|
-
* is the WALI case and is why `
|
|
175
|
+
* is the WALI case and is why `cyclotron status` prints "unreported" rather than
|
|
176
176
|
* a zero. */
|
|
177
177
|
memoryBytes(): number | null;
|
|
178
178
|
/** Bytes of packed `.data` payload handed back after boot, or null when the
|
|
@@ -361,7 +361,7 @@ export function openSessionChannel(
|
|
|
361
361
|
*
|
|
362
362
|
* The loop below waits for `out/<id>.done` and nothing else. When the
|
|
363
363
|
* program behind the directory DIES, that file is never written, so the
|
|
364
|
-
* call waits forever: `
|
|
364
|
+
* call waits forever: `cyclotron status` reports it IN FLIGHT for as long as
|
|
365
365
|
* anyone looks, `blender-start` keeps answering from the host's cached
|
|
366
366
|
* banner, and the harness has no answer to time out against. MEASURED
|
|
367
367
|
* 2026-09-19 three times on the WALI skew -- 507 s, 621 s and 964 s of a
|
|
@@ -574,7 +574,7 @@ export async function startWaliBlenderEngine(
|
|
|
574
574
|
readArena: () => filesystem.readFile(ARENA_PATH),
|
|
575
575
|
bootMs,
|
|
576
576
|
// The module's memory is the program worker's and no door reports it.
|
|
577
|
-
// Null rather than zero: `
|
|
577
|
+
// Null rather than zero: `cyclotron status` prints "unreported", which is true,
|
|
578
578
|
// where a zero would be a measurement that was never taken.
|
|
579
579
|
memoryBytes: () => null,
|
|
580
580
|
// There is no packed payload on this skew: Blender's runtime tree is
|
package/browser/protocol.ts
CHANGED
|
@@ -181,6 +181,8 @@ export interface RenderRequest {
|
|
|
181
181
|
* `'None'` means the base transform; anything else is a look the view holds
|
|
182
182
|
* a baked table for, because the renderer refuses the rest by name. */
|
|
183
183
|
look?: string;
|
|
184
|
+
/** Display gamma, applied after the view transform; alpha is unaffected. */
|
|
185
|
+
gamma?: number;
|
|
184
186
|
/** Hand back the SCENE-REFERRED half-float frame too. An EXR is written
|
|
185
187
|
* from it, and so is the COMPOSITOR's input. */
|
|
186
188
|
linear?: boolean;
|
package/browser/runtime.ts
CHANGED
|
@@ -216,7 +216,7 @@ export class BlenderRuntime {
|
|
|
216
216
|
// The page console is the editor's ledger (`installEditorConsoleReporting`
|
|
217
217
|
// captures it, source-blind), and this package may import nothing of the
|
|
218
218
|
// editor to say it any other way. Without this line the failure reached
|
|
219
|
-
// a toast and `
|
|
219
|
+
// a toast and `cyclotron console` read 0/0 while no Model document could open
|
|
220
220
|
// (measured 2026-09-20 from a registry install).
|
|
221
221
|
console.error(error.message);
|
|
222
222
|
for (const id of [...this.#pending.keys()]) this.#settled(id);
|
package/browser/session.py
CHANGED
|
@@ -407,13 +407,13 @@ def _describe_world_socket(root_socket, camera_ray):
|
|
|
407
407
|
return float(default) if isinstance(default, (float, int)) else [float(c) for c in list(default)[:3]]
|
|
408
408
|
if kind == "ShaderNodeTexSky" and socket == "Color":
|
|
409
409
|
model = node.sky_type
|
|
410
|
-
if model
|
|
410
|
+
if model not in ("SINGLE_SCATTERING", "MULTIPLE_SCATTERING"):
|
|
411
411
|
raise NotImplementedError(
|
|
412
|
-
"World Sky Texture: sky_type %r is not implemented (MULTIPLE_SCATTERING
|
|
412
|
+
"World Sky Texture: sky_type %r is not implemented (SINGLE_SCATTERING and MULTIPLE_SCATTERING are)" % model)
|
|
413
413
|
vector = node.inputs.get("Vector")
|
|
414
414
|
if vector is not None and vector.links:
|
|
415
415
|
raise NotImplementedError("Linked World Sky Texture Vector")
|
|
416
|
-
return {"kind": "sky", "sun_elevation": float(node.sun_elevation),
|
|
416
|
+
return {"kind": "sky", "sky_model": model, "sun_elevation": float(node.sun_elevation),
|
|
417
417
|
"sun_rotation": float(node.sun_rotation), "altitude": float(node.altitude),
|
|
418
418
|
"air_density": float(node.air_density), "aerosol_density": float(node.aerosol_density),
|
|
419
419
|
"ozone_density": float(node.ozone_density)}
|
|
@@ -608,7 +608,7 @@ _GRAPH_NODES = frozenset((
|
|
|
608
608
|
"ShaderNodeGamma", "ShaderNodeRGBToBW", "ShaderNodeSeparateColor", "ShaderNodeCombineColor",
|
|
609
609
|
"ShaderNodeVectorRotate", "ShaderNodeFresnel", "ShaderNodeLayerWeight", "ShaderNodeBump",
|
|
610
610
|
"ShaderNodeNormalMap", "ShaderNodeNewGeometry", "ShaderNodeVertexColor", "ShaderNodeAttribute",
|
|
611
|
-
"ShaderNodeRGBCurve", "ShaderNodeVectorCurve", "ShaderNodeFloatCurve",
|
|
611
|
+
"ShaderNodeRGBCurve", "ShaderNodeVectorCurve", "ShaderNodeFloatCurve", "ShaderNodeObjectInfo",
|
|
612
612
|
))
|
|
613
613
|
# The surfaces whose inputs map onto the presenter's standard material, and
|
|
614
614
|
# the inputs of each the presenter reads from a graph.
|
|
@@ -959,6 +959,10 @@ class _MaterialGraph:
|
|
|
959
959
|
if node.bl_idname in _GENERATED_BY_DEFAULT and not bool(_links_into(node.inputs[0])):
|
|
960
960
|
self.generated = True
|
|
961
961
|
props = _node_properties(node)
|
|
962
|
+
if node.bl_idname == "ShaderNodeObjectInfo":
|
|
963
|
+
# node_shader_gpu_object_info: the index belongs to this material,
|
|
964
|
+
# even when the node itself is inside a shared node group.
|
|
965
|
+
props["material_index"] = float(self.material.pass_index)
|
|
962
966
|
if props.get("image"):
|
|
963
967
|
self.images.add(props["image"])
|
|
964
968
|
return {
|
|
@@ -1133,6 +1137,33 @@ def _mixed_graph(material, stack, surface):
|
|
|
1133
1137
|
}
|
|
1134
1138
|
|
|
1135
1139
|
|
|
1140
|
+
def _object_info_random(name):
|
|
1141
|
+
"""Ordinary-object Random, shared by EEVEE and Cycles: hash_string then
|
|
1142
|
+
Jenkins hash_uint2(name_hash, 0), converted to float32 * 2**-32.
|
|
1143
|
+
Instances must use depsgraph.random_id instead; their export is separate.
|
|
1144
|
+
See draw_handle.hh::random and cycles/blender/object.cpp.
|
|
1145
|
+
"""
|
|
1146
|
+
import struct
|
|
1147
|
+
|
|
1148
|
+
mask = 0xffffffff
|
|
1149
|
+
h = 0
|
|
1150
|
+
# Blender's platform flags specify -funsigned-char (native and WASM).
|
|
1151
|
+
for byte in name.encode("utf8"):
|
|
1152
|
+
h = (h * 37 + byte) & mask
|
|
1153
|
+
a = (0xdeadbeef + 21 + h) & mask
|
|
1154
|
+
b = c = (0xdeadbeef + 21) & mask
|
|
1155
|
+
def rotate(x, bits):
|
|
1156
|
+
return ((x << bits) | (x >> (32 - bits))) & mask
|
|
1157
|
+
c = ((c ^ b) - rotate(b, 14)) & mask
|
|
1158
|
+
a = ((a ^ c) - rotate(c, 11)) & mask
|
|
1159
|
+
b = ((b ^ a) - rotate(a, 25)) & mask
|
|
1160
|
+
c = ((c ^ b) - rotate(b, 16)) & mask
|
|
1161
|
+
a = ((a ^ c) - rotate(c, 4)) & mask
|
|
1162
|
+
b = ((b ^ a) - rotate(a, 14)) & mask
|
|
1163
|
+
c = ((c ^ b) - rotate(b, 24)) & mask
|
|
1164
|
+
return struct.unpack("f", struct.pack("f", c / 4294967296.0))[0]
|
|
1165
|
+
|
|
1166
|
+
|
|
1136
1167
|
def material_graphs(scene):
|
|
1137
1168
|
"""Every graph the scene's materials need, by material name."""
|
|
1138
1169
|
_TREE_LINKS.clear()
|
|
@@ -1454,6 +1485,12 @@ class Session:
|
|
|
1454
1485
|
if not any(m in graphs for m in row["materials"] if m is not None):
|
|
1455
1486
|
continue
|
|
1456
1487
|
obj = by_name.get(row["name"])
|
|
1488
|
+
if obj is not None:
|
|
1489
|
+
row["object_info"] = {
|
|
1490
|
+
"color": [float(v) for v in obj.color],
|
|
1491
|
+
"index": float(obj.pass_index),
|
|
1492
|
+
"random": _object_info_random(obj.name),
|
|
1493
|
+
}
|
|
1457
1494
|
data = obj.evaluated_get(depsgraph).data if obj is not None else None
|
|
1458
1495
|
if getattr(data, "use_auto_texspace", True) is False:
|
|
1459
1496
|
row["texspace"] = [[float(v) for v in data.texspace_location],
|
|
@@ -2013,7 +2050,7 @@ _ASK_SEQUENCE = [0]
|
|
|
2013
2050
|
def _drop(path):
|
|
2014
2051
|
"""Remove one of THIS side's own channel files. A failure here means the
|
|
2015
2052
|
ownership rule was broken by the other side, so it is named rather than
|
|
2016
|
-
swallowed -- and named where `
|
|
2053
|
+
swallowed -- and named where `cyclotron console` reads it, not into a log."""
|
|
2017
2054
|
try:
|
|
2018
2055
|
os.unlink(path)
|
|
2019
2056
|
except OSError as error:
|
|
@@ -2140,15 +2177,12 @@ def _photograph(depsgraph, width, height, linear=False):
|
|
|
2140
2177
|
"fov": _vertical_extent(camera.data, int(width), int(height)),
|
|
2141
2178
|
"toneMapping": transform,
|
|
2142
2179
|
"exposure": float(2.0 ** view.exposure),
|
|
2180
|
+
"gamma": float(view.gamma),
|
|
2143
2181
|
"orthographic": camera.data.type == "ORTHO",
|
|
2144
2182
|
"transparent": bool(scene.render.film_transparent),
|
|
2145
2183
|
}
|
|
2146
|
-
#
|
|
2147
|
-
|
|
2148
|
-
# view itself -- the same table as no look at all. Send the full name for a
|
|
2149
|
-
# non-identity look and nothing for the identity; a look with no table is
|
|
2150
|
-
# refused BY NAME on the other side (`blender-runtime-host.ts`).
|
|
2151
|
-
if look not in ("None", "AgX - Base Contrast"):
|
|
2184
|
+
# Preserve the full look name: the native OCIO display processor owns it.
|
|
2185
|
+
if look != "None":
|
|
2152
2186
|
render["look"] = look
|
|
2153
2187
|
# The capture's scene-referred frame rides along when asked for: a render
|
|
2154
2188
|
# result is what a render leaves, and it holds scene-linear pixels.
|
|
@@ -2215,6 +2249,20 @@ class VolterRenderEngine(bpy.types.RenderEngine):
|
|
|
2215
2249
|
bl_label = "three.js"
|
|
2216
2250
|
bl_use_preview = False
|
|
2217
2251
|
|
|
2252
|
+
def view_update(self, context, depsgraph):
|
|
2253
|
+
"""The tab presents the depsgraph; Blender owns the shading selection."""
|
|
2254
|
+
pass
|
|
2255
|
+
|
|
2256
|
+
def view_draw(self, context, depsgraph):
|
|
2257
|
+
"""Advertise viewport rendering to RNA without asking headless Blender for a GPU.
|
|
2258
|
+
|
|
2259
|
+
`rna_SpaceView3DShading_type_itemf` only offers RENDERED for a Python
|
|
2260
|
+
engine with `view_draw`. The Three presenter draws that viewport; this
|
|
2261
|
+
callback is never its drawing path. Without it, opening a Cycles file
|
|
2262
|
+
makes the ordinary Rendered control reject its own enum value.
|
|
2263
|
+
"""
|
|
2264
|
+
pass
|
|
2265
|
+
|
|
2218
2266
|
def render(self, depsgraph):
|
|
2219
2267
|
scene = depsgraph.scene
|
|
2220
2268
|
scale = scene.render.resolution_percentage / 100.0
|
|
@@ -2238,7 +2286,7 @@ def _fill_result(engine, result, answer, png, width, height):
|
|
|
2238
2286
|
view transform over them wherever it shows or saves one (`write_still`, the
|
|
2239
2287
|
Image Editor), as it does over Cycles'. The photograph's PNG is the display
|
|
2240
2288
|
image -- the transform already ran -- so loading it put every render
|
|
2241
|
-
through AgX or Filmic twice. Measured 2026-09-28 by
|
|
2289
|
+
through AgX or Filmic twice. Measured 2026-09-28 by Cyclotron's
|
|
2242
2290
|
render view: a background 53 against the viewport's 59, a shadow's blue 2
|
|
2243
2291
|
against 7. The same capture's linear frame goes in instead: half floats,
|
|
2244
2292
|
RGBA, bottom row first, which is Blender's own row order. Linear 0.005,
|
|
@@ -4155,7 +4203,7 @@ def _node_row(node):
|
|
|
4155
4203
|
|
|
4156
4204
|
|
|
4157
4205
|
def _node_tree_of(path, material):
|
|
4158
|
-
"""WHICH TREE. Either an explicit RNA address (so a `
|
|
4206
|
+
"""WHICH TREE. Either an explicit RNA address (so a `cyclotron eval` can open a
|
|
4159
4207
|
world's or a group's tree with the engine's own spelling), or a material by
|
|
4160
4208
|
name, or -- given neither -- the active object's active material, which is
|
|
4161
4209
|
what Blender's own Shading header resolves (`space_node.py:89-93`,
|
|
@@ -4413,7 +4461,7 @@ def rna_rig(object_name=None):
|
|
|
4413
4461
|
presented frame carries every object at once, and the presenter has to know
|
|
4414
4462
|
which of its meshes are `THREE.SkinnedMesh`es before it builds them. Asking
|
|
4415
4463
|
per object would be one round trip per mesh; asking by NAME stays available
|
|
4416
|
-
for a `
|
|
4464
|
+
for a `cyclotron eval` that wants to read one.
|
|
4417
4465
|
"""
|
|
4418
4466
|
scene_frame = int(bpy.context.scene.frame_current)
|
|
4419
4467
|
if object_name:
|
|
@@ -5150,7 +5198,7 @@ def _dispatch(request):
|
|
|
5150
5198
|
# to the console and nothing else), so it logged at `log` level and
|
|
5151
5199
|
# reached no counter -- measured 2026-09-19 (I4), when a throw in
|
|
5152
5200
|
# the overlay walk froze the viewport through a dozen successful
|
|
5153
|
-
# `blender-execute` calls with `
|
|
5201
|
+
# `blender-execute` calls with `cyclotron console` silent throughout.
|
|
5154
5202
|
_say("@@VOLTER-ERROR the present after this call failed, so the Model document is "
|
|
5155
5203
|
"showing the state before it: " + repr(thrown))
|
|
5156
5204
|
return answer
|
|
@@ -5200,7 +5248,7 @@ def _dispatch(request):
|
|
|
5200
5248
|
# to the console and nothing else), so it logged at `log` level and
|
|
5201
5249
|
# reached no counter -- measured 2026-09-19 (I4), when a throw in
|
|
5202
5250
|
# the overlay walk froze the viewport through a dozen successful
|
|
5203
|
-
# `blender-execute` calls with `
|
|
5251
|
+
# `blender-execute` calls with `cyclotron console` silent throughout.
|
|
5204
5252
|
_say("@@VOLTER-ERROR the present after this call failed, so the Model document is "
|
|
5205
5253
|
"showing the state before it: " + repr(thrown))
|
|
5206
5254
|
return answer
|
|
@@ -5223,7 +5271,7 @@ def _dispatch(request):
|
|
|
5223
5271
|
# to the console and nothing else), so it logged at `log` level and
|
|
5224
5272
|
# reached no counter -- measured 2026-09-19 (I4), when a throw in
|
|
5225
5273
|
# the overlay walk froze the viewport through a dozen successful
|
|
5226
|
-
# `blender-execute` calls with `
|
|
5274
|
+
# `blender-execute` calls with `cyclotron console` silent throughout.
|
|
5227
5275
|
_say("@@VOLTER-ERROR the present after this call failed, so the Model document is "
|
|
5228
5276
|
"showing the state before it: " + repr(thrown))
|
|
5229
5277
|
return answer
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
Blender display processors
|
|
2
|
+
==========================
|
|
3
|
+
|
|
4
|
+
blender-display-shaders.json and blender-display-shaders.lut are generated
|
|
5
|
+
using OpenColorIO 2.5.0 from Blender's color-management configuration.
|
|
6
|
+
The pinned configuration is publicly available at:
|
|
7
|
+
https://github.com/volter-ai/blender/blob/071e080a21c09e07f2dac8593dca2f9a566e591e/release/datafiles/colormanagement/config.ocio
|
|
8
|
+
|
|
9
|
+
Configuration SHA-256:
|
|
10
|
+
47a7d83e79c1d21f49ba6c505efe311da723471688c614b5c366e1da7eb8ea3a
|
|
11
|
+
|
|
12
|
+
Filmic configuration: Troy James Sobotka, with the contributors named in
|
|
13
|
+
config.ocio. AgX: based on Troy Sobotka's AgX, further developed by Zijun Eary
|
|
14
|
+
Zhou, Mark Faderbauer and Sakari Kapanen. The generated operations and table
|
|
15
|
+
payload preserve the source configuration; Blender source/license information
|
|
16
|
+
is available at that repository's release/license/ directory.
|
|
17
|
+
|
|
18
|
+
OpenColorIO's license for the shader generator follows.
|
|
19
|
+
|
|
20
|
+
Copyright Contributors to the OpenColorIO Project.
|
|
21
|
+
|
|
22
|
+
Redistribution and use in source and binary forms, with or without
|
|
23
|
+
modification, are permitted provided that the following conditions are
|
|
24
|
+
met:
|
|
25
|
+
|
|
26
|
+
* Redistributions of source code must retain the above copyright
|
|
27
|
+
notice, this list of conditions and the following disclaimer.
|
|
28
|
+
* Redistributions in binary form must reproduce the above copyright
|
|
29
|
+
notice, this list of conditions and the following disclaimer in the
|
|
30
|
+
documentation and/or other materials provided with the distribution.
|
|
31
|
+
* Neither the name of the copyright holder nor the names of its
|
|
32
|
+
contributors may be used to endorse or promote products derived from
|
|
33
|
+
this software without specific prior written permission.
|
|
34
|
+
|
|
35
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
36
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
37
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
38
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
39
|
+
HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
40
|
+
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
41
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
42
|
+
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
43
|
+
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
44
|
+
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
45
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|