@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.
Files changed (441) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE.md +60 -0
  3. package/PRIVACY.md +46 -0
  4. package/README.md +311 -0
  5. package/SECURITY.md +21 -0
  6. package/apps/desktop/README.md +966 -0
  7. package/apps/desktop/analysis/protocol.ts +223 -0
  8. package/apps/desktop/application/capability-binding.ts +594 -0
  9. package/apps/desktop/application/context.ts +97 -0
  10. package/apps/desktop/application/creative-iteration.ts +1385 -0
  11. package/apps/desktop/application/default-registry.ts +146 -0
  12. package/apps/desktop/application/directing-contract.ts +218 -0
  13. package/apps/desktop/application/directing-plan.ts +178 -0
  14. package/apps/desktop/application/errors.ts +18 -0
  15. package/apps/desktop/application/gateway-port.ts +867 -0
  16. package/apps/desktop/application/html-overlay-browser-runtime.ts +687 -0
  17. package/apps/desktop/application/html-overlay-integrity.ts +337 -0
  18. package/apps/desktop/application/html-overlay-renderer.ts +45 -0
  19. package/apps/desktop/application/index.ts +13 -0
  20. package/apps/desktop/application/operation-completion-checkpoint.ts +333 -0
  21. package/apps/desktop/application/operation-effects.ts +94 -0
  22. package/apps/desktop/application/operation.ts +224 -0
  23. package/apps/desktop/application/operations/analysis/faces.ts +244 -0
  24. package/apps/desktop/application/operations/analysis/music.ts +248 -0
  25. package/apps/desktop/application/operations/analysis/project-auto-zooms.ts +302 -0
  26. package/apps/desktop/application/operations/analysis/project-inactivity.ts +381 -0
  27. package/apps/desktop/application/operations/analysis/scenes.ts +272 -0
  28. package/apps/desktop/application/operations/analysis/shared.ts +202 -0
  29. package/apps/desktop/application/operations/derive/edit-batch.ts +967 -0
  30. package/apps/desktop/application/operations/derive/follow-faces.ts +454 -0
  31. package/apps/desktop/application/operations/edit/bind-candidate-revision.ts +91 -0
  32. package/apps/desktop/application/operations/edit/create-candidate-revision.ts +377 -0
  33. package/apps/desktop/application/operations/edit/create-revision.ts +255 -0
  34. package/apps/desktop/application/operations/edit/freeze-revision.ts +182 -0
  35. package/apps/desktop/application/operations/gateway/image.ts +41 -0
  36. package/apps/desktop/application/operations/gateway/index.ts +17 -0
  37. package/apps/desktop/application/operations/gateway/shared.ts +122 -0
  38. package/apps/desktop/application/operations/gateway/speech.ts +41 -0
  39. package/apps/desktop/application/operations/gateway/transcription.ts +42 -0
  40. package/apps/desktop/application/operations/gateway/video.ts +41 -0
  41. package/apps/desktop/application/operations/index.ts +31 -0
  42. package/apps/desktop/application/operations/iteration/create-candidate.ts +240 -0
  43. package/apps/desktop/application/operations/iteration/create-matrix.ts +117 -0
  44. package/apps/desktop/application/operations/iteration/select.ts +155 -0
  45. package/apps/desktop/application/operations/iteration/shared.ts +250 -0
  46. package/apps/desktop/application/operations/media/audio-effects.ts +376 -0
  47. package/apps/desktop/application/operations/media/binding.ts +79 -0
  48. package/apps/desktop/application/operations/media/capabilities.ts +112 -0
  49. package/apps/desktop/application/operations/media/color-grade.ts +343 -0
  50. package/apps/desktop/application/operations/media/html-overlay.ts +802 -0
  51. package/apps/desktop/application/operations/media/index.ts +8 -0
  52. package/apps/desktop/application/operations/media/ingest.ts +303 -0
  53. package/apps/desktop/application/operations/media/overlay.ts +729 -0
  54. package/apps/desktop/application/operations/media/shared.ts +845 -0
  55. package/apps/desktop/application/operations/media/transform-platform.ts +120 -0
  56. package/apps/desktop/application/operations/project/commit-edits.ts +2054 -0
  57. package/apps/desktop/application/operations/project/promote-selection.ts +495 -0
  58. package/apps/desktop/application/operations/project/snapshot.ts +95 -0
  59. package/apps/desktop/application/operations/recording/index.ts +5 -0
  60. package/apps/desktop/application/operations/recording/pause.ts +34 -0
  61. package/apps/desktop/application/operations/recording/resume.ts +34 -0
  62. package/apps/desktop/application/operations/recording/shared.ts +199 -0
  63. package/apps/desktop/application/operations/recording/start.ts +169 -0
  64. package/apps/desktop/application/operations/recording/stop.ts +34 -0
  65. package/apps/desktop/application/operations/render/bind-candidate-output.ts +324 -0
  66. package/apps/desktop/application/operations/render/materialize-selection.ts +322 -0
  67. package/apps/desktop/application/operations/render/project-caption-plan.ts +287 -0
  68. package/apps/desktop/application/operations/render/project-plan.ts +939 -0
  69. package/apps/desktop/application/operations/render/project-spatial-receipt.ts +70 -0
  70. package/apps/desktop/application/operations/render/project.ts +1922 -0
  71. package/apps/desktop/application/operations/shared.ts +67 -0
  72. package/apps/desktop/application/operations/slopcamera-portable.ts +1188 -0
  73. package/apps/desktop/application/operations/slopcamera-visuals.ts +784 -0
  74. package/apps/desktop/application/operations/spatial-project.ts +209 -0
  75. package/apps/desktop/application/operations/spatial-render.ts +415 -0
  76. package/apps/desktop/application/operations/spatial-scene.ts +97 -0
  77. package/apps/desktop/application/operations/studio.ts +143 -0
  78. package/apps/desktop/application/output-publication-lease.ts +163 -0
  79. package/apps/desktop/application/project-publication-lease.ts +233 -0
  80. package/apps/desktop/application/project-store.ts +196 -0
  81. package/apps/desktop/application/receipts.ts +533 -0
  82. package/apps/desktop/application/registry.ts +182 -0
  83. package/apps/desktop/application/spatial-assets.ts +675 -0
  84. package/apps/desktop/application/spatial-compositor-cadence.ts +23 -0
  85. package/apps/desktop/application/spatial-project-authority.ts +35 -0
  86. package/apps/desktop/application/spatial-project-lease.ts +126 -0
  87. package/apps/desktop/application/spatial-project-render-preparation.ts +225 -0
  88. package/apps/desktop/application/spatial-project-store.ts +286 -0
  89. package/apps/desktop/application/spatial-render-projection.ts +201 -0
  90. package/apps/desktop/application/spatial-render.ts +677 -0
  91. package/apps/desktop/application/spatial-spz.ts +44 -0
  92. package/apps/desktop/application/spatial-world-import.ts +135 -0
  93. package/apps/desktop/application/spatial-world-provenance.ts +46 -0
  94. package/apps/desktop/application/studio-port.ts +58 -0
  95. package/apps/desktop/application/verified-receipt-reconciliation.ts +1574 -0
  96. package/apps/desktop/capture/protocol.ts +689 -0
  97. package/apps/desktop/cli/analyzer.ts +406 -0
  98. package/apps/desktop/cli/args.ts +3181 -0
  99. package/apps/desktop/cli/asset-ingest.ts +747 -0
  100. package/apps/desktop/cli/atomic-render-effects.ts +210 -0
  101. package/apps/desktop/cli/atomic-render-platform.ts +205 -0
  102. package/apps/desktop/cli/atomic-render.ts +50 -0
  103. package/apps/desktop/cli/audio-analysis.ts +252 -0
  104. package/apps/desktop/cli/bounded-map.ts +39 -0
  105. package/apps/desktop/cli/bundle-service.ts +256 -0
  106. package/apps/desktop/cli/capabilities.ts +212 -0
  107. package/apps/desktop/cli/capture-bundle.ts +1117 -0
  108. package/apps/desktop/cli/command-host-resources.ts +251 -0
  109. package/apps/desktop/cli/commands.ts +6836 -0
  110. package/apps/desktop/cli/directing-blob.ts +291 -0
  111. package/apps/desktop/cli/directing-media.ts +517 -0
  112. package/apps/desktop/cli/directing-quote.ts +89 -0
  113. package/apps/desktop/cli/directing-service.ts +288 -0
  114. package/apps/desktop/cli/emoji-assets.ts +538 -0
  115. package/apps/desktop/cli/errors.ts +29 -0
  116. package/apps/desktop/cli/face-analysis-service.ts +940 -0
  117. package/apps/desktop/cli/filter-script.ts +76 -0
  118. package/apps/desktop/cli/gateway-application-port.ts +1142 -0
  119. package/apps/desktop/cli/gateway-credential.ts +81 -0
  120. package/apps/desktop/cli/gateway-media-artifacts.ts +723 -0
  121. package/apps/desktop/cli/gateway-media-catalog.ts +922 -0
  122. package/apps/desktop/cli/gateway-media-service.ts +3106 -0
  123. package/apps/desktop/cli/gateway-media-signature.ts +122 -0
  124. package/apps/desktop/cli/gateway-provider-options.ts +562 -0
  125. package/apps/desktop/cli/gateway-scene-provider.ts +391 -0
  126. package/apps/desktop/cli/help.ts +501 -0
  127. package/apps/desktop/cli/html-overlay-gpu.ts +97 -0
  128. package/apps/desktop/cli/html-overlay-renderer.ts +2478 -0
  129. package/apps/desktop/cli/io.ts +290 -0
  130. package/apps/desktop/cli/main.ts +123 -0
  131. package/apps/desktop/cli/media-effects-service.ts +885 -0
  132. package/apps/desktop/cli/media-ingest-model.ts +364 -0
  133. package/apps/desktop/cli/media-ingest-platform.ts +227 -0
  134. package/apps/desktop/cli/media-ingest-program.ts +200 -0
  135. package/apps/desktop/cli/media-ingest.ts +30 -0
  136. package/apps/desktop/cli/music-analysis-service.ts +434 -0
  137. package/apps/desktop/cli/mutation-lock.ts +530 -0
  138. package/apps/desktop/cli/native-assets.d.ts +12 -0
  139. package/apps/desktop/cli/native-media-runtime.macos.ts +7 -0
  140. package/apps/desktop/cli/overlay-playback.ts +177 -0
  141. package/apps/desktop/cli/paths.ts +265 -0
  142. package/apps/desktop/cli/portable-surface.ts +242 -0
  143. package/apps/desktop/cli/project-camera-receipt.ts +112 -0
  144. package/apps/desktop/cli/project-face-camera.ts +394 -0
  145. package/apps/desktop/cli/project-inactivity-service.ts +552 -0
  146. package/apps/desktop/cli/project-media-integrity.ts +121 -0
  147. package/apps/desktop/cli/project-renderer.ts +1526 -0
  148. package/apps/desktop/cli/project-service.ts +519 -0
  149. package/apps/desktop/cli/project-state-transaction.ts +520 -0
  150. package/apps/desktop/cli/query-limits.ts +1 -0
  151. package/apps/desktop/cli/recording-controller.ts +1567 -0
  152. package/apps/desktop/cli/recording-daemon.ts +931 -0
  153. package/apps/desktop/cli/recording-ref.ts +56 -0
  154. package/apps/desktop/cli/renamed-environment.ts +6 -0
  155. package/apps/desktop/cli/renderer.ts +1439 -0
  156. package/apps/desktop/cli/run-cli-test-helper.ts +60 -0
  157. package/apps/desktop/cli/scene-analysis-service.ts +879 -0
  158. package/apps/desktop/cli/spatial-compositor-verifier.ts +213 -0
  159. package/apps/desktop/cli/spatial-project-service.ts +90 -0
  160. package/apps/desktop/cli/spatial-scene-service.ts +105 -0
  161. package/apps/desktop/cli/spatial-world-service.ts +39 -0
  162. package/apps/desktop/cli/speech-analysis-service.ts +682 -0
  163. package/apps/desktop/cli/studio-assemble.ts +58 -0
  164. package/apps/desktop/cli/studio-command.ts +60 -0
  165. package/apps/desktop/cli/studio-custody.ts +60 -0
  166. package/apps/desktop/cli/studio-encode.ts +216 -0
  167. package/apps/desktop/cli/studio-exr.ts +52 -0
  168. package/apps/desktop/cli/studio-files.ts +179 -0
  169. package/apps/desktop/cli/studio-output-validation.ts +162 -0
  170. package/apps/desktop/cli/studio-process.ts +145 -0
  171. package/apps/desktop/cli/studio-runtime.ts +77 -0
  172. package/apps/desktop/cli/studio-scaffold.ts +70 -0
  173. package/apps/desktop/cli/studio-service.ts +301 -0
  174. package/apps/desktop/cli/studio-spatial-asset.ts +248 -0
  175. package/apps/desktop/cli/studio-template-names.ts +3 -0
  176. package/apps/desktop/cli/studio-workflow.ts +19 -0
  177. package/apps/desktop/cli/time.ts +25 -0
  178. package/apps/desktop/cli/workflow-code.ts +584 -0
  179. package/apps/desktop/cli/workflow-runs.ts +576 -0
  180. package/apps/desktop/code/advanced.ts +139 -0
  181. package/apps/desktop/code/application-node-planner.ts +1288 -0
  182. package/apps/desktop/code/compiler.ts +329 -0
  183. package/apps/desktop/code/contracts.ts +13 -0
  184. package/apps/desktop/code/creative-recipes.ts +94 -0
  185. package/apps/desktop/code/define-workflow.ts +115 -0
  186. package/apps/desktop/code/editing.ts +284 -0
  187. package/apps/desktop/code/file-candidate-provenance.ts +420 -0
  188. package/apps/desktop/code/file-candidate.ts +69 -0
  189. package/apps/desktop/code/graph-builder.ts +10 -0
  190. package/apps/desktop/code/host-resource-policy.ts +55 -0
  191. package/apps/desktop/code/plan-contracts.ts +159 -0
  192. package/apps/desktop/code/planning.ts +361 -0
  193. package/apps/desktop/code/public.ts +134 -0
  194. package/apps/desktop/code/reference-projection.ts +62 -0
  195. package/apps/desktop/code/run-contracts.ts +466 -0
  196. package/apps/desktop/code/run-store.ts +2478 -0
  197. package/apps/desktop/code/runtime-identity.ts +328 -0
  198. package/apps/desktop/code/scheduler.ts +3067 -0
  199. package/apps/desktop/code/semantic-builder.ts +1436 -0
  200. package/apps/desktop/code/source-bundle.ts +1641 -0
  201. package/apps/desktop/code/source-typecheck.ts +211 -0
  202. package/apps/desktop/code/testing.ts +79 -0
  203. package/apps/desktop/code/worker-client.ts +1675 -0
  204. package/apps/desktop/code/worker-effects.ts +141 -0
  205. package/apps/desktop/code/worker-entry.ts +435 -0
  206. package/apps/desktop/code/worker-lease-guardian.ts +179 -0
  207. package/apps/desktop/code/worker-process-identity.ts +484 -0
  208. package/apps/desktop/code/worker-protocol.ts +211 -0
  209. package/apps/desktop/code/workflow-effects.ts +132 -0
  210. package/apps/desktop/contracts/analysis.ts +949 -0
  211. package/apps/desktop/contracts/edit.ts +576 -0
  212. package/apps/desktop/contracts/index.ts +9 -0
  213. package/apps/desktop/contracts/media-effects.ts +139 -0
  214. package/apps/desktop/contracts/project-render.ts +644 -0
  215. package/apps/desktop/contracts/project.ts +740 -0
  216. package/apps/desktop/contracts/recording.ts +1434 -0
  217. package/apps/desktop/contracts/render.ts +334 -0
  218. package/apps/desktop/contracts/runtime.ts +155 -0
  219. package/apps/desktop/contracts/spatial-compositor.ts +58 -0
  220. package/apps/desktop/contracts/spatial-project.ts +159 -0
  221. package/apps/desktop/contracts/spatial-world.ts +48 -0
  222. package/apps/desktop/contracts/video-effects.ts +120 -0
  223. package/apps/desktop/core/alignment-apply.ts +400 -0
  224. package/apps/desktop/core/audio-alignment.ts +466 -0
  225. package/apps/desktop/core/auto-zoom.ts +209 -0
  226. package/apps/desktop/core/canonical-json.ts +14 -0
  227. package/apps/desktop/core/emoji.ts +210 -0
  228. package/apps/desktop/core/events.ts +409 -0
  229. package/apps/desktop/core/face-framing.ts +533 -0
  230. package/apps/desktop/core/face-tracks.ts +326 -0
  231. package/apps/desktop/core/inactivity.ts +137 -0
  232. package/apps/desktop/core/index.ts +30 -0
  233. package/apps/desktop/core/intervals.ts +123 -0
  234. package/apps/desktop/core/montage.ts +236 -0
  235. package/apps/desktop/core/music-analysis.ts +518 -0
  236. package/apps/desktop/core/plan.ts +244 -0
  237. package/apps/desktop/core/project-auto-zoom.ts +75 -0
  238. package/apps/desktop/core/project-camera-spatial.ts +676 -0
  239. package/apps/desktop/core/project-camera.ts +403 -0
  240. package/apps/desktop/core/project-captions.ts +888 -0
  241. package/apps/desktop/core/project-inactivity.ts +125 -0
  242. package/apps/desktop/core/project-metadata.ts +927 -0
  243. package/apps/desktop/core/project-plan.ts +375 -0
  244. package/apps/desktop/core/project-render-plan.ts +303 -0
  245. package/apps/desktop/core/project-time.ts +469 -0
  246. package/apps/desktop/core/render-plan.ts +442 -0
  247. package/apps/desktop/core/scene-cache.ts +174 -0
  248. package/apps/desktop/core/scene-sampling.ts +448 -0
  249. package/apps/desktop/core/spatial-compositor.ts +22 -0
  250. package/apps/desktop/core/spatial-durability.ts +99 -0
  251. package/apps/desktop/core/spatial-project.ts +169 -0
  252. package/apps/desktop/core/spatial-shot-clock.ts +32 -0
  253. package/apps/desktop/core/speech-analysis.ts +333 -0
  254. package/apps/desktop/core/storage.ts +970 -0
  255. package/apps/desktop/core/time-map.ts +91 -0
  256. package/apps/desktop/core/time.ts +61 -0
  257. package/apps/desktop/core/video-effects.ts +660 -0
  258. package/apps/desktop/dist/cli/NebulaSans-Bold-26se8aek.otf +0 -0
  259. package/apps/desktop/dist/cli/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  260. package/apps/desktop/dist/cli/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  261. package/apps/desktop/dist/cli/NebulaSans-Book-8cenzchw.otf +0 -0
  262. package/apps/desktop/dist/cli/main.js +5086 -0
  263. package/apps/desktop/html-overlay/catalog.ts +171 -0
  264. package/apps/desktop/html-overlay/contracts.ts +372 -0
  265. package/apps/desktop/html-overlay/execution-profile.ts +77 -0
  266. package/apps/desktop/html-overlay/index.ts +98 -0
  267. package/apps/desktop/html-overlay/libraries.ts +263 -0
  268. package/apps/desktop/html-overlay/random.ts +49 -0
  269. package/apps/desktop/html-overlay/runtime.ts +813 -0
  270. package/apps/desktop/html-overlay/scaffolds.ts +792 -0
  271. package/apps/desktop/html-overlay/spark-runtime.ts +88 -0
  272. package/apps/desktop/html-overlay/spatial-splat-runtime.ts +60 -0
  273. package/apps/desktop/html-overlay/spatial.ts +720 -0
  274. package/apps/desktop/studio/assets/README.md +39 -0
  275. package/apps/desktop/studio/assets/contracts.ts +129 -0
  276. package/apps/desktop/studio/assets/network.ts +64 -0
  277. package/apps/desktop/studio/assets/poly-haven.ts +148 -0
  278. package/apps/desktop/studio/drivers/blender_driver.py +466 -0
  279. package/apps/desktop/studio/drivers/cadquery_driver.py +176 -0
  280. package/apps/desktop/studio/education/driver.py +308 -0
  281. package/apps/desktop/studio/education/lesson.py +180 -0
  282. package/apps/desktop/studio/education/toolkit.py +249 -0
  283. package/apps/desktop/workflows/chaptered-demo.ts +48 -0
  284. package/apps/desktop/workflows/creative-iteration.ts +107 -0
  285. package/apps/desktop/workflows/creative-selection.ts +92 -0
  286. package/apps/desktop/workflows/fragments.ts +486 -0
  287. package/apps/desktop/workflows/index.ts +93 -0
  288. package/apps/desktop/workflows/polished-screen-demo.ts +128 -0
  289. package/apps/desktop/workflows/scene-direction.ts +12 -0
  290. package/apps/desktop/workflows/social-variants.ts +75 -0
  291. package/apps/desktop/workflows/talking-head-cleanup.ts +53 -0
  292. package/dist/NebulaSans-Bold-26se8aek.otf +0 -0
  293. package/dist/NebulaSans-Bold-bcz7y08t.woff2 +0 -0
  294. package/dist/NebulaSans-Book-5ax05zvn.woff2 +0 -0
  295. package/dist/NebulaSans-Book-8cenzchw.otf +0 -0
  296. package/dist/cli.js +479 -0
  297. package/dist/code/advanced.js +270 -0
  298. package/dist/code/index.js +2240 -0
  299. package/dist/generate.js +24 -0
  300. package/dist/host-resources.js +26 -0
  301. package/dist/index-42zsesc1.js +2257 -0
  302. package/dist/index-7308egqr.js +48 -0
  303. package/dist/index-8txs6fkn.js +712 -0
  304. package/dist/index-mcy8z0br.js +192 -0
  305. package/dist/index-p63wavx0.js +2029 -0
  306. package/dist/index-r7gdhmsp.js +424 -0
  307. package/dist/index-sh6xbav6.js +1219 -0
  308. package/dist/index-z1w83f81.js +4 -0
  309. package/dist/index-z7239b4h.js +2363 -0
  310. package/dist/index-zfeg9hmw.js +2013 -0
  311. package/dist/index.js +241 -0
  312. package/dist/operations.js +29 -0
  313. package/dist/skill-install-dh0ntqba.js +10 -0
  314. package/dist/vectorize/worker.js +142 -0
  315. package/dist/workflow.js +16 -0
  316. package/docs/README.md +38 -0
  317. package/docs/studio.md +215 -0
  318. package/examples/studio/blender/README.md +26 -0
  319. package/examples/studio/blender/character.py +152 -0
  320. package/examples/studio/blender/cloth.py +101 -0
  321. package/examples/studio/blender/color_chart.py +29 -0
  322. package/examples/studio/blender/fluid.py +78 -0
  323. package/examples/studio/blender/import_model.py +74 -0
  324. package/examples/studio/blender/product.py +53 -0
  325. package/examples/studio/blender/shaded_street.py +481 -0
  326. package/examples/studio/blender/studio_scene.py +147 -0
  327. package/examples/studio/cadquery/README.md +13 -0
  328. package/examples/studio/cadquery/bracket.py +36 -0
  329. package/examples/studio/cadquery/import_step.py +12 -0
  330. package/examples/studio/education/README.md +46 -0
  331. package/examples/studio/education/lesson.json +99 -0
  332. package/examples/studio/education/scene.py +7 -0
  333. package/examples/studio/hybrid-scene.ts +96 -0
  334. package/examples/studio/native-workflow.ts +14 -0
  335. package/examples/studio/vgpu/README.md +64 -0
  336. package/examples/studio/vgpu/heat-field.mjs +290 -0
  337. package/package.json +304 -0
  338. package/packages/scene/src/index.ts +1 -0
  339. package/packages/scene/src/scene.ts +215 -0
  340. package/schema/diagram.schema.json +334 -0
  341. package/skills/slopcamera/SKILL.md +45 -0
  342. package/skills/slopcamera/agents/openai.yaml +4 -0
  343. package/skills/slopcamera/references/customization.md +77 -0
  344. package/skills/slopcamera/references/diagrams.md +153 -0
  345. package/skills/slopcamera/references/directed-scenes.md +42 -0
  346. package/skills/slopcamera/references/directing-video.md +18 -0
  347. package/skills/slopcamera/references/educational-video.md +26 -0
  348. package/skills/slopcamera/references/gateway-media.md +134 -0
  349. package/skills/slopcamera/references/install.md +62 -0
  350. package/skills/slopcamera/references/native-studio.md +31 -0
  351. package/skills/slopcamera/references/reference-led-3d.md +140 -0
  352. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-1.jpg +0 -0
  353. package/skills/slopcamera/references/rubber-stamp-examples/poster-example-2.jpg +0 -0
  354. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-1.png +0 -0
  355. package/skills/slopcamera/references/rubber-stamp-examples/stamp-style-2.png +0 -0
  356. package/skills/slopcamera/references/rubber-stamp-field-notes.md +144 -0
  357. package/skills/slopcamera/references/vectorization.md +29 -0
  358. package/skills/slopcamera/references/video-projects.md +187 -0
  359. package/skills/slopcamera/references/visual-communication.md +120 -0
  360. package/skills/slopcamera/references/workflows-sdk.md +45 -0
  361. package/skills/slopcamera/scripts/compose-rubber-stamp-field-note.ts +229 -0
  362. package/src/artifacts.ts +106 -0
  363. package/src/assets/fonts/nebula-sans/LICENSE.txt +96 -0
  364. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.otf +0 -0
  365. package/src/assets/fonts/nebula-sans/NebulaSans-Bold.woff2 +0 -0
  366. package/src/assets/fonts/nebula-sans/NebulaSans-Book.otf +0 -0
  367. package/src/assets/fonts/nebula-sans/NebulaSans-Book.woff2 +0 -0
  368. package/src/assets/fonts/nebula-sans/PROVENANCE.md +21 -0
  369. package/src/assets.d.ts +9 -0
  370. package/src/cli.ts +601 -0
  371. package/src/cloud-errors.ts +26 -0
  372. package/src/code/advanced.ts +10 -0
  373. package/src/code/boundary.ts +21 -0
  374. package/src/code/canonical-json.ts +129 -0
  375. package/src/code/compiler.ts +953 -0
  376. package/src/code/contracts.ts +576 -0
  377. package/src/code/define-workflow.ts +334 -0
  378. package/src/code/errors.ts +41 -0
  379. package/src/code/graph-builder.ts +945 -0
  380. package/src/code/index.ts +121 -0
  381. package/src/code/json-snapshot.ts +899 -0
  382. package/src/code/json-utf8.ts +88 -0
  383. package/src/code/portable-builder.ts +136 -0
  384. package/src/code/projection.ts +378 -0
  385. package/src/code/public-operations.ts +450 -0
  386. package/src/code/runtime.ts +580 -0
  387. package/src/code/sha256.ts +36 -0
  388. package/src/config.ts +180 -0
  389. package/src/desktop.ts +253 -0
  390. package/src/fs.ts +10 -0
  391. package/src/generate.ts +704 -0
  392. package/src/host-resource-posix.ts +161 -0
  393. package/src/host-resources.ts +1749 -0
  394. package/src/icons.ts +58 -0
  395. package/src/index.ts +200 -0
  396. package/src/label-layout.ts +130 -0
  397. package/src/layout.ts +413 -0
  398. package/src/lint.ts +127 -0
  399. package/src/mcp/boundary.ts +354 -0
  400. package/src/mcp/index.ts +33 -0
  401. package/src/mcp/server.ts +272 -0
  402. package/src/mcp/tools.ts +963 -0
  403. package/src/mcp/types.ts +71 -0
  404. package/src/operations.ts +775 -0
  405. package/src/parse.ts +861 -0
  406. package/src/process-environment.ts +17 -0
  407. package/src/render.ts +437 -0
  408. package/src/skill-install.ts +49 -0
  409. package/src/spatial-scene/authoring.ts +27 -0
  410. package/src/spatial-scene/camera-track.ts +83 -0
  411. package/src/spatial-scene/contracts.ts +277 -0
  412. package/src/spatial-scene/evaluate.ts +145 -0
  413. package/src/spatial-scene/gltf.ts +558 -0
  414. package/src/spatial-scene/identity.ts +260 -0
  415. package/src/spatial-scene/index.ts +10 -0
  416. package/src/spatial-scene/inspect.ts +68 -0
  417. package/src/spatial-scene/math.ts +280 -0
  418. package/src/spatial-scene/patch.ts +131 -0
  419. package/src/spatial-scene/time.ts +53 -0
  420. package/src/studio/contracts.ts +129 -0
  421. package/src/studio/index.ts +2 -0
  422. package/src/studio/plan.ts +156 -0
  423. package/src/studio/shared.ts +41 -0
  424. package/src/theme.ts +68 -0
  425. package/src/tldr.ts +474 -0
  426. package/src/types.ts +212 -0
  427. package/src/vectorize/archive.ts +101 -0
  428. package/src/vectorize/command.ts +797 -0
  429. package/src/vectorize/index.ts +27 -0
  430. package/src/vectorize/limits.ts +61 -0
  431. package/src/vectorize/metrics.ts +351 -0
  432. package/src/vectorize/pixels.ts +323 -0
  433. package/src/vectorize/supervisor.ts +339 -0
  434. package/src/vectorize/svg.ts +188 -0
  435. package/src/vectorize/tool.ts +542 -0
  436. package/src/vectorize/types.ts +113 -0
  437. package/src/vectorize/vectorize.ts +634 -0
  438. package/src/vectorize/worker-protocol.ts +40 -0
  439. package/src/vectorize/worker.ts +209 -0
  440. package/src/version.ts +1 -0
  441. 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()