feat(web): add world lightgroup membership
This commit is contained in:
BIN
web/app/public/vendor/blender/web_engine.wasm
vendored
BIN
web/app/public/vendor/blender/web_engine.wasm
vendored
Binary file not shown.
@@ -938,7 +938,7 @@ function Properties({ snapshot, selectedFaceIndices, selectedVertexIndices, grea
|
||||
{(snapshot?.libraries?.length ?? 0) > 0 ? <div className="property-section" data-testid="library-append-workbench"><h3>Library Append</h3>{snapshot?.libraries?.slice(0, 16).map((library) => <output key={library.id} data-resource-id={library.id}>{library.name} · {library.status} · {library.sourcePath}</output>)}<label>Source .blend <input aria-label="Library source blend" type="file" accept=".blend,application/octet-stream" onChange={(event) => { setLibrarySourceFile(event.target.files?.[0] ?? null); setLibraryAppendError(""); }} /></label><label>Binding JSON <input aria-label="Library append binding" type="file" accept="application/json,.json" onChange={(event) => { setLibraryRequestFile(event.target.files?.[0] ?? null); setLibraryAppendError(""); }} /></label><button type="button" disabled={!librarySourceFile || !libraryRequestFile} onClick={() => void appendLibrary()}>Append to Main</button>{libraryAppendError ? <output role="alert">{libraryAppendError}</output> : null}</div> : null}
|
||||
{activeCamera ? <div className="property-section" data-testid="camera-workbench"><h3>Camera</h3><output>{activeCamera.projection} · {activeCamera.lensMm.toFixed(1)}mm</output><button type="button" onClick={() => onCommand(animationRenderCommands.setCameraProperties(activeCamera.id, { projection: activeCamera.projection === "ORTHOGRAPHIC" ? "PERSPECTIVE" : "ORTHOGRAPHIC" }))}>切换投影</button><button type="button" onClick={() => onCommand(animationRenderCommands.setCameraProperties(activeCamera.id, { lensMm: Math.min(10000, activeCamera.lensMm + 5) }))}>增加焦距</button></div> : null}
|
||||
{activeLight ? <div className="property-section" data-testid="light-workbench"><h3>Light</h3><output>{activeLight.energy.toFixed(1)} energy</output><button type="button" onClick={() => onCommand(animationRenderCommands.setLightProperties(activeLight.id, { energy: Math.min(1e9, activeLight.energy + 10) }))}>增加能量</button><button type="button" onClick={() => onCommand(animationRenderCommands.setLightProperties(activeLight.id, { castsShadow: !(activeLight.castsShadow ?? true) }))}>切换阴影</button></div> : null}
|
||||
{activeWorld ? <div className="property-section" data-testid="world-workbench"><h3>World</h3><output>Exposure {activeWorld.exposure.toFixed(2)}</output><button type="button" onClick={() => onCommand(animationRenderCommands.setWorldProperties(activeWorld.id, { exposure: Math.max(-20, Math.min(20, activeWorld.exposure + 0.5)) }))}>增加曝光</button></div> : null}
|
||||
{activeWorld ? <div className="property-section" data-testid="world-workbench"><h3>World</h3><output>Exposure {activeWorld.exposure.toFixed(2)} · Lightgroup {activeWorld.lightgroup ?? "None"}</output><div className="property-actions"><button type="button" onClick={() => onCommand(animationRenderCommands.setWorldProperties(activeWorld.id, { exposure: Math.max(-20, Math.min(20, activeWorld.exposure + 0.5)) }))}>增加曝光</button><button type="button" onClick={() => onCommand(animationRenderCommands.setWorldLightgroup(activeWorld.id, activeWorld.lightgroup ? null : "Lightgroup_1"))}>{activeWorld.lightgroup ? "清除分组" : "绑定分组"}</button></div></div> : null}
|
||||
{activeScene ? <div className="property-section" data-testid="scene-properties-workbench"><h3>Scene Render</h3><output>{activeScene.name} · {activeScene.renderEngine ?? "UNKNOWN"}</output><output>{activeScene.colorManagement ? `${activeScene.colorManagement.viewTransform} · ${activeScene.colorManagement.look} · Exposure ${activeScene.colorManagement.exposure.toFixed(2)} · Gamma ${activeScene.colorManagement.gamma.toFixed(2)}` : "Color management unavailable"}</output><div className="property-actions"><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneProperties(activeScene.id, { exposure: Math.max(-1000, Math.min(1000, (activeScene.colorManagement?.exposure ?? 0) + 0.5)) }))}>增加曝光</button><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneProperties(activeScene.id, { gamma: Math.max(0, Math.min(1000, (activeScene.colorManagement?.gamma ?? 1) + 0.1)) }))}>增加 Gamma</button><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneProperties(activeScene.id, { viewTransform: activeScene.colorManagement?.viewTransform === "AgX" ? "Standard" : "AgX" }))}>切换 View Transform</button></div></div> : null}
|
||||
{activeScene?.colorManagement ? <div className="property-section" data-testid="scene-color-management-workbench"><h3>Color Management</h3><output>{activeScene.colorManagement.displayDevice} · {activeScene.colorManagement.temperature?.toFixed(0) ?? "-"} K · Tint {activeScene.colorManagement.tint?.toFixed(2) ?? "-"}</output><div className="property-actions"><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneColorManagement(activeScene.id, { temperature: Math.min(20000, (activeScene.colorManagement!.temperature ?? 6500) + 250) }))}>升高色温</button><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneColorManagement(activeScene.id, { tint: Math.max(-100, Math.min(100, (activeScene.colorManagement!.tint ?? 0) + 1)) }))}>调整 Tint</button></div></div> : null}
|
||||
{activeScene?.imageFormatSettings ? <div className="property-section" data-testid="scene-image-format-workbench"><h3>Output Format</h3><output>{activeScene.imageFormatSettings.fileType} · {activeScene.imageFormatSettings.colorMode}-channel · {activeScene.imageFormatSettings.depth}-bit · Quality {activeScene.imageFormatSettings.quality}</output><div className="property-actions"><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneImageFormat(activeScene.id, { fileType: activeScene.imageFormatSettings!.fileType === "PNG" ? "OPEN_EXR" : "PNG" }))}>切换格式</button><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneImageFormat(activeScene.id, { colorMode: activeScene.imageFormatSettings!.colorMode === 32 ? 24 : 32 }))}>切换通道</button><button type="button" onClick={() => onCommand(animationRenderCommands.setSceneImageFormat(activeScene.id, { quality: Math.min(100, activeScene.imageFormatSettings!.quality + 5) }))}>提高质量</button></div></div> : null}
|
||||
|
||||
BIN
web/app/src/vendor/blender/web_engine.wasm
vendored
BIN
web/app/src/vendor/blender/web_engine.wasm
vendored
Binary file not shown.
@@ -834,6 +834,12 @@ function assertFutureCapability(payload: Extract<WebEngineRequest["command"], {
|
||||
if (payload.lightgroup !== null && (payload.lightgroup.length === 0 || new TextEncoder().encode(payload.lightgroup).byteLength > 63)) throw report("RENDER_PROPERTY_INVALID", "Object lightgroup name is not bounded");
|
||||
return;
|
||||
}
|
||||
case "setWorldLightgroup": {
|
||||
const world = currentSnapshot?.worlds.find((candidate) => candidate.id === payload.dataId);
|
||||
if (!world || typeof payload.lightgroup !== "string" && payload.lightgroup !== null) throw report("RENDER_PROPERTY_INVALID", "World lightgroup target is invalid");
|
||||
if (payload.lightgroup !== null && (payload.lightgroup.length === 0 || new TextEncoder().encode(payload.lightgroup).byteLength > 63)) throw report("RENDER_PROPERTY_INVALID", "World lightgroup name is not bounded");
|
||||
return;
|
||||
}
|
||||
case "setGeometryNodeGraph": {
|
||||
const snapshot = currentSnapshot;
|
||||
const availableResourceIds = new Set([
|
||||
|
||||
@@ -104,4 +104,7 @@ export const animationRenderCommands = Object.freeze({
|
||||
setObjectLightgroup(objectId: string, lightgroup: string | null): AnimationCommand<"setObjectLightgroup"> {
|
||||
return { type: "setObjectLightgroup", objectId, lightgroup };
|
||||
},
|
||||
setWorldLightgroup(dataId: string, lightgroup: string | null): AnimationCommand<"setWorldLightgroup"> {
|
||||
return { type: "setWorldLightgroup", dataId, lightgroup };
|
||||
},
|
||||
} satisfies Record<string, (...args: never[]) => WebEngineEditCommand>);
|
||||
|
||||
@@ -268,6 +268,7 @@ export interface WorldIR {
|
||||
environmentStrength?: number;
|
||||
environmentRotation?: number;
|
||||
backgroundVisible?: boolean;
|
||||
lightgroup?: string | null;
|
||||
mist?: {
|
||||
enabled: boolean;
|
||||
type: "QUADRATIC" | "LINEAR" | "INVERSE_QUADRATIC";
|
||||
@@ -1552,6 +1553,7 @@ export function parseSceneSnapshotIR(value: unknown): SceneSnapshotIR {
|
||||
requireString(world.name, `worlds[${index}].name`);
|
||||
requireTuple(world.color, 3, `worlds[${index}].color`);
|
||||
requireNumber(world.exposure, `worlds[${index}].exposure`);
|
||||
if (world.lightgroup !== undefined && world.lightgroup !== null) requireString(world.lightgroup, `worlds[${index}].lightgroup`);
|
||||
if (world.environmentImageId !== undefined && world.environmentImageId !== null) requireString(world.environmentImageId, `worlds[${index}].environmentImageId`);
|
||||
if (world.environmentStrength !== undefined) requireNumber(world.environmentStrength, `worlds[${index}].environmentStrength`);
|
||||
if (world.environmentRotation !== undefined) requireNumber(world.environmentRotation, `worlds[${index}].environmentRotation`);
|
||||
|
||||
@@ -104,6 +104,7 @@ export const WEB_ENGINE_COMMAND_CAPABILITIES = {
|
||||
setSceneViewLayerAOVs: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setSceneViewLayerLightgroups: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setObjectLightgroup: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setWorldLightgroup: { family: "RENDER", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setMetaballElements: { family: "GEOMETRY", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
sculptStroke: { family: "SCULPT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
setSculptMeshAttributes: { family: "SCULPT", route: "MAIN_AUTHORITATIVE", implementation: "SOURCE_BACKED" },
|
||||
|
||||
@@ -168,6 +168,7 @@ export type WebEngineEditCommand =
|
||||
| { type: "setSceneViewLayerAOVs"; dataId: string; layerName: string; aovs: Array<{ name: string; type: 0 | 1 }> }
|
||||
| { type: "setSceneViewLayerLightgroups"; dataId: string; layerName: string; lightgroups: string[] }
|
||||
| { type: "setObjectLightgroup"; objectId: string; lightgroup: string | null }
|
||||
| { type: "setWorldLightgroup"; dataId: string; lightgroup: string | null }
|
||||
| { type: "setMetaballElements"; dataId: string; elements: Array<{ type: number; position: [number, number, number]; radius: number; scale: [number, number, number] }> }
|
||||
| { type: "sculptStroke"; stroke: SculptStrokeIR }
|
||||
| { type: "setSculptMeshAttributes"; attributes: SculptMeshAttributesIR }
|
||||
|
||||
@@ -4,7 +4,7 @@ export const WORKBENCH_COMMAND_GROUPS = Object.freeze({
|
||||
OBJECT: ["createPrimitive", "duplicateObject", "deleteObject", "setObjectTransform", "setParent", "createCollection", "moveObjectToCollection", "renameId", "applyObjectTransform", "setObjectOrigin"],
|
||||
MESH: ["meshEdit", "translateMeshVertices", "createUVMap", "setActiveUVMap", "unwrapUV", "setVertexColors", "setVertexWeights"],
|
||||
ANIMATION: ["setFrame", "insertObjectKeyframe", "deleteObjectKeyframe", "setFCurveInterpolation", "setConstraint", "setNLAStack"],
|
||||
RENDER: ["setCameraProperties", "setLightProperties", "setWorldProperties", "setSceneProperties", "setSceneRenderSettings", "setSceneAudioSettings", "setSceneUnitSettings", "setSceneTimelineSettings", "setScenePipelineSettings", "setSceneBindings", "setSceneViewLayerSettings", "setSceneCollectionSettings", "setSceneBorderSettings", "setSceneSimplifySettings", "setSceneColorManagement", "setSceneImageFormat", "setSceneMotionBlur", "setSceneHairSettings", "setSceneCompositorSettings", "setScenePerformanceSettings", "setSceneSamplingSettings", "setSceneMultiviewSettings", "setSceneStampSettings", "setSceneLineSettings", "setSceneViewLayerPassSettings", "setSceneViewLayerAOVs", "setSceneViewLayerLightgroups", "setObjectLightgroup"],
|
||||
RENDER: ["setCameraProperties", "setLightProperties", "setWorldProperties", "setSceneProperties", "setSceneRenderSettings", "setSceneAudioSettings", "setSceneUnitSettings", "setSceneTimelineSettings", "setScenePipelineSettings", "setSceneBindings", "setSceneViewLayerSettings", "setSceneCollectionSettings", "setSceneBorderSettings", "setSceneSimplifySettings", "setSceneColorManagement", "setSceneImageFormat", "setSceneMotionBlur", "setSceneHairSettings", "setSceneCompositorSettings", "setScenePerformanceSettings", "setSceneSamplingSettings", "setSceneMultiviewSettings", "setSceneStampSettings", "setSceneLineSettings", "setSceneViewLayerPassSettings", "setSceneViewLayerAOVs", "setSceneViewLayerLightgroups", "setObjectLightgroup", "setWorldLightgroup"],
|
||||
IO: ["importImage", "importVFont", "setMaterialImageNode"],
|
||||
} as const satisfies Record<string, readonly WebEngineCommandType[]>);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user