@hraness/slopcamera 3.2.4
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/LICENSE +21 -0
- package/NOTICE.md +60 -0
- package/PRIVACY.md +46 -0
- package/README.md +311 -0
- package/SECURITY.md +21 -0
- package/apps/desktop/README.md +966 -0
- package/apps/desktop/analysis/protocol.ts +223 -0
- package/apps/desktop/application/capability-binding.ts +594 -0
- package/apps/desktop/application/context.ts +97 -0
- package/apps/desktop/application/creative-iteration.ts +1385 -0
- package/apps/desktop/application/default-registry.ts +146 -0
- package/apps/desktop/application/directing-contract.ts +218 -0
- package/apps/desktop/application/directing-plan.ts +178 -0
- package/apps/desktop/application/errors.ts +18 -0
- package/apps/desktop/application/gateway-port.ts +867 -0
- package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
- package/apps/desktop/application/html-overlay-integrity.ts +337 -0
- package/apps/desktop/application/html-overlay-renderer.ts +45 -0
- package/apps/desktop/application/index.ts +13 -0
- package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
- package/apps/desktop/application/operation-effects.ts +94 -0
- package/apps/desktop/application/operation.ts +224 -0
- package/apps/desktop/application/operations/analysis/faces.ts +244 -0
- package/apps/desktop/application/operations/analysis/music.ts +248 -0
- package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
- package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
- package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
- package/apps/desktop/application/operations/analysis/shared.ts +202 -0
- package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
- package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
- package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
- package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
- package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
- package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
- package/apps/desktop/application/operations/gateway/image.ts +41 -0
- package/apps/desktop/application/operations/gateway/index.ts +17 -0
- package/apps/desktop/application/operations/gateway/shared.ts +122 -0
- package/apps/desktop/application/operations/gateway/speech.ts +41 -0
- package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
- package/apps/desktop/application/operations/gateway/video.ts +41 -0
- package/apps/desktop/application/operations/index.ts +31 -0
- package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
- package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
- package/apps/desktop/application/operations/iteration/select.ts +155 -0
- package/apps/desktop/application/operations/iteration/shared.ts +250 -0
- package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
- package/apps/desktop/application/operations/media/binding.ts +79 -0
- package/apps/desktop/application/operations/media/capabilities.ts +112 -0
- package/apps/desktop/application/operations/media/color-grade.ts +343 -0
- package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
- package/apps/desktop/application/operations/media/index.ts +8 -0
- package/apps/desktop/application/operations/media/ingest.ts +303 -0
- package/apps/desktop/application/operations/media/overlay.ts +729 -0
- package/apps/desktop/application/operations/media/shared.ts +845 -0
- package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
- package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
- package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
- package/apps/desktop/application/operations/project/snapshot.ts +95 -0
- package/apps/desktop/application/operations/recording/index.ts +5 -0
- package/apps/desktop/application/operations/recording/pause.ts +34 -0
- package/apps/desktop/application/operations/recording/resume.ts +34 -0
- package/apps/desktop/application/operations/recording/shared.ts +199 -0
- package/apps/desktop/application/operations/recording/start.ts +169 -0
- package/apps/desktop/application/operations/recording/stop.ts +34 -0
- package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
- package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
- package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
- package/apps/desktop/application/operations/render/project-plan.ts +939 -0
- package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
- package/apps/desktop/application/operations/render/project.ts +1922 -0
- package/apps/desktop/application/operations/shared.ts +67 -0
- package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
- package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
- package/apps/desktop/application/operations/spatial-project.ts +209 -0
- package/apps/desktop/application/operations/spatial-render.ts +415 -0
- package/apps/desktop/application/operations/spatial-scene.ts +97 -0
- package/apps/desktop/application/operations/studio.ts +143 -0
- package/apps/desktop/application/output-publication-lease.ts +163 -0
- package/apps/desktop/application/project-publication-lease.ts +233 -0
- package/apps/desktop/application/project-store.ts +196 -0
- package/apps/desktop/application/receipts.ts +533 -0
- package/apps/desktop/application/registry.ts +182 -0
- package/apps/desktop/application/spatial-assets.ts +675 -0
- package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
- package/apps/desktop/application/spatial-project-authority.ts +35 -0
- package/apps/desktop/application/spatial-project-lease.ts +126 -0
- package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
- package/apps/desktop/application/spatial-project-store.ts +286 -0
- package/apps/desktop/application/spatial-render-projection.ts +201 -0
- package/apps/desktop/application/spatial-render.ts +677 -0
- package/apps/desktop/application/spatial-spz.ts +44 -0
- package/apps/desktop/application/spatial-world-import.ts +135 -0
- package/apps/desktop/application/spatial-world-provenance.ts +46 -0
- package/apps/desktop/application/studio-port.ts +58 -0
- package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
- package/apps/desktop/capture/protocol.ts +689 -0
- package/apps/desktop/cli/analyzer.ts +406 -0
- package/apps/desktop/cli/args.ts +3181 -0
- package/apps/desktop/cli/asset-ingest.ts +747 -0
- package/apps/desktop/cli/atomic-render-effects.ts +210 -0
- package/apps/desktop/cli/atomic-render-platform.ts +205 -0
- package/apps/desktop/cli/atomic-render.ts +50 -0
- package/apps/desktop/cli/audio-analysis.ts +252 -0
- package/apps/desktop/cli/bounded-map.ts +39 -0
- package/apps/desktop/cli/bundle-service.ts +256 -0
- package/apps/desktop/cli/capabilities.ts +212 -0
- package/apps/desktop/cli/capture-bundle.ts +1117 -0
- package/apps/desktop/cli/command-host-resources.ts +251 -0
- package/apps/desktop/cli/commands.ts +6836 -0
- package/apps/desktop/cli/directing-blob.ts +291 -0
- package/apps/desktop/cli/directing-media.ts +517 -0
- package/apps/desktop/cli/directing-quote.ts +89 -0
- package/apps/desktop/cli/directing-service.ts +288 -0
- package/apps/desktop/cli/emoji-assets.ts +538 -0
- package/apps/desktop/cli/errors.ts +29 -0
- package/apps/desktop/cli/face-analysis-service.ts +940 -0
- package/apps/desktop/cli/filter-script.ts +76 -0
- package/apps/desktop/cli/gateway-application-port.ts +1142 -0
- package/apps/desktop/cli/gateway-credential.ts +81 -0
- package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
- package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
- package/apps/desktop/cli/gateway-media-service.ts +3106 -0
- package/apps/desktop/cli/gateway-media-signature.ts +122 -0
- package/apps/desktop/cli/gateway-provider-options.ts +562 -0
- package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
- package/apps/desktop/cli/help.ts +501 -0
- package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
- package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
- package/apps/desktop/cli/io.ts +290 -0
- package/apps/desktop/cli/main.ts +123 -0
- package/apps/desktop/cli/media-effects-service.ts +885 -0
- package/apps/desktop/cli/media-ingest-model.ts +364 -0
- package/apps/desktop/cli/media-ingest-platform.ts +227 -0
- package/apps/desktop/cli/media-ingest-program.ts +200 -0
- package/apps/desktop/cli/media-ingest.ts +30 -0
- package/apps/desktop/cli/music-analysis-service.ts +434 -0
- package/apps/desktop/cli/mutation-lock.ts +530 -0
- package/apps/desktop/cli/native-assets.d.ts +12 -0
- package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
- package/apps/desktop/cli/overlay-playback.ts +177 -0
- package/apps/desktop/cli/paths.ts +265 -0
- package/apps/desktop/cli/portable-surface.ts +242 -0
- package/apps/desktop/cli/project-camera-receipt.ts +112 -0
- package/apps/desktop/cli/project-face-camera.ts +394 -0
- package/apps/desktop/cli/project-inactivity-service.ts +552 -0
- package/apps/desktop/cli/project-media-integrity.ts +121 -0
- package/apps/desktop/cli/project-renderer.ts +1526 -0
- package/apps/desktop/cli/project-service.ts +519 -0
- package/apps/desktop/cli/project-state-transaction.ts +520 -0
- package/apps/desktop/cli/query-limits.ts +1 -0
- package/apps/desktop/cli/recording-controller.ts +1567 -0
- package/apps/desktop/cli/recording-daemon.ts +931 -0
- package/apps/desktop/cli/recording-ref.ts +56 -0
- package/apps/desktop/cli/renamed-environment.ts +6 -0
- package/apps/desktop/cli/renderer.ts +1439 -0
- package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
- package/apps/desktop/cli/scene-analysis-service.ts +879 -0
- package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
- package/apps/desktop/cli/spatial-project-service.ts +90 -0
- package/apps/desktop/cli/spatial-scene-service.ts +105 -0
- package/apps/desktop/cli/spatial-world-service.ts +39 -0
- package/apps/desktop/cli/speech-analysis-service.ts +682 -0
- package/apps/desktop/cli/studio-assemble.ts +58 -0
- package/apps/desktop/cli/studio-command.ts +60 -0
- package/apps/desktop/cli/studio-custody.ts +60 -0
- package/apps/desktop/cli/studio-encode.ts +216 -0
- package/apps/desktop/cli/studio-exr.ts +52 -0
- package/apps/desktop/cli/studio-files.ts +179 -0
- package/apps/desktop/cli/studio-output-validation.ts +162 -0
- package/apps/desktop/cli/studio-process.ts +145 -0
- package/apps/desktop/cli/studio-runtime.ts +77 -0
- package/apps/desktop/cli/studio-scaffold.ts +70 -0
- package/apps/desktop/cli/studio-service.ts +301 -0
- package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
- package/apps/desktop/cli/studio-template-names.ts +3 -0
- package/apps/desktop/cli/studio-workflow.ts +19 -0
- package/apps/desktop/cli/time.ts +25 -0
- package/apps/desktop/cli/workflow-code.ts +584 -0
- package/apps/desktop/cli/workflow-runs.ts +576 -0
- package/apps/desktop/code/advanced.ts +139 -0
- package/apps/desktop/code/application-node-planner.ts +1288 -0
- package/apps/desktop/code/compiler.ts +329 -0
- package/apps/desktop/code/contracts.ts +13 -0
- package/apps/desktop/code/creative-recipes.ts +94 -0
- package/apps/desktop/code/define-workflow.ts +115 -0
- package/apps/desktop/code/editing.ts +284 -0
- package/apps/desktop/code/file-candidate-provenance.ts +420 -0
- package/apps/desktop/code/file-candidate.ts +69 -0
- package/apps/desktop/code/graph-builder.ts +10 -0
- package/apps/desktop/code/host-resource-policy.ts +55 -0
- package/apps/desktop/code/plan-contracts.ts +159 -0
- package/apps/desktop/code/planning.ts +361 -0
- package/apps/desktop/code/public.ts +134 -0
- package/apps/desktop/code/reference-projection.ts +62 -0
- package/apps/desktop/code/run-contracts.ts +466 -0
- package/apps/desktop/code/run-store.ts +2478 -0
- package/apps/desktop/code/runtime-identity.ts +328 -0
- package/apps/desktop/code/scheduler.ts +3067 -0
- package/apps/desktop/code/semantic-builder.ts +1436 -0
- package/apps/desktop/code/source-bundle.ts +1641 -0
- package/apps/desktop/code/source-typecheck.ts +211 -0
- package/apps/desktop/code/testing.ts +79 -0
- package/apps/desktop/code/worker-client.ts +1675 -0
- package/apps/desktop/code/worker-effects.ts +141 -0
- package/apps/desktop/code/worker-entry.ts +435 -0
- package/apps/desktop/code/worker-lease-guardian.ts +179 -0
- package/apps/desktop/code/worker-process-identity.ts +484 -0
- package/apps/desktop/code/worker-protocol.ts +211 -0
- package/apps/desktop/code/workflow-effects.ts +132 -0
- package/apps/desktop/contracts/analysis.ts +949 -0
- package/apps/desktop/contracts/edit.ts +576 -0
- package/apps/desktop/contracts/index.ts +9 -0
- package/apps/desktop/contracts/media-effects.ts +139 -0
- package/apps/desktop/contracts/project-render.ts +644 -0
- package/apps/desktop/contracts/project.ts +740 -0
- package/apps/desktop/contracts/recording.ts +1434 -0
- package/apps/desktop/contracts/render.ts +334 -0
- package/apps/desktop/contracts/runtime.ts +155 -0
- package/apps/desktop/contracts/spatial-compositor.ts +58 -0
- package/apps/desktop/contracts/spatial-project.ts +159 -0
- package/apps/desktop/contracts/spatial-world.ts +48 -0
- package/apps/desktop/contracts/video-effects.ts +120 -0
- package/apps/desktop/core/alignment-apply.ts +400 -0
- package/apps/desktop/core/audio-alignment.ts +466 -0
- package/apps/desktop/core/auto-zoom.ts +209 -0
- package/apps/desktop/core/canonical-json.ts +14 -0
- package/apps/desktop/core/emoji.ts +210 -0
- package/apps/desktop/core/events.ts +409 -0
- package/apps/desktop/core/face-framing.ts +533 -0
- package/apps/desktop/core/face-tracks.ts +326 -0
- package/apps/desktop/core/inactivity.ts +137 -0
- package/apps/desktop/core/index.ts +30 -0
- package/apps/desktop/core/intervals.ts +123 -0
- package/apps/desktop/core/montage.ts +236 -0
- package/apps/desktop/core/music-analysis.ts +518 -0
- package/apps/desktop/core/plan.ts +244 -0
- package/apps/desktop/core/project-auto-zoom.ts +75 -0
- package/apps/desktop/core/project-camera-spatial.ts +676 -0
- package/apps/desktop/core/project-camera.ts +403 -0
- package/apps/desktop/core/project-captions.ts +888 -0
- package/apps/desktop/core/project-inactivity.ts +125 -0
- package/apps/desktop/core/project-metadata.ts +927 -0
- package/apps/desktop/core/project-plan.ts +375 -0
- package/apps/desktop/core/project-render-plan.ts +303 -0
- package/apps/desktop/core/project-time.ts +469 -0
- package/apps/desktop/core/render-plan.ts +442 -0
- package/apps/desktop/core/scene-cache.ts +174 -0
- package/apps/desktop/core/scene-sampling.ts +448 -0
- package/apps/desktop/core/spatial-compositor.ts +22 -0
- package/apps/desktop/core/spatial-durability.ts +99 -0
- package/apps/desktop/core/spatial-project.ts +169 -0
- package/apps/desktop/core/spatial-shot-clock.ts +32 -0
- package/apps/desktop/core/speech-analysis.ts +333 -0
- package/apps/desktop/core/storage.ts +970 -0
- package/apps/desktop/core/time-map.ts +91 -0
- package/apps/desktop/core/time.ts +61 -0
- package/apps/desktop/core/video-effects.ts +660 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
- package/apps/desktop/dist/cli/main.js +5086 -0
- package/apps/desktop/html-overlay/catalog.ts +171 -0
- package/apps/desktop/html-overlay/contracts.ts +372 -0
- package/apps/desktop/html-overlay/execution-profile.ts +77 -0
- package/apps/desktop/html-overlay/index.ts +98 -0
- package/apps/desktop/html-overlay/libraries.ts +263 -0
- package/apps/desktop/html-overlay/random.ts +49 -0
- package/apps/desktop/html-overlay/runtime.ts +813 -0
- package/apps/desktop/html-overlay/scaffolds.ts +792 -0
- package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
- package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
- package/apps/desktop/html-overlay/spatial.ts +720 -0
- package/apps/desktop/studio/assets/README.md +39 -0
- package/apps/desktop/studio/assets/contracts.ts +129 -0
- package/apps/desktop/studio/assets/network.ts +64 -0
- package/apps/desktop/studio/assets/poly-haven.ts +148 -0
- package/apps/desktop/studio/drivers/blender_driver.py +466 -0
- package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
- package/apps/desktop/studio/education/driver.py +308 -0
- package/apps/desktop/studio/education/lesson.py +180 -0
- package/apps/desktop/studio/education/toolkit.py +249 -0
- package/apps/desktop/workflows/chaptered-demo.ts +48 -0
- package/apps/desktop/workflows/creative-iteration.ts +107 -0
- package/apps/desktop/workflows/creative-selection.ts +92 -0
- package/apps/desktop/workflows/fragments.ts +486 -0
- package/apps/desktop/workflows/index.ts +93 -0
- package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
- package/apps/desktop/workflows/scene-direction.ts +12 -0
- package/apps/desktop/workflows/social-variants.ts +75 -0
- package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
- package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
- package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
- package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
- package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
- package/dist/cli.js +479 -0
- package/dist/code/advanced.js +270 -0
- package/dist/code/index.js +2240 -0
- package/dist/generate.js +24 -0
- package/dist/host-resources.js +26 -0
- package/dist/index-42zsesc1.js +2257 -0
- package/dist/index-7308egqr.js +48 -0
- package/dist/index-8txs6fkn.js +712 -0
- package/dist/index-mcy8z0br.js +192 -0
- package/dist/index-p63wavx0.js +2029 -0
- package/dist/index-r7gdhmsp.js +424 -0
- package/dist/index-sh6xbav6.js +1219 -0
- package/dist/index-z1w83f81.js +4 -0
- package/dist/index-z7239b4h.js +2363 -0
- package/dist/index-zfeg9hmw.js +2013 -0
- package/dist/index.js +241 -0
- package/dist/operations.js +29 -0
- package/dist/skill-install-dh0ntqba.js +10 -0
- package/dist/vectorize/worker.js +142 -0
- package/dist/workflow.js +16 -0
- package/docs/README.md +38 -0
- package/docs/studio.md +215 -0
- package/examples/studio/blender/README.md +26 -0
- package/examples/studio/blender/character.py +152 -0
- package/examples/studio/blender/cloth.py +101 -0
- package/examples/studio/blender/color_chart.py +29 -0
- package/examples/studio/blender/fluid.py +78 -0
- package/examples/studio/blender/import_model.py +74 -0
- package/examples/studio/blender/product.py +53 -0
- package/examples/studio/blender/shaded_street.py +481 -0
- package/examples/studio/blender/studio_scene.py +147 -0
- package/examples/studio/cadquery/README.md +13 -0
- package/examples/studio/cadquery/bracket.py +36 -0
- package/examples/studio/cadquery/import_step.py +12 -0
- package/examples/studio/education/README.md +46 -0
- package/examples/studio/education/lesson.json +99 -0
- package/examples/studio/education/scene.py +7 -0
- package/examples/studio/hybrid-scene.ts +96 -0
- package/examples/studio/native-workflow.ts +14 -0
- package/examples/studio/vgpu/README.md +64 -0
- package/examples/studio/vgpu/heat-field.mjs +290 -0
- package/package.json +304 -0
- package/packages/scene/src/index.ts +1 -0
- package/packages/scene/src/scene.ts +215 -0
- package/schema/diagram.schema.json +334 -0
- package/skills/slopcamera/SKILL.md +45 -0
- package/skills/slopcamera/agents/openai.yaml +4 -0
- package/skills/slopcamera/references/customization.md +77 -0
- package/skills/slopcamera/references/diagrams.md +153 -0
- package/skills/slopcamera/references/directed-scenes.md +42 -0
- package/skills/slopcamera/references/directing-video.md +18 -0
- package/skills/slopcamera/references/educational-video.md +26 -0
- package/skills/slopcamera/references/gateway-media.md +134 -0
- package/skills/slopcamera/references/install.md +62 -0
- package/skills/slopcamera/references/native-studio.md +31 -0
- package/skills/slopcamera/references/reference-led-3d.md +140 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
- package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
- package/skills/slopcamera/references/vectorization.md +29 -0
- package/skills/slopcamera/references/video-projects.md +187 -0
- package/skills/slopcamera/references/visual-communication.md +120 -0
- package/skills/slopcamera/references/workflows-sdk.md +45 -0
- package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
- package/src/artifacts.ts +106 -0
- package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
- package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
- package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
- package/src/assets.d.ts +9 -0
- package/src/cli.ts +601 -0
- package/src/cloud-errors.ts +26 -0
- package/src/code/advanced.ts +10 -0
- package/src/code/boundary.ts +21 -0
- package/src/code/canonical-json.ts +129 -0
- package/src/code/compiler.ts +953 -0
- package/src/code/contracts.ts +576 -0
- package/src/code/define-workflow.ts +334 -0
- package/src/code/errors.ts +41 -0
- package/src/code/graph-builder.ts +945 -0
- package/src/code/index.ts +121 -0
- package/src/code/json-snapshot.ts +899 -0
- package/src/code/json-utf8.ts +88 -0
- package/src/code/portable-builder.ts +136 -0
- package/src/code/projection.ts +378 -0
- package/src/code/public-operations.ts +450 -0
- package/src/code/runtime.ts +580 -0
- package/src/code/sha256.ts +36 -0
- package/src/config.ts +180 -0
- package/src/desktop.ts +253 -0
- package/src/fs.ts +10 -0
- package/src/generate.ts +704 -0
- package/src/host-resource-posix.ts +161 -0
- package/src/host-resources.ts +1749 -0
- package/src/icons.ts +58 -0
- package/src/index.ts +200 -0
- package/src/label-layout.ts +130 -0
- package/src/layout.ts +413 -0
- package/src/lint.ts +127 -0
- package/src/mcp/boundary.ts +354 -0
- package/src/mcp/index.ts +33 -0
- package/src/mcp/server.ts +272 -0
- package/src/mcp/tools.ts +963 -0
- package/src/mcp/types.ts +71 -0
- package/src/operations.ts +775 -0
- package/src/parse.ts +861 -0
- package/src/process-environment.ts +17 -0
- package/src/render.ts +437 -0
- package/src/skill-install.ts +49 -0
- package/src/spatial-scene/authoring.ts +27 -0
- package/src/spatial-scene/camera-track.ts +83 -0
- package/src/spatial-scene/contracts.ts +277 -0
- package/src/spatial-scene/evaluate.ts +145 -0
- package/src/spatial-scene/gltf.ts +558 -0
- package/src/spatial-scene/identity.ts +260 -0
- package/src/spatial-scene/index.ts +10 -0
- package/src/spatial-scene/inspect.ts +68 -0
- package/src/spatial-scene/math.ts +280 -0
- package/src/spatial-scene/patch.ts +131 -0
- package/src/spatial-scene/time.ts +53 -0
- package/src/studio/contracts.ts +129 -0
- package/src/studio/index.ts +2 -0
- package/src/studio/plan.ts +156 -0
- package/src/studio/shared.ts +41 -0
- package/src/theme.ts +68 -0
- package/src/tldr.ts +474 -0
- package/src/types.ts +212 -0
- package/src/vectorize/archive.ts +101 -0
- package/src/vectorize/command.ts +797 -0
- package/src/vectorize/index.ts +27 -0
- package/src/vectorize/limits.ts +61 -0
- package/src/vectorize/metrics.ts +351 -0
- package/src/vectorize/pixels.ts +323 -0
- package/src/vectorize/supervisor.ts +339 -0
- package/src/vectorize/svg.ts +188 -0
- package/src/vectorize/tool.ts +542 -0
- package/src/vectorize/types.ts +113 -0
- package/src/vectorize/vectorize.ts +634 -0
- package/src/vectorize/worker-protocol.ts +40 -0
- package/src/vectorize/worker.ts +209 -0
- package/src/version.ts +1 -0
- package/src/workflow.ts +409 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
"""Fixed SLOPCAMERA Blender adapter. Authored Python is explicitly trusted current-user code.
|
|
2
|
+
|
|
3
|
+
Python bundles may execute at module scope or define build(context), followed by
|
|
4
|
+
bake(context) for bake jobs. The adapter never infers or silently runs a bake for
|
|
5
|
+
render jobs. Declared .blend snapshots preserve the authored scene and caches.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import hashlib
|
|
10
|
+
import json
|
|
11
|
+
import math
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import runpy
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def read_request(path):
|
|
19
|
+
with Path(path).open("rb") as stream:
|
|
20
|
+
data = stream.read(2 * 1024 * 1024 + 1)
|
|
21
|
+
if len(data) > 2 * 1024 * 1024:
|
|
22
|
+
raise ValueError("Driver request exceeds its byte bound")
|
|
23
|
+
request = json.loads(data)
|
|
24
|
+
for key in ("sourceRoot", "outputRoot", "workingRoot"):
|
|
25
|
+
root = Path(request[key])
|
|
26
|
+
if not root.is_absolute() or not root.is_dir() or root.is_symlink() or root.resolve() != root:
|
|
27
|
+
raise ValueError("Driver roots must be existing absolute physical directories")
|
|
28
|
+
roots = [Path(request[key]) for key in ("sourceRoot", "outputRoot", "workingRoot")]
|
|
29
|
+
if any(a == b or a in b.parents or b in a.parents for index, a in enumerate(roots) for b in roots[index + 1:]):
|
|
30
|
+
raise ValueError("Driver source, output and scratch roots must be disjoint")
|
|
31
|
+
if any(Path(request["outputRoot"]).iterdir()):
|
|
32
|
+
raise ValueError("Blender requires a fresh owned output directory")
|
|
33
|
+
if request["bundle"]["engine"] != "blender" or request["job"]["engine"]["engine"] != "blender":
|
|
34
|
+
raise ValueError("Blender driver requires a Blender bundle and job")
|
|
35
|
+
return request
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def child_path(root, relative):
|
|
39
|
+
if not isinstance(relative, str) or not relative or "\\" in relative:
|
|
40
|
+
raise ValueError("Expected a normalized relative artifact path")
|
|
41
|
+
parts = relative.split("/")
|
|
42
|
+
if any(part in ("", ".", "..") for part in parts) or Path(relative).is_absolute():
|
|
43
|
+
raise ValueError("Artifact path escapes its retained root")
|
|
44
|
+
path = Path(root).joinpath(*parts)
|
|
45
|
+
if not path.resolve().is_relative_to(Path(root).resolve()):
|
|
46
|
+
raise ValueError("Artifact path crosses an external symlink")
|
|
47
|
+
return path
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def verify_bundle(request):
|
|
51
|
+
for item in request["bundle"]["files"]:
|
|
52
|
+
path = child_path(request["sourceRoot"], item["path"])
|
|
53
|
+
if path.is_symlink() or not path.is_file():
|
|
54
|
+
raise ValueError("Retained source is not a regular file: " + item["path"])
|
|
55
|
+
digest = hashlib.sha256()
|
|
56
|
+
count = 0
|
|
57
|
+
with path.open("rb") as stream:
|
|
58
|
+
while data := stream.read(1024 * 1024):
|
|
59
|
+
digest.update(data)
|
|
60
|
+
count += len(data)
|
|
61
|
+
if count != item["bytes"] or digest.hexdigest() != item["sha256"]:
|
|
62
|
+
raise ValueError("Retained source bytes changed: " + item["path"])
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def context_for(request):
|
|
66
|
+
job = request["job"]
|
|
67
|
+
return json.loads(json.dumps({"parameters": job["parameters"], "stage": job["stage"],
|
|
68
|
+
"render": job.get("render"), "sourceRoot": request["sourceRoot"],
|
|
69
|
+
"outputRoot": request["outputRoot"], "workingRoot": request["workingRoot"]}))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def spatial_camera_settings(camera, render=None):
|
|
73
|
+
"""Map canonical top-origin calibrated pixels to Blender's horizontal fit.
|
|
74
|
+
|
|
75
|
+
Camera-local negative Z and local positive Y are shared. Only world space
|
|
76
|
+
rotates: canonical (x,y,z) becomes native (x,-z,y). No lens/look inference.
|
|
77
|
+
"""
|
|
78
|
+
if not isinstance(camera, dict) or set(camera) != {"cameraId", "name", "pose", "projection"}:
|
|
79
|
+
raise ValueError("Expected an explicit canonical SpatialCamera")
|
|
80
|
+
pose, projection = camera["pose"], camera["projection"]
|
|
81
|
+
if not isinstance(pose, dict) or set(pose) != {"position", "rotation"} or not isinstance(projection, dict):
|
|
82
|
+
raise ValueError("Invalid camera pose or projection")
|
|
83
|
+
|
|
84
|
+
def number(value):
|
|
85
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value):
|
|
86
|
+
raise ValueError("Camera values must be finite numbers")
|
|
87
|
+
return value
|
|
88
|
+
|
|
89
|
+
for field, length in (("position", 3), ("rotation", 4)):
|
|
90
|
+
values = pose[field]
|
|
91
|
+
if not isinstance(values, list) or len(values) != length or any(abs(number(v)) > (1000000 if field == "position" else 1) for v in values):
|
|
92
|
+
raise ValueError("Invalid camera pose vector")
|
|
93
|
+
if abs(sum(v * v for v in pose["rotation"]) - 1) > 1e-6:
|
|
94
|
+
raise ValueError("Camera requires a unit XYZW quaternion")
|
|
95
|
+
kind = projection.get("kind")
|
|
96
|
+
expected = {"kind", "width", "height", "near", "far"} | ({"fx", "fy", "cx", "cy"} if kind == "perspective" else {"left", "right", "top", "bottom"})
|
|
97
|
+
if kind not in ("perspective", "orthographic") or set(projection) != expected:
|
|
98
|
+
raise ValueError("Unsupported calibrated camera projection")
|
|
99
|
+
for key, value in projection.items():
|
|
100
|
+
if key != "kind":
|
|
101
|
+
number(value)
|
|
102
|
+
w, h = projection["width"], projection["height"]
|
|
103
|
+
if any(type(v) is not int or not 1 <= v <= 16384 for v in (w, h)) or w * h > 33554432:
|
|
104
|
+
raise ValueError("Camera image exceeds its bounds")
|
|
105
|
+
if render and (w != render["width"] or h != render["height"]):
|
|
106
|
+
raise ValueError("Calibrated camera image dimensions differ from the admitted render job")
|
|
107
|
+
if not 0.000001 <= projection["near"] < projection["far"] <= 1000000:
|
|
108
|
+
raise ValueError("Invalid camera clipping interval")
|
|
109
|
+
settings = {"type": "PERSP" if kind == "perspective" else "ORTHO", "sensor_fit": "HORIZONTAL",
|
|
110
|
+
"clip_start": projection["near"], "clip_end": projection["far"]}
|
|
111
|
+
if kind == "perspective":
|
|
112
|
+
if not 0.000001 <= projection["fx"] <= 1000000 or not 0.000001 <= projection["fy"] <= 1000000:
|
|
113
|
+
raise ValueError("Invalid camera focal lengths")
|
|
114
|
+
aspect = projection["fx"] / projection["fy"]
|
|
115
|
+
settings.update(sensor_width=36.0, lens=projection["fx"] * 36.0 / w,
|
|
116
|
+
shift_x=(w / 2 - projection["cx"]) / w,
|
|
117
|
+
shift_y=(projection["cy"] - h / 2) * aspect / w)
|
|
118
|
+
else:
|
|
119
|
+
span_x, span_y = projection["right"] - projection["left"], projection["top"] - projection["bottom"]
|
|
120
|
+
if span_x <= 0 or span_y <= 0:
|
|
121
|
+
raise ValueError("Orthographic camera extents must be positive")
|
|
122
|
+
aspect = span_y * w / (span_x * h)
|
|
123
|
+
settings.update(ortho_scale=span_x,
|
|
124
|
+
shift_x=(projection["right"] + projection["left"]) / (2 * span_x),
|
|
125
|
+
shift_y=(projection["top"] + projection["bottom"]) / (2 * span_x))
|
|
126
|
+
if not 1 / 200 <= aspect <= 200:
|
|
127
|
+
raise ValueError("Calibrated pixel aspect exceeds Blender's supported range")
|
|
128
|
+
return {"camera": settings, "render": {"resolution_x": w, "resolution_y": h, "resolution_percentage": 100,
|
|
129
|
+
"pixel_aspect_x": max(1.0, 1.0 / aspect), "pixel_aspect_y": max(1.0, aspect)}}
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def apply_spatial_camera(bpy, camera, camera_object=None, render=None):
|
|
133
|
+
"""Apply one sampled camera, rejecting native clamps instead of changing framing."""
|
|
134
|
+
from mathutils import Matrix, Quaternion, Vector
|
|
135
|
+
settings = spatial_camera_settings(camera, render)
|
|
136
|
+
scene = bpy.context.scene
|
|
137
|
+
if camera_object is None:
|
|
138
|
+
data = bpy.data.cameras.new(camera["cameraId"])
|
|
139
|
+
camera_object = bpy.data.objects.new(camera["cameraId"], data)
|
|
140
|
+
scene.collection.objects.link(camera_object)
|
|
141
|
+
if camera_object.type != "CAMERA" or camera_object.parent is not None or len(camera_object.constraints) or camera_object.animation_data or camera_object.data.animation_data:
|
|
142
|
+
raise ValueError("Camera exchange requires an unconstrained, unparented camera without animation owners")
|
|
143
|
+
for target, values in ((camera_object.data, settings["camera"]), (scene.render, settings["render"])):
|
|
144
|
+
for key, value in values.items():
|
|
145
|
+
setattr(target, key, value)
|
|
146
|
+
actual = getattr(target, key)
|
|
147
|
+
if isinstance(value, (int, float)):
|
|
148
|
+
if not math.isclose(actual, value, rel_tol=2e-6, abs_tol=1e-7):
|
|
149
|
+
raise ValueError("Blender cannot represent calibrated camera field " + key)
|
|
150
|
+
elif actual != value:
|
|
151
|
+
raise ValueError("Blender changed calibrated camera field " + key)
|
|
152
|
+
x, y, z, w = camera["pose"]["rotation"]
|
|
153
|
+
native_from_canonical = Matrix(((1, 0, 0, 0), (0, 0, -1, 0), (0, 1, 0, 0), (0, 0, 0, 1)))
|
|
154
|
+
camera_object.matrix_world = native_from_canonical @ Matrix.LocRotScale(Vector(camera["pose"]["position"]), Quaternion((w, x, y, z)), Vector((1, 1, 1)))
|
|
155
|
+
scene.camera = camera_object
|
|
156
|
+
return camera_object
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def check_output_budget(request):
|
|
160
|
+
count, size = 0, 0
|
|
161
|
+
for parent, directories, files in os.walk(request["outputRoot"], followlinks=False):
|
|
162
|
+
for name in directories:
|
|
163
|
+
if (Path(parent) / name).is_symlink():
|
|
164
|
+
raise ValueError("An output directory became a symlink")
|
|
165
|
+
for name in files:
|
|
166
|
+
path = Path(parent) / name
|
|
167
|
+
if path.is_symlink() or not path.is_file():
|
|
168
|
+
raise ValueError("An output is not a regular file")
|
|
169
|
+
count += 1
|
|
170
|
+
size += path.stat().st_size
|
|
171
|
+
limits = request["job"]["limits"]
|
|
172
|
+
if count > limits["maximumOutputFiles"] or size > limits["maximumOutputBytes"]:
|
|
173
|
+
raise ValueError("Blender exceeded the declared output budget")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def configure_scene(bpy, job):
|
|
177
|
+
scene = bpy.context.scene
|
|
178
|
+
render = job.get("render")
|
|
179
|
+
if render:
|
|
180
|
+
scene.render.resolution_x = render["width"]
|
|
181
|
+
scene.render.resolution_y = render["height"]
|
|
182
|
+
scene.render.resolution_percentage = 100
|
|
183
|
+
numerator = render["frameRate"]["numerator"]
|
|
184
|
+
denominator = render["frameRate"]["denominator"]
|
|
185
|
+
# Blender stores integer FPS plus a floating base; retain the exact
|
|
186
|
+
# rational in result metadata, and use the ratio for scene evaluation.
|
|
187
|
+
fps = min(32767, max(1, round(numerator / denominator)))
|
|
188
|
+
scene.render.fps = fps
|
|
189
|
+
scene.render.fps_base = fps * denominator / numerator
|
|
190
|
+
scene.frame_start = render["startFrame"]
|
|
191
|
+
scene.frame_end = render["endFrameExclusive"] - 1
|
|
192
|
+
scene.frame_step = 1
|
|
193
|
+
options = job["engine"]
|
|
194
|
+
scene.render.engine = "CYCLES" if options["renderer"] == "cycles" else "BLENDER_EEVEE"
|
|
195
|
+
scene.render.film_transparent = options["transparent"]
|
|
196
|
+
scene.display_settings.display_device = "sRGB"
|
|
197
|
+
scene.view_settings.view_transform = options["viewTransform"]
|
|
198
|
+
scene.render.image_settings.color_management = "FOLLOW_SCENE"
|
|
199
|
+
if options["renderer"] == "cycles":
|
|
200
|
+
scene.cycles.samples = options["samples"]
|
|
201
|
+
scene.cycles.use_denoising = options["denoise"]
|
|
202
|
+
scene.cycles.seed = options["seed"]
|
|
203
|
+
scene.cycles.use_animated_seed = False
|
|
204
|
+
elif hasattr(scene, "eevee"):
|
|
205
|
+
scene.eevee.taa_render_samples = options["samples"]
|
|
206
|
+
return scene
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def select_device(bpy, options):
|
|
210
|
+
if options["renderer"] == "eevee":
|
|
211
|
+
if options["device"] != "gpu":
|
|
212
|
+
raise ValueError("EEVEE is a GPU renderer; select device gpu explicitly")
|
|
213
|
+
import gpu
|
|
214
|
+
gpu.init()
|
|
215
|
+
return {"requested": "gpu", "backend": gpu.platform.backend_type_get(),
|
|
216
|
+
"devices": [{"name": gpu.platform.renderer_get(), "type": "GPU"}],
|
|
217
|
+
"evidence": "initialized-eevee-gpu-context"}
|
|
218
|
+
scene = bpy.context.scene
|
|
219
|
+
preferences = bpy.context.preferences.addons["cycles"].preferences
|
|
220
|
+
if options["device"] == "cpu":
|
|
221
|
+
scene.cycles.device = "CPU"
|
|
222
|
+
for device in preferences.devices:
|
|
223
|
+
device.use = device.type == "CPU"
|
|
224
|
+
return {"requested": "cpu", "backend": "CPU", "devices": [],
|
|
225
|
+
"evidence": "configured-cycles-cpu"}
|
|
226
|
+
failures = []
|
|
227
|
+
for backend in ("METAL", "OPTIX", "CUDA", "HIP", "ONEAPI"):
|
|
228
|
+
try:
|
|
229
|
+
preferences.compute_device_type = backend
|
|
230
|
+
preferences.refresh_devices()
|
|
231
|
+
devices = [device for device in preferences.devices if device.type == backend]
|
|
232
|
+
if not devices:
|
|
233
|
+
continue
|
|
234
|
+
for device in preferences.devices:
|
|
235
|
+
device.use = device.type == backend
|
|
236
|
+
scene.cycles.device = "GPU"
|
|
237
|
+
return {"requested": "gpu", "backend": backend,
|
|
238
|
+
"devices": [{"name": d.name, "id": d.id, "type": d.type} for d in devices],
|
|
239
|
+
"evidence": "cycles-device-discovery-and-selection"}
|
|
240
|
+
except (TypeError, RuntimeError, ValueError) as error:
|
|
241
|
+
failures.append(type(error).__name__)
|
|
242
|
+
raise RuntimeError("Requested Cycles GPU is unavailable; no CPU fallback was authorized")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def output_file(request, output, frame=None):
|
|
246
|
+
relative = output["pathPattern"].replace("%06d", f"{frame:06d}") if frame is not None else output["path"]
|
|
247
|
+
path = child_path(request["outputRoot"], relative)
|
|
248
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
249
|
+
return path
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def require_fresh(path):
|
|
253
|
+
if path.exists() or path.is_symlink():
|
|
254
|
+
raise ValueError("Refusing to overwrite a preexisting output: " + path.name)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def export_models(bpy, request):
|
|
258
|
+
for output in request["job"]["outputs"]:
|
|
259
|
+
if output["role"] != "model":
|
|
260
|
+
continue
|
|
261
|
+
if output["kind"] != "file":
|
|
262
|
+
raise ValueError("Model export requires a file output")
|
|
263
|
+
path = output_file(request, output)
|
|
264
|
+
require_fresh(path)
|
|
265
|
+
if output["format"] == "glb":
|
|
266
|
+
if output["interpretation"].get("sourceSpace") != {"units": "meters", "upAxis": "y", "handedness": "right"}:
|
|
267
|
+
raise ValueError("Blender GLB export requires meter, right-handed Y-up declaration")
|
|
268
|
+
bpy.ops.export_scene.gltf(filepath=str(path), export_format="GLB", check_existing=False)
|
|
269
|
+
elif output["format"] in ("usd", "usda", "usdc"):
|
|
270
|
+
if output["interpretation"].get("sourceSpace") != {"units": "meters", "upAxis": "z", "handedness": "right"} or bpy.context.scene.unit_settings.scale_length != 1:
|
|
271
|
+
raise ValueError("Automatic USD export requires meter-scale, right-handed Z-up scene and declaration")
|
|
272
|
+
bpy.ops.wm.usd_export(filepath=str(path), check_existing=False,
|
|
273
|
+
export_animation=bool(request["job"].get("render")))
|
|
274
|
+
else:
|
|
275
|
+
raise ValueError("Blender automatic model export supports GLB and USD only")
|
|
276
|
+
check_output_budget(request)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def render_outputs(bpy, request):
|
|
280
|
+
job = request["job"]
|
|
281
|
+
scene = bpy.context.scene
|
|
282
|
+
render = job["render"]
|
|
283
|
+
if scene.camera is None:
|
|
284
|
+
raise ValueError("The authored Blender scene has no active camera")
|
|
285
|
+
raster_outputs = [output for output in job["outputs"] if output["role"] == "beauty"]
|
|
286
|
+
if not raster_outputs and not any(output["role"] == "auxiliary" for output in job["outputs"]):
|
|
287
|
+
return []
|
|
288
|
+
for output in raster_outputs:
|
|
289
|
+
if output["format"] not in ("png", "exr") or output["kind"] not in ("file", "sequence"):
|
|
290
|
+
raise ValueError("Blender beauty output supports PNG/EXR files or sequences; encode video through SLOPCAMERA")
|
|
291
|
+
if output["kind"] == "file" and render["endFrameExclusive"] - render["startFrame"] != 1:
|
|
292
|
+
raise ValueError("A beauty file requires a single-frame interval; use a sequence for animation")
|
|
293
|
+
if output["interpretation"]["kind"] != "raster" or output["interpretation"]["semantic"] != "color":
|
|
294
|
+
raise ValueError("Automatic beauty outputs require color raster interpretation")
|
|
295
|
+
interpretation = output["interpretation"]
|
|
296
|
+
expected_alpha = ("straight" if output["format"] == "png" else "premultiplied") if job["engine"]["transparent"] else "opaque"
|
|
297
|
+
expected_channels = ["R", "G", "B", "A"] if job["engine"]["transparent"] else ["R", "G", "B"]
|
|
298
|
+
if interpretation["alpha"] != expected_alpha or interpretation["channels"] != expected_channels or interpretation["unit"] != "unitless":
|
|
299
|
+
raise ValueError("Beauty interpretation must match the selected encoding's channels and alpha convention")
|
|
300
|
+
captured = []
|
|
301
|
+
# Render once per frame; save each declared encoding from the same result.
|
|
302
|
+
# Compositor-authored auxiliary files remain explicit source-owned outputs.
|
|
303
|
+
for frame in range(render["startFrame"], render["endFrameExclusive"]):
|
|
304
|
+
scene.frame_set(frame)
|
|
305
|
+
bpy.ops.render.render(write_still=False)
|
|
306
|
+
image = bpy.data.images.get("Render Result")
|
|
307
|
+
if image is None:
|
|
308
|
+
raise RuntimeError("Blender did not produce Render Result")
|
|
309
|
+
for output in raster_outputs:
|
|
310
|
+
path = output_file(request, output, frame if output["kind"] == "sequence" else None)
|
|
311
|
+
require_fresh(path)
|
|
312
|
+
interpretation = output["interpretation"]
|
|
313
|
+
scene.render.image_settings.file_format = "PNG" if output["format"] == "png" else "OPEN_EXR"
|
|
314
|
+
scene.render.image_settings.color_mode = "RGBA" if job["engine"]["transparent"] else "RGB"
|
|
315
|
+
if output["format"] == "png":
|
|
316
|
+
scene.render.image_settings.color_management = "FOLLOW_SCENE"
|
|
317
|
+
if interpretation["colorSpace"] != "srgb" or interpretation["dataType"] not in ("uint8", "uint16"):
|
|
318
|
+
raise ValueError("PNG beauty requires sRGB uint8 or uint16 interpretation")
|
|
319
|
+
scene.render.image_settings.color_depth = "16" if interpretation["dataType"] == "uint16" else "8"
|
|
320
|
+
else:
|
|
321
|
+
if interpretation["colorSpace"] != "linear-rec709" or interpretation["dataType"] not in ("float16", "float32"):
|
|
322
|
+
raise ValueError("EXR beauty requires linear-rec709 float16 or float32 interpretation")
|
|
323
|
+
scene.render.image_settings.color_depth = "16" if interpretation["dataType"] == "float16" else "32"
|
|
324
|
+
scene.render.image_settings.exr_codec = "ZIP"
|
|
325
|
+
scene.render.image_settings.color_management = "OVERRIDE"
|
|
326
|
+
scene.render.image_settings.linear_colorspace_settings.name = "Linear Rec.709"
|
|
327
|
+
image.save_render(str(path), scene=scene)
|
|
328
|
+
check_output_budget(request)
|
|
329
|
+
captured.append(frame)
|
|
330
|
+
return captured
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def scene_summary(bpy):
|
|
334
|
+
scene = bpy.context.scene
|
|
335
|
+
objects = []
|
|
336
|
+
for obj in list(scene.objects)[:512]:
|
|
337
|
+
item = {"name": obj.name, "type": obj.type,
|
|
338
|
+
"location": [round(float(x), 7) for x in obj.matrix_world.translation],
|
|
339
|
+
"modifiers": [modifier.type for modifier in obj.modifiers]}
|
|
340
|
+
if obj.type == "MESH":
|
|
341
|
+
item["vertices"] = len(obj.data.vertices)
|
|
342
|
+
item["polygons"] = len(obj.data.polygons)
|
|
343
|
+
item["shapeKeys"] = [key.name for key in obj.data.shape_keys.key_blocks] if obj.data.shape_keys else []
|
|
344
|
+
elif obj.type == "ARMATURE":
|
|
345
|
+
item["bones"] = [bone.name for bone in obj.data.bones]
|
|
346
|
+
objects.append(item)
|
|
347
|
+
return {"objectCount": len(scene.objects), "objects": objects,
|
|
348
|
+
"activeCamera": scene.camera.name if scene.camera else None,
|
|
349
|
+
"frameStart": scene.frame_start, "frameEndInclusive": scene.frame_end,
|
|
350
|
+
"fps": scene.render.fps, "fpsBase": scene.render.fps_base,
|
|
351
|
+
"viewTransform": scene.view_settings.view_transform,
|
|
352
|
+
"workingSpace": bpy.data.colorspace.working_space,
|
|
353
|
+
"linearOutputSpace": "Linear Rec.709",
|
|
354
|
+
"displayDevice": scene.display_settings.display_device}
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def validate_blend_images(bpy, request):
|
|
358
|
+
# Blender can return exit 0 with missing external images and a wrong render.
|
|
359
|
+
# Native entrypoints therefore require packed image bytes or exact retained
|
|
360
|
+
# FILE dependencies. Trusted Python authors remain responsible for their
|
|
361
|
+
# wider, explicitly nonhermetic runtime dependencies.
|
|
362
|
+
root = Path(request["sourceRoot"])
|
|
363
|
+
declared = {item["path"] for item in request["bundle"]["files"]}
|
|
364
|
+
for image in bpy.data.images:
|
|
365
|
+
if image.source != "FILE" or image.packed_file:
|
|
366
|
+
continue
|
|
367
|
+
source = Path(os.path.normpath(bpy.path.abspath(image.filepath, library=image.library)))
|
|
368
|
+
if not source.is_absolute() or source.is_symlink() or not source.is_file() or source.resolve() != source:
|
|
369
|
+
raise ValueError("Native scene image requires a physical retained file or packed bytes: " + image.name)
|
|
370
|
+
if not source.is_relative_to(root) or source.relative_to(root).as_posix() not in declared:
|
|
371
|
+
raise ValueError("Native scene image is outside its declared source bundle: " + image.name)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def run(bpy, request):
|
|
375
|
+
verify_bundle(request)
|
|
376
|
+
os.chdir(request["workingRoot"])
|
|
377
|
+
sys.dont_write_bytecode = True
|
|
378
|
+
job = request["job"]
|
|
379
|
+
context = context_for(request)
|
|
380
|
+
entrypoint = request["bundle"]["entrypoint"]
|
|
381
|
+
source = child_path(request["sourceRoot"], entrypoint["path"])
|
|
382
|
+
namespace = {}
|
|
383
|
+
def apply_declared_camera(camera, camera_object=None):
|
|
384
|
+
return apply_spatial_camera(bpy, camera, camera_object, job.get("render"))
|
|
385
|
+
if entrypoint["kind"] == "blend":
|
|
386
|
+
if job["stage"] != "render":
|
|
387
|
+
raise ValueError("Native .blend entrypoints support render only")
|
|
388
|
+
bpy.ops.wm.open_mainfile(filepath=str(source), load_ui=False, use_scripts=False)
|
|
389
|
+
validate_blend_images(bpy, request)
|
|
390
|
+
else:
|
|
391
|
+
configure_scene(bpy, job)
|
|
392
|
+
sys.path.insert(0, request["sourceRoot"])
|
|
393
|
+
namespace = runpy.run_path(str(source), init_globals={"SLOPCAMERA_CONTEXT": context,
|
|
394
|
+
"SLOPCAMERA_APPLY_SPATIAL_CAMERA": apply_declared_camera}, run_name="__slopcamera_studio__")
|
|
395
|
+
if callable(namespace.get("build")):
|
|
396
|
+
namespace["build"](context)
|
|
397
|
+
scene = configure_scene(bpy, job)
|
|
398
|
+
device = select_device(bpy, job["engine"])
|
|
399
|
+
native_outputs = [o for o in job["outputs"] if o["role"] == "native-source" and o["format"] == "blend"]
|
|
400
|
+
native_paths = []
|
|
401
|
+
bpy.context.preferences.filepaths.save_version = 0
|
|
402
|
+
for output in native_outputs:
|
|
403
|
+
if output["kind"] != "file":
|
|
404
|
+
raise ValueError("Native blend output must be a file")
|
|
405
|
+
path = output_file(request, output)
|
|
406
|
+
require_fresh(path)
|
|
407
|
+
bpy.ops.wm.save_as_mainfile(filepath=str(path), check_existing=False)
|
|
408
|
+
native_paths.append(path)
|
|
409
|
+
bake_hook_ran = False
|
|
410
|
+
if job["stage"] == "bake" and callable(namespace.get("bake")):
|
|
411
|
+
namespace["bake"](context)
|
|
412
|
+
bake_hook_ran = True
|
|
413
|
+
for path in native_paths:
|
|
414
|
+
bpy.ops.wm.save_as_mainfile(filepath=str(path), check_existing=False)
|
|
415
|
+
check_output_budget(request)
|
|
416
|
+
export_models(bpy, request)
|
|
417
|
+
captured = render_outputs(bpy, request) if job["stage"] == "render" else []
|
|
418
|
+
verify_bundle(request)
|
|
419
|
+
result = {"kind": "slopcamera.studio-blender-result", "schemaVersion": 1,
|
|
420
|
+
"version": bpy.app.version_string, "buildHash": bpy.app.build_hash.decode("ascii"),
|
|
421
|
+
"stage": job["stage"], "renderer": scene.render.engine, "device": device,
|
|
422
|
+
"render": job.get("render"), "renderedFrames": captured,
|
|
423
|
+
"bakeHookRan": bake_hook_ran, "scene": scene_summary(bpy),
|
|
424
|
+
"trust": "trusted-current-user", "hermetic": False}
|
|
425
|
+
result_path = Path(request["workingRoot"]) / "result.json"
|
|
426
|
+
with result_path.open("x", encoding="utf-8") as stream:
|
|
427
|
+
json.dump(result, stream, indent=2, allow_nan=False)
|
|
428
|
+
stream.write("\n")
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def main():
|
|
432
|
+
import bpy
|
|
433
|
+
parser = argparse.ArgumentParser()
|
|
434
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
435
|
+
group.add_argument("--probe", action="store_true")
|
|
436
|
+
group.add_argument("--request")
|
|
437
|
+
arguments = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else sys.argv[1:]
|
|
438
|
+
args = parser.parse_args(arguments)
|
|
439
|
+
if args.probe:
|
|
440
|
+
# The factory scene leaves the output-space enum unset until an EXR
|
|
441
|
+
# override is selected. Verify the adapter's actual supported selection.
|
|
442
|
+
settings = bpy.context.scene.render.image_settings
|
|
443
|
+
settings.file_format = "OPEN_EXR"
|
|
444
|
+
settings.color_management = "OVERRIDE"
|
|
445
|
+
settings.linear_colorspace_settings.name = "Linear Rec.709"
|
|
446
|
+
capabilities = ["python-authoring", "blend-authoring", "build", "bake", "render",
|
|
447
|
+
"image-sequence", "model-export", "native-cache", "auxiliary-passes"]
|
|
448
|
+
packages = {"blender": bpy.app.version_string, "python": sys.version.split()[0],
|
|
449
|
+
"working-space": bpy.data.colorspace.working_space,
|
|
450
|
+
"linear-output-space": bpy.context.scene.render.image_settings.linear_colorspace_settings.name,
|
|
451
|
+
"display-device": bpy.context.scene.display_settings.display_device}
|
|
452
|
+
try:
|
|
453
|
+
selected = select_device(bpy, {"renderer": "cycles", "device": "gpu"})
|
|
454
|
+
capabilities.append("gpu-render")
|
|
455
|
+
packages["cycles-backend"] = selected["backend"]
|
|
456
|
+
packages["cycles-devices"] = ", ".join(device["name"] for device in selected["devices"])
|
|
457
|
+
except RuntimeError:
|
|
458
|
+
packages["cycles-backend"] = "unavailable"
|
|
459
|
+
print("SLOPCAMERA_STUDIO_PROBE=" + json.dumps({"name": "Blender", "version": bpy.app.version_string,
|
|
460
|
+
"packages": packages, "capabilities": capabilities}))
|
|
461
|
+
return
|
|
462
|
+
run(bpy, read_request(args.request))
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
if __name__ == "__main__":
|
|
466
|
+
main()
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Fixed SLOPCAMERA CadQuery adapter for explicitly trusted native source bundles."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import hashlib
|
|
5
|
+
import importlib.metadata
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import runpy
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def child_path(root, relative):
|
|
14
|
+
if not isinstance(relative, str) or not relative or "\\" in relative:
|
|
15
|
+
raise ValueError("Expected a normalized relative artifact path")
|
|
16
|
+
if any(part in ("", ".", "..") for part in relative.split("/")) or Path(relative).is_absolute():
|
|
17
|
+
raise ValueError("Artifact path escapes its retained root")
|
|
18
|
+
path = Path(root) / relative
|
|
19
|
+
if not path.resolve().is_relative_to(Path(root).resolve()):
|
|
20
|
+
raise ValueError("Artifact path crosses an external symlink")
|
|
21
|
+
return path
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def verify_bundle(request):
|
|
25
|
+
for item in request["bundle"]["files"]:
|
|
26
|
+
path = child_path(request["sourceRoot"], item["path"])
|
|
27
|
+
if path.is_symlink() or not path.is_file():
|
|
28
|
+
raise ValueError("Retained source is not a regular file")
|
|
29
|
+
digest = hashlib.sha256()
|
|
30
|
+
count = 0
|
|
31
|
+
with path.open("rb") as stream:
|
|
32
|
+
while data := stream.read(1024 * 1024):
|
|
33
|
+
digest.update(data)
|
|
34
|
+
count += len(data)
|
|
35
|
+
if digest.hexdigest() != item["sha256"] or count != item["bytes"]:
|
|
36
|
+
raise ValueError("Retained source bytes changed: " + item["path"])
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def shapes_for(cq, value):
|
|
40
|
+
if isinstance(value, cq.Assembly):
|
|
41
|
+
return [value.toCompound()]
|
|
42
|
+
if isinstance(value, cq.Workplane):
|
|
43
|
+
return [item for item in value.vals() if isinstance(item, cq.Shape)]
|
|
44
|
+
if isinstance(value, cq.Shape):
|
|
45
|
+
return [value]
|
|
46
|
+
raise ValueError("exportVariable must name a CadQuery Workplane, Shape, or Assembly")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def export_meter_glb(assembly, path, tolerance, angular_tolerance):
|
|
50
|
+
# CadQuery 2.8's high-level GLTF writer rotates the assembly but leaves its
|
|
51
|
+
# millimeter numbers unchanged. Set OCCT's physical units explicitly while
|
|
52
|
+
# retaining CadQuery's colored assembly conversion and mesh tolerance in mm.
|
|
53
|
+
from cadquery.occ_impl.assembly import toCAF
|
|
54
|
+
from OCP.Message import Message_ProgressRange
|
|
55
|
+
from OCP.RWGltf import RWGltf_CafWriter
|
|
56
|
+
from OCP.RWMesh import RWMesh_CoordinateSystem_Zup, RWMesh_CoordinateSystem_Yup
|
|
57
|
+
from OCP.TCollection import TCollection_AsciiString
|
|
58
|
+
from OCP.TColStd import TColStd_IndexedDataMapOfStringString
|
|
59
|
+
_, document = toCAF(assembly, True, True, tolerance, angular_tolerance)
|
|
60
|
+
writer = RWGltf_CafWriter(TCollection_AsciiString(str(path)), True)
|
|
61
|
+
converter = writer.ChangeCoordinateSystemConverter()
|
|
62
|
+
converter.SetInputLengthUnit(0.001)
|
|
63
|
+
converter.SetOutputLengthUnit(1.0)
|
|
64
|
+
# Let OCCT transform both mesh positions and assembly translations. Rotating
|
|
65
|
+
# only the root location would leave a translated assembly's origin in Z-up.
|
|
66
|
+
converter.SetInputCoordinateSystem(RWMesh_CoordinateSystem_Zup)
|
|
67
|
+
converter.SetOutputCoordinateSystem(RWMesh_CoordinateSystem_Yup)
|
|
68
|
+
if not writer.Perform(document, TColStd_IndexedDataMapOfStringString(), Message_ProgressRange()):
|
|
69
|
+
raise RuntimeError("OCCT did not complete the declared meter-scale GLB export")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def run(cq, request):
|
|
73
|
+
for key in ("sourceRoot", "outputRoot", "workingRoot"):
|
|
74
|
+
root = Path(request[key])
|
|
75
|
+
if not root.is_absolute() or not root.is_dir() or root.is_symlink() or root.resolve() != root:
|
|
76
|
+
raise ValueError("Driver roots must be existing absolute physical directories")
|
|
77
|
+
roots = [Path(request[key]) for key in ("sourceRoot", "outputRoot", "workingRoot")]
|
|
78
|
+
if any(a == b or a in b.parents or b in a.parents for index, a in enumerate(roots) for b in roots[index + 1:]):
|
|
79
|
+
raise ValueError("Source, output and scratch roots must be disjoint")
|
|
80
|
+
if any(Path(request["outputRoot"]).iterdir()):
|
|
81
|
+
raise ValueError("CadQuery requires a fresh owned output directory")
|
|
82
|
+
job = request["job"]
|
|
83
|
+
if request["bundle"]["engine"] != "cadquery" or job["engine"]["engine"] != "cadquery":
|
|
84
|
+
raise ValueError("CadQuery driver requires a CadQuery bundle and job")
|
|
85
|
+
if request["bundle"]["entrypoint"]["kind"] != "python":
|
|
86
|
+
raise ValueError("CadQuery requires a Python entrypoint")
|
|
87
|
+
verify_bundle(request)
|
|
88
|
+
context = json.loads(json.dumps({"parameters": job["parameters"], "stage": job["stage"], "render": job.get("render"),
|
|
89
|
+
"sourceRoot": request["sourceRoot"], "outputRoot": request["outputRoot"],
|
|
90
|
+
"workingRoot": request["workingRoot"]}))
|
|
91
|
+
os.chdir(request["workingRoot"])
|
|
92
|
+
sys.dont_write_bytecode = True
|
|
93
|
+
source = child_path(request["sourceRoot"], request["bundle"]["entrypoint"]["path"])
|
|
94
|
+
sys.path.insert(0, request["sourceRoot"])
|
|
95
|
+
namespace = runpy.run_path(str(source), init_globals={"SLOPCAMERA_CONTEXT": context}, run_name="__slopcamera_studio__")
|
|
96
|
+
if callable(namespace.get("build")):
|
|
97
|
+
built = namespace["build"](context)
|
|
98
|
+
if built is not None:
|
|
99
|
+
namespace[job["engine"]["exportVariable"]] = built
|
|
100
|
+
if job["stage"] == "bake" and callable(namespace.get("bake")):
|
|
101
|
+
namespace["bake"](context)
|
|
102
|
+
value = namespace.get(job["engine"]["exportVariable"])
|
|
103
|
+
shapes = shapes_for(cq, value)
|
|
104
|
+
if not shapes or any(not shape.isValid() for shape in shapes):
|
|
105
|
+
raise ValueError("CadQuery output is empty or geometrically invalid")
|
|
106
|
+
options = job["engine"]
|
|
107
|
+
exported = []
|
|
108
|
+
for output in job["outputs"]:
|
|
109
|
+
if output["role"] != "model":
|
|
110
|
+
continue
|
|
111
|
+
if output["kind"] != "file" or output["format"] not in ("step", "glb"):
|
|
112
|
+
raise ValueError("CadQuery automatic export supports STEP and GLB file outputs")
|
|
113
|
+
path = child_path(request["outputRoot"], output["path"])
|
|
114
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
115
|
+
if path.exists() or path.is_symlink():
|
|
116
|
+
raise ValueError("Refusing to overwrite a preexisting CAD output")
|
|
117
|
+
if output["format"] == "step":
|
|
118
|
+
if output["interpretation"].get("sourceSpace") != {"units": "millimeters", "upAxis": "z", "handedness": "right"}:
|
|
119
|
+
raise ValueError("Automatic CadQuery STEP export requires millimeters, right-handed Z-up declaration")
|
|
120
|
+
if isinstance(value, cq.Assembly):
|
|
121
|
+
value.export(str(path), exportType="STEP", mode="default", unit="MM")
|
|
122
|
+
else:
|
|
123
|
+
cq.exporters.export(value, str(path), exportType="STEP")
|
|
124
|
+
else:
|
|
125
|
+
if output["interpretation"].get("sourceSpace") != {"units": "meters", "upAxis": "y", "handedness": "right"}:
|
|
126
|
+
raise ValueError("Automatic CadQuery GLB export requires meters, right-handed Y-up declaration")
|
|
127
|
+
assembly = value if isinstance(value, cq.Assembly) else cq.Assembly(value, name="model")
|
|
128
|
+
export_meter_glb(assembly, path, options["tolerance"], options["angularTolerance"])
|
|
129
|
+
exported.append({"id": output["id"], "format": output["format"], "path": output["path"]})
|
|
130
|
+
observed = [path for path in Path(request["outputRoot"]).rglob("*") if path.is_file()]
|
|
131
|
+
if any(path.is_symlink() for path in observed) or len(observed) > job["limits"]["maximumOutputFiles"] or sum(path.stat().st_size for path in observed) > job["limits"]["maximumOutputBytes"]:
|
|
132
|
+
raise ValueError("CadQuery exceeded its declared output budget or produced indirect output")
|
|
133
|
+
verify_bundle(request)
|
|
134
|
+
bounds = []
|
|
135
|
+
for shape in shapes:
|
|
136
|
+
box = shape.BoundingBox()
|
|
137
|
+
bounds.append({"minimum": [box.xmin, box.ymin, box.zmin],
|
|
138
|
+
"maximum": [box.xmax, box.ymax, box.zmax],
|
|
139
|
+
"volume": shape.Volume(), "solids": len(shape.Solids()),
|
|
140
|
+
"faces": len(shape.Faces()), "valid": shape.isValid()})
|
|
141
|
+
result = {"kind": "slopcamera.studio-cadquery-result", "schemaVersion": 1,
|
|
142
|
+
"version": cq.__version__, "stage": job["stage"], "bounds": bounds,
|
|
143
|
+
"sourceSpace": {"units": "millimeters", "upAxis": "z", "handedness": "right"},
|
|
144
|
+
"tessellation": {"tolerance": options["tolerance"], "angularTolerance": options["angularTolerance"]},
|
|
145
|
+
"exports": exported, "trust": "trusted-current-user", "hermetic": False}
|
|
146
|
+
with (Path(request["workingRoot"]) / "result.json").open("x", encoding="utf-8") as stream:
|
|
147
|
+
json.dump(result, stream, indent=2, allow_nan=False)
|
|
148
|
+
stream.write("\n")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def main():
|
|
152
|
+
import cadquery as cq
|
|
153
|
+
parser = argparse.ArgumentParser()
|
|
154
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
155
|
+
group.add_argument("--probe", action="store_true")
|
|
156
|
+
group.add_argument("--request")
|
|
157
|
+
args = parser.parse_args()
|
|
158
|
+
if args.probe:
|
|
159
|
+
packages = {"cadquery": cq.__version__, "python": sys.version.split()[0]}
|
|
160
|
+
for name in ("cadquery-ocp", "numpy", "vtk"):
|
|
161
|
+
try:
|
|
162
|
+
packages[name] = importlib.metadata.version(name)
|
|
163
|
+
except importlib.metadata.PackageNotFoundError:
|
|
164
|
+
pass
|
|
165
|
+
print("SLOPCAMERA_STUDIO_PROBE=" + json.dumps({"name": "CadQuery", "version": cq.__version__,
|
|
166
|
+
"packages": packages, "capabilities": ["python-authoring", "build", "bake", "render", "model-export", "native-cache"]}))
|
|
167
|
+
return
|
|
168
|
+
with Path(args.request).open("rb") as stream:
|
|
169
|
+
data = stream.read(2 * 1024 * 1024 + 1)
|
|
170
|
+
if len(data) > 2 * 1024 * 1024:
|
|
171
|
+
raise ValueError("Driver request exceeds its byte bound")
|
|
172
|
+
run(cq, json.loads(data))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
if __name__ == "__main__":
|
|
176
|
+
main()
|