@salsita-ai/sdk 0.49.0-develop.cbee856178 → 0.49.0-develop.f5fc5cfc25
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/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
- package/dist/application-spec/generate-application/code/templates/index.d.ts +1 -1
- package/dist/application-spec/generate-application/code/templates/index.js +1 -1
- package/dist/application-spec/generate-application/code/templates/phoenix-feedback-skill.js +14 -2
- package/dist/application-spec/generate-application/code/templates/phoenix-skill.d.ts +13 -1
- package/dist/application-spec/generate-application/code/templates/phoenix-skill.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.d.ts +7 -3
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-control-system.js +1 -1
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-system.d.ts +24 -1
- package/dist/configurator/application-runtime/extensions/systems/camera-control-system/camera-system.js +1 -1
- package/dist/docs/3d-scene/cameras/custom.mdx +4 -1
- package/dist/docs/3d-scene/cameras/fit-entity.mdx +2 -0
- package/dist/docs/3d-scene/cameras/fit-root.mdx +2 -0
- package/dist/docs/3d-scene/cameras/fit-selection.mdx +2 -0
- package/dist/docs/3d-scene/cameras/locks.mdx +439 -0
- package/dist/docs/3d-scene/cameras/static.mdx +2 -0
- package/dist/docs/3d-scene/cameras/transitions.mdx +2 -0
- package/dist/docs/index.json +12 -1
- package/dist/docs/phoenix/phoenix-cli.mdx +6 -5
- package/dist/docs/phoenix/project-structure.mdx +1 -1
- package/dist/docs/phoenix/sdk-builder-workflow.mdx +13 -8
- package/dist/domain/projections/common/camera-orbit.d.ts +37 -9
- package/dist/domain/projections/common/camera-orbit.js +1 -1
- package/dist/domain/projections/common/index.d.ts +1 -1
- package/dist/domain/projections/common/index.js +1 -1
- package/dist/domain/system-components/fit-entity-orthographic-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-entity-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/fit-entity-perspective-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-entity-perspective-camera-component.js +1 -1
- package/dist/domain/system-components/fit-root-orthographic-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-root-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/fit-root-perspective-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-root-perspective-camera-component.js +1 -1
- package/dist/domain/system-components/fit-selection-orthographic-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-selection-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/fit-selection-perspective-camera-component.d.ts +8 -0
- package/dist/domain/system-components/fit-selection-perspective-camera-component.js +1 -1
- package/dist/domain/system-components/static-orthographic-camera-component.d.ts +8 -0
- package/dist/domain/system-components/static-orthographic-camera-component.js +1 -1
- package/dist/domain/system-components/static-perspective-camera-component.d.ts +8 -0
- package/dist/domain/system-components/static-perspective-camera-component.js +1 -1
- package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +16 -0
- package/dist/phoenix/application-spec/project-files.js +1 -1
- package/dist/phoenix/cli.js +1 -1
- package/dist/phoenix/commands/deploy.js +2 -2
- package/dist/phoenix/commands/dev.js +4 -4
- package/dist/phoenix/commands/init.d.ts +2 -1
- package/dist/phoenix/commands/init.js +1 -1
- package/dist/phoenix/commands/login.js +2 -2
- package/dist/phoenix/commands/sync.js +1 -1
- package/dist/phoenix/services/cli-credentials.d.ts +3 -0
- package/dist/phoenix/services/cli-credentials.js +1 -1
- package/dist/phoenix/utils/index.d.ts +1 -1
- package/dist/phoenix/utils/index.js +1 -1
- package/dist/phoenix/utils/package-info.d.ts +9 -0
- package/dist/phoenix/utils/package-info.js +1 -1
- package/package.json +3 -3
- package/schema/domain-spec.json +208 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import{GeneratedApplicationFile as e}from"../../core/index.js";import{
|
|
1
|
+
import{GeneratedApplicationFile as e}from"../../core/index.js";import{followsCanaryBuilds as t,generateAgentsMd as n,generateApplicationSpecEnvCi as r,generateApplicationSpecEnvExample as o,generateApplicationSpecEslintConfig as i,generateApplicationSpecFavicon as a,generateApplicationSpecGitignore as s,generateApplicationSpecNvmrc as c,generateApplicationSpecPackageJson as l,generateApplicationSpecPrettierConfig as p,generateApplicationSpecPrettierIgnore as m,generateApplicationSpecPrivateAssetsReadme as f,generateApplicationSpecPublicAssetsReadme as d,generateApplicationSpecReadme as g,generateApplicationSpecRobotsTxt as u,generateApplicationSpecTsconfig as x,generateApplicationSpecWebhookImplementation as h,generateLinguiRcFile as v,generatePhoenixCliSkill as j,generatePhoenixCreateConfiguratorSkill as k,generatePhoenixCreateConfiguratorSkillReferences as b,generatePhoenixFeedbackSkill as E,generatePhoenixReviewSkill as F,generatePhoenixSkill as M,stripSdkMarkerContent as P}from"./templates/index.js";export const agentSkillRoots=[".claude/skills",".agents/skills"];let w=[{path:"CLAUDE.md",generatedPrefix:""},{path:".cursor/rules/phoenix.mdc",generatedPrefix:`---
|
|
2
2
|
description: Phoenix SDK project rules
|
|
3
3
|
globs:
|
|
4
4
|
alwaysApply: true
|
|
5
5
|
---
|
|
6
|
-
`}],
|
|
6
|
+
`}],A=[".claude/commands/review.md",".cursor/commands/review.md"],S=(e,t)=>e.projectFiles.find(e=>e.file.name===t)?.file.content;export const generateProjectScaffolding=C=>{let D,{applicationSpec:L,categorizedFiles:R,sdkVersion:$,cliUseWorkspace:y,name:K,description:G}=C,I=S(R,"package.json"),N=S(R,".gitignore"),T=void 0!==y?y:L.appMetadata.workspace,U=(t=>{let n=[],r=[...A];for(let{path:o,generatedPrefix:i}of w){let a=S(t,o);if(void 0===a)continue;let s=P(a,i);void 0===s?r.push(o):s!==a&&n.push(e(o,s,{format:!1}))}return{strippedFiles:n,removedFiles:r}})(R);return{generatedProjectFiles:[e("package.json",l({sdkVersion:$,useWorkspace:T,useClerk:L.appMetadata.useClerk??!1,name:K,description:G,existingContent:I})),e(".nvmrc",c(),{format:!1}),e(".linguirc",v(L.configurator.ui.availableLocales),{format:!1}),e("tsconfig.json",x()),e("eslint.config.js",i()),e(".gitignore",s(N)),...(D=[{name:"phoenix",content:M({checkLatestCanary:t({useWorkspace:T,sdkVersion:$})}),references:[]},{name:"phoenix-cli",content:j(),references:[]},{name:"phoenix-create-configurator",content:k(),references:b()},{name:"phoenix-feedback",content:E(),references:[]},{name:"phoenix-review",content:F(),references:[]}],agentSkillRoots.flatMap(t=>D.flatMap(n=>[e(`${t}/${n.name}/SKILL.md`,n.content),...n.references.map(r=>e(`${t}/${n.name}/${r.path}`,r.content))]))),...U.strippedFiles],initialProjectFiles:[e("README.md",g(K)),e("AGENTS.md",n()),e(".env.ci",r()),e(".env.example",o()),e("public/favicon.ico",a(),{binary:!0}),e("public/robots.txt",u(),{format:!1}),e("assets/public/README.md",d()),e("assets/private/README.md",f()),e("src/webhooks/index.ts",h())],prettierFiles:[e("prettier.config.js",p()),e(".prettierignore",m(),{format:!1})],removedProjectFiles:U.removedFiles}};
|
|
@@ -46,7 +46,7 @@ export { generatePhoenixCliSkill } from './phoenix-cli-skill.js';
|
|
|
46
46
|
export { generatePhoenixCreateConfiguratorSkill, generatePhoenixCreateConfiguratorSkillReferences, } from './phoenix-create-configurator-skill.js';
|
|
47
47
|
export { generatePhoenixFeedbackSkill } from './phoenix-feedback-skill.js';
|
|
48
48
|
export { generatePhoenixReviewSkill } from './phoenix-review-skill.js';
|
|
49
|
-
export { generatePhoenixSkill } from './phoenix-skill.js';
|
|
49
|
+
export { type PhoenixSkillOptions, followsCanaryBuilds, generatePhoenixSkill } from './phoenix-skill.js';
|
|
50
50
|
export { generateApplicationSpecPrettierConfig } from './prettier-config.js';
|
|
51
51
|
export { generateApplicationSpecPrettierIgnore } from './prettier-ignore.js';
|
|
52
52
|
export { generateApplicationSpecPrivateAssetsReadme } from './private-assets-readme.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./phoenix-review-skill.js";export{generatePhoenixSkill}from"./phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
|
|
1
|
+
export{stripSdkMarkerContent}from"./common/strip-sdk-marker-content.js";export{generateAgentsMd}from"./agents-md.js";export{generateApplicationSpecAdminConfig}from"./admin-config.js";export{generateApplicationSpecAdminCode}from"./admin-domain-code.js";export{generateApplicationSpecAdminFrontendCode}from"./admin-frontend-code.js";export{generateAdminGlobalBindingBlock,generateApplicationSpecAdminGlobals}from"./admin-globals.js";export{generateApplicationSpecTypesModule}from"./application-types.js";export{generateApplicationSpecAdminFile}from"./admin.js";export{generateApplicationSpecConfiguratorConfig}from"./configurator-config.js";export{generateApplicationSpecConfiguratorCode}from"./configurator-domain-code.js";export{generateApplicationSpecConfiguratorFrontendCode}from"./configurator-frontend-code.js";export{generateApplicationSpecConfiguratorGlobals,generateConfiguratorGlobalBindingBlock}from"./configurator-globals.js";export{generateApplicationSpecConfiguratorFile}from"./configurator.js";export{generateApplicationSpecDomainSpecFile}from"./domain-spec.js";export{generateImportedStateConstants,generatePhxExportsBlock,generatePhxGlobalsBlock,generateStateConstants}from"./domain-spec/index.js";export{generateApplicationSpecDomainFile}from"./domain.js";export{generateApplicationSpecEditorFile}from"./editor.js";export{generateApplicationSpecEmbeddedAdmin}from"./embedded-admin.js";export{generateApplicationSpecEmbeddedConfigurator}from"./embedded-configurator.js";export{generateApplicationSpecEnvCi}from"./env-ci.js";export{generateApplicationSpecEnvExample}from"./env-example.js";export{generateApplicationSpecEslintConfig}from"./eslint-config.js";export{generateApplicationSpecFavicon}from"./favicon.js";export{generateApplicationSpecFrontendConfig}from"./frontend-config.js";export{generateApplicationSpecGitignore}from"./gitignore.js";export{generateApplicationSpecGlobals}from"./globals.js";export{generateApplicationSpecHtmlAdminIndex}from"./html-admin-index.js";export{generateApplicationSpecHtmlIndex}from"./html-index.js";export{generateLinguiRcFile}from"./lingui-rc.js";export{generateApplicationSpecNvmrc}from"./nvmrc.js";export{generateApplicationSpecPackageJson}from"./package-json.js";export{generatePhoenixCliSkill}from"./phoenix-cli-skill.js";export{generatePhoenixCreateConfiguratorSkill,generatePhoenixCreateConfiguratorSkillReferences}from"./phoenix-create-configurator-skill.js";export{generatePhoenixFeedbackSkill}from"./phoenix-feedback-skill.js";export{generatePhoenixReviewSkill}from"./phoenix-review-skill.js";export{followsCanaryBuilds,generatePhoenixSkill}from"./phoenix-skill.js";export{generateApplicationSpecPrettierConfig}from"./prettier-config.js";export{generateApplicationSpecPrettierIgnore}from"./prettier-ignore.js";export{generateApplicationSpecPrivateAssetsReadme}from"./private-assets-readme.js";export{generateApplicationSpecPublicAssetsReadme}from"./public-assets-readme.js";export{generateApplicationSpecReadme}from"./readme.js";export{generateApplicationSpecRobotsTxt}from"./robots-txt.js";export{generateApplicationSpecStoryDevDeclaration,generateApplicationSpecStoryPhxModule,generateApplicationSpecStoryRawProjectFiles,generateApplicationSpecStoryWrapper}from"./story/index.js";export{generateApplicationSpecTsconfig}from"./tsconfig.js";export{generateApplicationSpecWebhookBuilders}from"./webhook-builders.js";export{generateApplicationSpecWebhookImplementation}from"./webhook-implementation.js";export{generateApplicationSpecWebhookIndex}from"./webhook-index.js";export{generateApplicationSpecWebhooksConfig}from"./webhooks-config.js";
|
|
@@ -12,8 +12,20 @@ When to ask is your judgement:
|
|
|
12
12
|
- **At the end of the task** — everything else. Collect the details while you work and show all drafts together in your final message.
|
|
13
13
|
- **When the user asks** you to send feedback — draft it and show it before sending.
|
|
14
14
|
|
|
15
|
-
The draft is exactly what would be sent: the type, the message, the project if you name one with \`--projectSlug\`, and the contact if the user gave one. Send it unchanged after a yes; if the user edits it, send their version. If they decline, drop it and do not raise that problem again.`,
|
|
15
|
+
The draft is exactly what would be sent: the type, the message, the project if you name one with \`--projectSlug\`, and the contact if the user gave one. Send it unchanged after a yes; if the user edits it, send their version. If they decline, drop it and do not raise that problem again.`,a=`## Ask for a Rating After a Deploy
|
|
16
|
+
When \`phoenix deploy\` reports that the configurator is live, tell the user the live URL first, then ask them to rate building with Phoenix so far:
|
|
17
|
+
- Ask for a score from 0 to 10, where 10 is the best, and one optional sentence on what would have made it better. Say in the same question that the answer is sent to the Phoenix team as feedback.
|
|
18
|
+
- Ask once per conversation, after the first deploy that goes live. Do not ask again after later deploys, and do not ask after a deploy that failed.
|
|
19
|
+
- Never choose or guess the score yourself. If the user skips the question or declines, send nothing and do not ask again.
|
|
20
|
+
|
|
21
|
+
A rating is feedback like any other, so the rules of this skill apply. Write the comment from the user's words, and take out anything the last section forbids: replace this project's product, customer and people names with generic words. Then show the user the three lines below exactly as they would be sent and send them only after a yes; if they edit the text, send their version. Use \`none\` when they gave no comment:
|
|
22
|
+
\`\`\`bash
|
|
23
|
+
pnpm phoenix feedback 'Rating: <score>/10
|
|
24
|
+
Comment: <their comment, or none>
|
|
25
|
+
Stage: after a deploy' --type other --agent '<your agent name>'
|
|
26
|
+
\`\`\`
|
|
27
|
+
Keep the three labels and their order, so the ratings can be read out of the feedback list. A rating is not a bug report: when the comment names a concrete problem, offer to draft it as its own feedback as the sections above describe.`,n=`## What Never Goes Into Feedback
|
|
16
28
|
The message leaves this project and is read by the Phoenix team. Never include:
|
|
17
29
|
- secrets: API tokens, keys, passwords or \`.env\` values;
|
|
18
30
|
- customer or end-user data, or personal data such as names and email addresses, other than a contact the user asked you to add;
|
|
19
|
-
- this project's source code, specs or business rules. Describe the shape of the problem in words, or reduce it to a generic example that contains nothing of the project.`;export const generatePhoenixFeedbackSkill=()=>e({name:"phoenix-feedback",description:"Drafting feedback about the Phoenix SDK, CLI, documentation or Studio for the Phoenix team and sending it with the phoenix feedback command once the user approves the draft, covering when to raise it, how to write it and what must never be included. Use when the SDK or CLI misbehaves, a doc or skill misled you, a missing feature forced a workaround,
|
|
31
|
+
- this project's source code, specs or business rules. Describe the shape of the problem in words, or reduce it to a generic example that contains nothing of the project.`;export const generatePhoenixFeedbackSkill=()=>e({name:"phoenix-feedback",description:"Drafting feedback about the Phoenix SDK, CLI, documentation or Studio for the Phoenix team and sending it with the phoenix feedback command once the user approves the draft, covering when to raise it, how to write it, the rating asked for after a deploy and what must never be included. Use when the SDK or CLI misbehaves, a doc or skill misled you, a missing feature forced a workaround, the user asks to send feedback, or phoenix deploy has just gone live and the user should be asked to rate the experience from 0 to 10.",sections:[t,o,"## How to Send It\n```bash\npnpm phoenix feedback '<message>' --type bug --agent '<your agent name>'\n```\n- Put the message in single quotes, so the shell passes backticks, `$` and double quotes through unchanged. In bash or zsh, write a single quote inside the message as `'\\''`; in PowerShell, double it instead.\n- `--type` is `bug`, `idea` or `other` (default `other`). Use `idea` for a missing feature or an improvement.\n- `--agent` names you, the coding agent, so the team can tell agent feedback from a person's.\n- `--contact '<email>'` only when the user wants a reply and gave you the contact to use.\n- The workspace, user, SDK version and the spec's `project` are attached automatically. `--projectSlug <slug>` names another project of the workspace.\n- A message holds at most 5000 characters.\n\nWrite the message for someone who cannot see this project: what you ran or called, what you expected, what happened instead with the error text, and the workaround if you found one. Before quoting an error, replace this project's names, file paths and values in it with generic placeholders, as the list below requires.\n\nThe command needs `phoenix login`. If it answers that you are not signed in, tell the user once and continue; do not sign in yourself, because the sign-in is approved in a browser.\n\nAfter sending, confirm it to the user in one line.",a,n]});
|
|
@@ -1 +1,13 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface PhoenixSkillOptions {
|
|
2
|
+
readonly checkLatestCanary: boolean;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Whether a project follows the canary builds: it installs the SDK from npm, and the build it was generated by is
|
|
6
|
+
* itself a canary. A workspace project follows the local SDK, a stable release stays on releases, and a PR beta
|
|
7
|
+
* (`<version>-pr-<n>.<sha>`) is the build under test, so none of them may be moved onto canary.
|
|
8
|
+
*/
|
|
9
|
+
export declare const followsCanaryBuilds: (project: {
|
|
10
|
+
readonly useWorkspace: boolean;
|
|
11
|
+
readonly sdkVersion: string;
|
|
12
|
+
}) => boolean;
|
|
13
|
+
export declare const generatePhoenixSkill: ({ checkLatestCanary }: PhoenixSkillOptions) => string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{renderAgentSkill as e}from"./common/agent-skill.js";import{sdkGuidanceSections as
|
|
1
|
+
import{renderAgentSkill as e}from"./common/agent-skill.js";import{sdkGuidanceSections as t}from"./common/sdk-content.js";export const followsCanaryBuilds=e=>!e.useWorkspace&&e.sdkVersion.includes("-develop.");export const generatePhoenixSkill=({checkLatestCanary:n})=>e({name:"phoenix",description:`Phoenix SDK project conventions, covering project structure, architecture principles, key patterns, file and export rules, webhook membership, showing the user the dev preview and the build of a deploy, and how to search the SDK documentation${n?", and the internal-beta check for the latest canary SDK build":""}. Use before writing or changing specs or source code in this Phoenix configurator project.`,sections:[...n?["## Stay on the Latest Canary\nDuring the internal beta this project uses the `canary` build of the SDK, which is published from every change to the platform. Once per conversation, before you change anything, compare the installed version with the newest canary:\n```bash\npnpm list @salsita-ai/sdk --depth 0\nnpm view @salsita-ai/sdk@canary version\n```\nWhen they differ, tell the user which version you are moving to, then update the project and regenerate its files:\n```bash\npnpm add @salsita-ai/sdk@canary\npnpm phoenix sync\n```\n`sync` rewrites the Phoenix skills, so read the ones you use again after it. When the user runs a globally installed `phoenix`, update that too with `npm install -g @salsita-ai/sdk@canary`. If the project no longer builds after the update, stop and tell the user; the `phoenix-feedback` skill covers reporting it."]:[],t.projectStructure,t.architecturePrinciples,t.keyPatterns,t.fileConventions,t.exportRules,t.webhookMembership,t.doNotEdit,"## Show the Preview While You Work\nThe user should see the configurator change as you build it. Before your first change in a conversation, make sure `pnpm phoenix dev` is running (start it in the background when it is not) and open the address it prints as `Preview: <url>` (also written to `.phoenix/phoenix-dev.log`) in the user's browser: `open '<url>'` on macOS, `xdg-open '<url>'` on Linux, `Start-Process '<url>'` in PowerShell, `start \"\" \"<url>\"` in cmd. Keep the quotes, the shell would otherwise read a `?` in the address as a pattern. The log keeps the lines of earlier runs, so when you have just started dev, wait for the `Preview:` line written after your start and use the last one. Open it once; the preview reloads on every change, so tell the user to keep that tab open instead of opening it again.","## Show the Build After a Deploy\n`pnpm phoenix deploy` starts a build on the server and waits for it to go live. Right after `Build <id> started` it prints `Follow the build in Studio: <url>`. Run the deploy so that you can read its output while it runs, and open that link in the user's browser, quoted as above, as soon as it appears, so they can follow the build they just triggered. When the line is missing, the sign-in is older than the link: tell the user the build is listed under Deployments in Studio, and that `pnpm phoenix login <workspace>` brings the link back.","## Code Review\nUse the `phoenix-review` skill to review code changes before committing.",t.sdkDocumentation]});
|
|
@@ -34,8 +34,12 @@ type ScheduledCameraTransition<T extends CameraControlType, C extends Perspectiv
|
|
|
34
34
|
abortSignal: AbortSignal;
|
|
35
35
|
firstLoad: boolean;
|
|
36
36
|
typeChanged: boolean;
|
|
37
|
+
bypassViewLocks: boolean;
|
|
37
38
|
}) => Promise<void>;
|
|
38
39
|
};
|
|
40
|
+
interface CameraTransitionOptions {
|
|
41
|
+
readonly bypassViewLocks?: boolean;
|
|
42
|
+
}
|
|
39
43
|
type SchedulingResult = {
|
|
40
44
|
finished: Promise<void>;
|
|
41
45
|
abort: () => void;
|
|
@@ -88,9 +92,9 @@ export declare class CameraControlSystem {
|
|
|
88
92
|
readonly resetCamera: () => void;
|
|
89
93
|
readonly setTransitionState: (state: 'starting' | 'active' | 'rest' | 'sleep') => void;
|
|
90
94
|
readonly reportRender: () => void;
|
|
91
|
-
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Perspective, PerspectiveCamera
|
|
92
|
-
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Orthographic, OrthographicCamera
|
|
93
|
-
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Perspective | typeof CameraControlType.Orthographic, PerspectiveCamera | OrthographicCamera
|
|
95
|
+
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Perspective, PerspectiveCamera>, options?: CameraTransitionOptions): SchedulingResult;
|
|
96
|
+
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Orthographic, OrthographicCamera>, options?: CameraTransitionOptions): SchedulingResult;
|
|
97
|
+
scheduleCameraTransition(transition: ScheduledCameraTransition<typeof CameraControlType.Perspective | typeof CameraControlType.Orthographic, PerspectiveCamera | OrthographicCamera>, options?: CameraTransitionOptions): SchedulingResult;
|
|
94
98
|
readonly setCameraControlState: (active: 'starting' | 'moving' | 'idle') => void;
|
|
95
99
|
/**
|
|
96
100
|
* Adapt both cameras to a new canvas size. With `manual: true` R3F no longer
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import t from"promise-queue";import{BehaviorSubject as e,Subject as r,debounceTime as a,map as i,of as o,shareReplay as s,startWith as n,switchMap as h}from"rxjs";import{OrthographicCamera as l,PerspectiveCamera as p}from"three";import{match as
|
|
1
|
+
import t from"promise-queue";import{BehaviorSubject as e,Subject as r,debounceTime as a,map as i,of as o,shareReplay as s,startWith as n,switchMap as h}from"rxjs";import{OrthographicCamera as l,PerspectiveCamera as p}from"three";import{match as c}from"ts-pattern";import{DEFAULT_DIMENSION_UNIT as C,distanceUnitToDefault as m}from"#package/domain";import{createCameraControls as g}from"./create-camera-controls.js";export const CameraControlType={Perspective:"perspective",Orthographic:"orthographic"};let d=t=>Number.isFinite(t.width)&&Number.isFinite(t.height)&&t.width>0&&t.height>0;export class CameraControlSystem{_context;perspectiveCamera=Object.assign(new p,{manual:!0});orthographicCamera=Object.assign(new l,{manual:!0});perspectiveCameraControls=g(this.perspectiveCamera);orthographicCameraControls=g(this.orthographicCamera);_transitionState$=new e("still");_rendered$=new r;_transitionQueue=new t(1,1/0);_activeCameraControls$=new e(void 0);activeCameraControls$=this._activeCameraControls$;_canvasPadding$=new e({top:0,right:0,bottom:0,left:0});canvasPadding$=this._canvasPadding$;_generation=null;_pendingAbortController=void 0;_lastTransition=void 0;_cameraControlState$=new e("idle");cameraControlState$=this._cameraControlState$;transitionState$=this._transitionState$.pipe(h(t=>"sleep"===t?this._rendered$.pipe(a(200),i(()=>"still"),n("sleep")):o(t)),s(1));constructor(t){this._context=t,this.perspectiveCamera.fov=50,this.perspectiveCamera.near=m(500,C),this.perspectiveCamera.far=m(4e4,C),this.perspectiveCamera.position.set(0,1,0),this.orthographicCamera.left=m(-500,C),this.orthographicCamera.bottom=m(-500,C),this.orthographicCamera.right=m(500,C),this.orthographicCamera.top=m(500,C),this.orthographicCamera.far=m(4e4,C),this.orthographicCamera.near=m(500,C),this.orthographicCamera.position.set(0,1,0),this._context.configuratorExtensionSelectors.getCanvasSystem().interactiveViewport$.subscribe(t=>{null!==t&&this.handleCanvasResize(t)})}_seedCameraControls=()=>{this.perspectiveCameraControls.enabled=!0,this.orthographicCameraControls.enabled=!0,this.perspectiveCameraControls.rotateTo(0,1.2,!1),this.orthographicCameraControls.rotateTo(0,1.2,!1),this.orthographicCameraControls.dollyTo(m(1e4,C),!1)};resetCamera=()=>{this._lastTransition&&this.scheduleCameraTransition(this._lastTransition,{bypassViewLocks:!0})};setTransitionState=t=>{this._transitionState$.next(t)};reportRender=()=>{this._rendered$.next()};scheduleCameraTransition(t,e={}){this._pendingAbortController?.abort();let r=new AbortController;this._pendingAbortController=r;let a=this._generation;return{finished:this._transitionQueue.add(async()=>{let i=r.signal;if(i.aborted||!a?.active)return;let o=c(t.cameraType).with(CameraControlType.Perspective,()=>this.perspectiveCameraControls).with(CameraControlType.Orthographic,()=>this.orthographicCameraControls).exhaustive(),s=null==this._activeCameraControls$.value,n=this._activeCameraControls$.value!==o;n&&this._activeCameraControls$.next(o),this._lastTransition=t,await t.run({controls:o,camera:o.camera,abortSignal:i,firstLoad:s,typeChanged:n,bypassViewLocks:!0===e.bypassViewLocks})}),abort:()=>{r.abort()}}}setCameraControlState=t=>{this._cameraControlState$.next(t)};handleCanvasResize=t=>{if(!d(t))return;let e=t.width/t.height;this.perspectiveCamera.aspect=e,this.perspectiveCamera.updateProjectionMatrix();let r=this.orthographicCamera,a=(r.left+r.right)/2,i=(r.top-r.bottom)/2*e;r.left=a-i,r.right=a+i,r.updateProjectionMatrix()};setCanvasPadding=t=>{let e=this._canvasPadding$.value;(e.top!==t.top||e.right!==t.right||e.bottom!==t.bottom||e.left!==t.left)&&this._canvasPadding$.next(t)};enable=()=>{this.perspectiveCameraControls.enabled=!0,this.orthographicCameraControls.enabled=!0};disable=()=>{this.perspectiveCameraControls.enabled=!1,this.orthographicCameraControls.enabled=!1};fitRootCamera=()=>{let t=this._context.configuratorExtensionSelectors.getCanvasSystem().ready$.value?.scene;t&&this.perspectiveCameraControls.fitToSphere(t,!1)};fitEntity=t=>{let e=this._context.configuratorExtensionSelectors.getCanvasSystem().ready$.value?.scene;if(!e)return;let r=e.getObjectByName(t.toString());null!=r&&this.perspectiveCameraControls.fitToBox(r,!1)};setCameraOffset=t=>{let{padding:e,viewPort:r,camera:a}=t;if(!d(r))return;if(![e.top,e.right,e.bottom,e.left].every(Number.isFinite)||e.left+e.right>=r.width||e.top+e.bottom>=r.height)return void a.clearViewOffset();let i=(e.right-e.left)/2,o=(e.bottom-e.top)/2;a.setViewOffset(r.width,r.height,i,o,r.width,r.height)};initialize=()=>{null!==this._generation&&(this._generation.active=!1),this._pendingAbortController?.abort(),this._pendingAbortController=void 0,this._lastTransition=void 0,this._transitionState$.next("still"),this._cameraControlState$.next("idle"),this._activeCameraControls$.next(void 0),this._generation={active:!0},this._seedCameraControls()}}
|
|
@@ -32,6 +32,7 @@ type ObjectFitConfig = {
|
|
|
32
32
|
readonly distance: number;
|
|
33
33
|
};
|
|
34
34
|
interface CommonCameraConfig {
|
|
35
|
+
readonly rotationLockGroup: string | null | undefined;
|
|
35
36
|
readonly priority: number;
|
|
36
37
|
readonly animateOnLoad: boolean;
|
|
37
38
|
readonly animateTransition: boolean;
|
|
@@ -61,6 +62,7 @@ interface CommonCameraConfig {
|
|
|
61
62
|
}
|
|
62
63
|
interface PerspectiveCameraConfig {
|
|
63
64
|
readonly cameraType: 'perspective';
|
|
65
|
+
readonly distanceLockGroup: string | null | undefined;
|
|
64
66
|
readonly fov: number;
|
|
65
67
|
readonly far: number;
|
|
66
68
|
readonly near: number;
|
|
@@ -68,6 +70,7 @@ interface PerspectiveCameraConfig {
|
|
|
68
70
|
}
|
|
69
71
|
interface OrthographicCameraConfig {
|
|
70
72
|
readonly cameraType: 'orthographic';
|
|
73
|
+
readonly zoomLockGroup: string | null | undefined;
|
|
71
74
|
readonly left: number;
|
|
72
75
|
readonly bottom: number;
|
|
73
76
|
readonly frustumSize: number;
|
|
@@ -81,6 +84,11 @@ export interface CameraConfig {
|
|
|
81
84
|
readonly commonConfig: CommonCameraConfig;
|
|
82
85
|
readonly typeSpecificConfig: PerspectiveCameraConfig | OrthographicCameraConfig;
|
|
83
86
|
}
|
|
87
|
+
interface PreservedCameraView {
|
|
88
|
+
readonly distance?: number;
|
|
89
|
+
readonly zoom?: number;
|
|
90
|
+
readonly rotation?: Pick<CommonCameraConfig, 'azimuthAngle' | 'polarAngle'>;
|
|
91
|
+
}
|
|
84
92
|
export type BoundingShapeDebugInfo = {
|
|
85
93
|
readonly shape: 'box';
|
|
86
94
|
readonly worldBox: Box3;
|
|
@@ -97,6 +105,7 @@ export declare class CameraSystem<ENTITIES extends DomainEntity<string>> {
|
|
|
97
105
|
readonly activeCameraConfig$: Observable<CameraConfig>;
|
|
98
106
|
private _useDefaultCamera;
|
|
99
107
|
private _cachedDefaultCameraComponent;
|
|
108
|
+
private _lastAppliedView;
|
|
100
109
|
private readonly _initializationGeneration$;
|
|
101
110
|
constructor(_context: CameraSystemContext);
|
|
102
111
|
getAllBoundingShapes(state: DomainState<ENTITIES>): readonly BoundingShapeDebugInfo[];
|
|
@@ -109,7 +118,21 @@ export declare class CameraSystem<ENTITIES extends DomainEntity<string>> {
|
|
|
109
118
|
private _resolveOrthographicCameraConfig;
|
|
110
119
|
private _resolvePerspectiveCameraConfig;
|
|
111
120
|
private _resolveTypeSpecificCameraConfig;
|
|
112
|
-
applyConfig(cameraConfig: CameraConfig, controls: CameraControls, animate: boolean, viewport: CameraFitViewport): Promise<void>;
|
|
121
|
+
applyConfig(cameraConfig: CameraConfig, controls: CameraControls, animate: boolean, viewport: CameraFitViewport, preservedView?: PreservedCameraView): Promise<void>;
|
|
122
|
+
/**
|
|
123
|
+
* Samples the live view aspects the next application may preserve. Each aspect is carried over
|
|
124
|
+
* only when both cameras name the same explicit lock group and the live value is a chosen view
|
|
125
|
+
* (see `isLiveAspectSettled`). Locks never cross a projection change, and the live values are
|
|
126
|
+
* read from the controls that are still configured for the previous camera — the caller samples
|
|
127
|
+
* before `applyConfig` rewrites their limits.
|
|
128
|
+
*/
|
|
129
|
+
private _capturePreservedView;
|
|
130
|
+
/**
|
|
131
|
+
* The live view a scheduled transition may preserve. The first application of a lifecycle has no
|
|
132
|
+
* history, and Reset Camera bypasses the locks for its own run only: the next transition consults
|
|
133
|
+
* the history as usual, which by then records the reset's destination as a settled view.
|
|
134
|
+
*/
|
|
135
|
+
private _captureTransitionView;
|
|
113
136
|
private _clampOrthographicGoalTargetToBoundary;
|
|
114
137
|
private _clampPerspectiveGoalTargetToBoundary;
|
|
115
138
|
initialize: () => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{BehaviorSubject as t,EMPTY as e,catchError as o,debounceTime as n,distinctUntilChanged as a,filter as i,from as r,map as s,of as m,shareReplay as l,skip as c,startWith as d,switchMap as p,take as u}from"rxjs";import{Box3 as g,OrthographicCamera as h,PerspectiveCamera as f,Vector3 as C}from"three";import{match as y}from"ts-pattern";import{applicationConfigComponent as x,boundingBoxComponent as S,boundingSphereComponent as _,distanceUnitToDefault as T,fitEntityOrthographicCameraComponent as v,fitEntityPerspectiveCameraComponent as b,fitRootOrthographicCameraComponent as w,fitRootPerspectiveCameraComponent as A,fitSelectionOrthographicCameraComponent as D,fitSelectionPerspectiveCameraComponent as z,isBoundingBoxComponent as M,isBoundingSphereComponent as E,isDomainEntityId as I,isFitEntityOrthographicCameraComponentSnapshot as j,isFitEntityPerspectiveCameraComponentSnapshot as P,isFitRootOrthographicCameraComponent as F,isFitRootPerspectiveCameraComponent as R,isFitSelectionOrthographicCameraComponent as k,isFitSelectionPerspectiveCameraComponent as O,isStaticOrthographicCameraComponent as B,isStaticPerspectiveCameraComponent as L,isVirtualEntityId as $,resolveBoundingBox as U,resolveBoundingSphere as Z,resolveCameraOrbit as G,staticOrthographicCameraComponent as q,staticPerspectiveCameraComponent as W}from"#package/domain";import{CanvasInitializationCancellationError as X}from"../canvas-system.js";import{computeMorphedBoundingBox as Y,computeMorphedBoundingSphere as N}from"./bounding-box.js";import{orthographicBoxGoal as H,orthographicSphereGoal as J,orthographicStaticGoal as K,perspectiveBoxGoal as Q,perspectiveSphereGoal as V,perspectiveStaticGoal as tt,resolveEffectiveFieldOfView as te,resolveEffectiveFitFrustum as to}from"./camera-goal.js";let tn=t=>F(t)||R(t)||P(t)||j(t)||O(t)||k(t);export class CameraSystem{_context;activeCameraConfig$;_useDefaultCamera=!0;_cachedDefaultCameraComponent=null;_initializationGeneration$=new t(0);constructor(t){this._context=t,this.activeCameraConfig$=this._initializationGeneration$.pipe(p(()=>this._context.configuratorExtensionSelectors.getCanvasSystem().firstLoadFinished$.pipe(p(t=>t?this._context.domainContextSelectors.getStoreSystem().stateSlices.committed:e),s(t=>{let e=this._readCameraComponents(t.state);return 0===e.length&&this._useDefaultCamera&&(e=[this._getDefaultCameraComponent(t.state)]),this._parseCameraData(e,t.state)}),i(t=>null!==t),a((t,e)=>t.cameraComponent===e.cameraComponent&&t.boundingBoxSnapshot===e.boundingBoxSnapshot&&t.boundingSphereSnapshot===e.boundingSphereSnapshot&&t.worldMatrix.equals(e.worldMatrix)&&t.dimensionUnit===e.dimensionUnit&&t.fitEntityId===e.fitEntityId&&t.fitShape===e.fitShape&&t.fitMode===e.fitMode))),p(t=>r(this._context.configuratorExtensionSelectors.getCanvasSystem().waitForFlush()).pipe(s(()=>t),o(t=>{if(t instanceof X)return e;throw t}))),p(t=>{let e=m({cameraData:t,objectToFit:null}),o=t.fitEntityId;return null===o?e:y(t.fitMode).with(null,()=>e).with("bounding",()=>e).with("mesh-dynamic",()=>this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d$({entityIds:[o],entityRoots:!0}).pipe(s(e=>({cameraData:t,objectToFit:e[0]??null})))).with("mesh-static",()=>m({cameraData:t,objectToFit:this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d({entityIds:[o],entityRoots:!0})[0]??null})).exhaustive()}),s(({cameraData:t,objectToFit:e})=>{let o=G({state:t.cameraComponent.state,dimensionUnit:t.dimensionUnit,worldMatrix:t.worldMatrix});return{objectFitConfig:this._getObjectFitConfig(t,e),commonConfig:this._resolveCommonCameraConfig(t.cameraComponent,o),typeSpecificConfig:this._resolveTypeSpecificCameraConfig(t.cameraComponent,t.dimensionUnit)}})).pipe(p(t=>this._context.configuratorExtensionSelectors.getCameraControlSystem().canvasPadding$.pipe(c(1),n(100),s(()=>t),d(t))),p(t=>this._context.configuratorExtensionSelectors.getDragAndDropSystem().draggingState$.pipe(i(t=>null===t),u(1),s(()=>t))),l(1)),this.activeCameraConfig$.subscribe(t=>{t&&this._context.configuratorExtensionSelectors.getCameraControlSystem().scheduleCameraTransition({cameraType:t.typeSpecificConfig.cameraType,run:async({controls:e,firstLoad:o,typeChanged:n})=>{let a=this._context.configuratorExtensionSelectors.getCanvasSystem().r3fHandle$.value;if(!a)return void this._context.domainServicesSelectors.getLogger().debug("Camera transition skipped: canvas not ready");let i=y({firstLoad:o,typeChanged:n}).with({firstLoad:!0},()=>t.commonConfig.animateOnLoad).with({firstLoad:!1,typeChanged:!1},()=>t.commonConfig.animateTransition).with({firstLoad:!1,typeChanged:!0},()=>!1).exhaustive(),r=a.get().size,s={width:r.width,height:r.height,padding:this._context.configuratorExtensionSelectors.getCameraControlSystem().canvasPadding$.value},m=new Promise(t=>setTimeout(t,300));Promise.race([this.applyConfig(t,e,i,s),m]).catch(t=>{this._context.domainServicesSelectors.getLogger().error("Camera transition failed",{},t)})}})})}getAllBoundingShapes(t){let e=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[S,_]},t),o=this._context.domainContextSelectors.getDimensionSystem().getDataDistanceUnit(t);return e.map(e=>{let n=e.componentInstance.entityId;if(!I(n))return null;let a=this._context.domainContextSelectors.getPositionProjections().getEntityWorldMatrix(n,t,null);return M(e)?{shape:"box",worldBox:U(e.state,a,o),componentSnapshot:e}:E(e)?{shape:"sphere",worldSphere:Z(e.state,a,o),componentSnapshot:e}:null}).filter(t=>null!==t)}_readCameraComponents(t){return this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[A,b,v,W,z,D,w,q]},t)}_parseCameraData(t,e){let o=t.filter(t=>{if(O(t)||k(t)){let o=this._context.domainContextSelectors.getNavigationSystem().getActiveSelectionState(e);return t.componentInstance.entityId===o}return!0});if(0===o.length)return null;let n=o.reduce((t,e)=>(e.state.priority??1)>(t.state.priority??1)?e:t),a=n.componentInstance.entityId;if($(a))throw Error("Cameras should not be attached to virtual entities");let i=tn(n)?n.state.fitToBox?"box":"sphere":null,r=e.rootEntityId,s=F(n)||R(n)?r:a,m=i?s:null,l=m?this._context.domainContextSelectors.getComponentReader().readOneComponent({componentType:S,entityId:s},e):null,c=m?this._context.domainContextSelectors.getComponentReader().readOneComponent({componentType:_,entityId:s},e):null,d=this._context.domainContextSelectors.getPositionProjections().getEntityWorldMatrix(a,e,null),p=this._context.domainContextSelectors.getDimensionSystem().getDataDistanceUnit(e),u=tn(n)&&!0===n.state.fitMeshChanges?"mesh-dynamic":"mesh-static",g=y(i).with("box",()=>null!==l?"bounding":u).with("sphere",()=>null!==c?"bounding":u).with(null,()=>null).exhaustive();return{cameraComponent:n,boundingBoxSnapshot:l,boundingSphereSnapshot:c,worldMatrix:d,dimensionUnit:p,fitEntityId:m,fitShape:i,fitMode:g}}_getDefaultCameraComponent(t){return null!==this._cachedDefaultCameraComponent&&this._cachedDefaultCameraComponent.componentInstance.entityId===t.rootEntityId||(this._cachedDefaultCameraComponent={componentInstance:{entityId:t.rootEntityId,instanceName:"--default-camera--",componentType:A},state:{animateOnLoad:!0,animateTransition:!0,azimuthAngle:-.2,polarAngle:Math.PI/2-.2}}),this._cachedDefaultCameraComponent}_getObjectFitConfig(t,e){let{cameraComponent:o,boundingBoxSnapshot:n,boundingSphereSnapshot:a,worldMatrix:i,fitShape:r,fitEntityId:s,dimensionUnit:m}=t,l={fitMode:"static",lookAtX:0,lookAtY:0,lookAtZ:0,distance:1e3};return y(r).with(null,()=>{if(L(o)||B(o)){let t=new C(T(o.state.lookAtX,m)??0,T(o.state.lookAtY,m)??0,T(o.state.lookAtZ,m)??0);return!0===o.state.relative&&t.applyMatrix4(i),{fitMode:"static",lookAtX:t.x,lookAtY:t.y,lookAtZ:t.z,distance:T(o.state.distance,m)??0}}throw Error("Static cameras are required when fitting is not specified")}).with("box",()=>n?{objectToFit:U(n.state,i,m),fitMode:"box",distance:T(o.state.distance,m)}:null==e?(this._context.domainServicesSelectors.getLogger()?.warn("Entity object not found",{entityId:s}),l):(e.updateMatrix(),e.updateMatrixWorld(),{objectToFit:Y(e),fitMode:"box",distance:T(o.state.distance,m)})).with("sphere",()=>a?{objectToFit:Z(a.state,i,m),fitMode:"sphere",distance:T(o.state.distance,m)}:null==e?(this._context.domainServicesSelectors.getLogger()?.warn("Entity object not found",{entityId:s}),l):(e.updateMatrix(),e.updateMatrixWorld(),{objectToFit:N(e),fitMode:"sphere",distance:T(o.state.distance,m)})).exhaustive()}getActiveCameraConfig(t){let e=this._parseCameraData(this._readCameraComponents(t),t);if(null===e)return null;let o=("mesh-static"===e.fitMode||"mesh-dynamic"===e.fitMode)&&null!==e.fitEntityId?this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d({entityIds:[e.fitEntityId],entityRoots:!0}):[],n=G({state:e.cameraComponent.state,dimensionUnit:e.dimensionUnit,worldMatrix:e.worldMatrix});return{objectFitConfig:this._getObjectFitConfig(e,o[0]??null),commonConfig:this._resolveCommonCameraConfig(e.cameraComponent,n),typeSpecificConfig:this._resolveTypeSpecificCameraConfig(e.cameraComponent,e.dimensionUnit)}}_resolveCommonCameraConfig(t,e){let o=t.state;return{priority:o.priority??1,animateOnLoad:o.animateOnLoad??!0,animateTransition:o.animateTransition??!0,minDistance:e.minDistance,maxDistance:e.maxDistance,polarAngle:e.polarAngle,minPolarAngle:e.minPolarAngle,maxPolarAngle:e.maxPolarAngle,azimuthAngle:e.azimuthAngle,minAzimuthAngle:e.minAzimuthAngle,maxAzimuthAngle:e.maxAzimuthAngle,smoothTime:o.smoothTime??.25,draggingSmoothTime:o.draggingSmoothTime??.125,maxSpeed:o.maxSpeed??1/0,azimuthRotateSpeed:o.azimuthRotateSpeed??1,polarRotateSpeed:o.polarRotateSpeed??1,dollySpeed:o.dollySpeed??1,dollyDragInverted:o.dollyDragInverted??!1,truckSpeed:o.truckSpeed??2,mouseButtons:o.mouseButtons??{left:1,middle:8,right:2,wheel:8},touches:o.touches??{one:32,two:1024,three:64},dollyToCursor:o.dollyToCursor??!1,dragToOffset:o.dragToOffset??!1,verticalDragToForward:o.verticalDragToForward??!1,boundaryEnclosesCamera:o.boundaryEnclosesCamera??!1,boundary:o.boundary??new g(new C(-1/0,-1/0,-1/0),new C(1/0,1/0,1/0))}}_resolveOrthographicCameraConfig(t,e){let o=t.state;return{cameraType:"orthographic",left:T(o.left,e)??0,bottom:T(o.bottom,e)??0,frustumSize:T(o.frustumSize,e)??1e4,far:T(o.far,e)??1e4,near:T(o.near,e)??100,minZoom:o.minZoom??.01,maxZoom:o.maxZoom??1/0}}_resolvePerspectiveCameraConfig(t,e){let o=t.state;return{cameraType:"perspective",fov:o.fov??50,far:T(o.far,e)??5e4,near:T(o.near,e)??100,focus:T(o.focus,e)??10}}_resolveTypeSpecificCameraConfig(t,e){if(B(t)||F(t)||j(t)||k(t))return this._resolveOrthographicCameraConfig(t,e);if(L(t)||R(t)||P(t)||O(t))return this._resolvePerspectiveCameraConfig(t,e);throw Error("Unsupported camera component")}async applyConfig(t,e,o,n){let{objectFitConfig:a,commonConfig:i,typeSpecificConfig:r}=t,{fullAspectRatio:s,effectiveAspectRatio:m,effectiveHeightRatio:l}=to(n);if("orthographic"===r.cameraType&&e.camera instanceof h){let t=e.camera;t.far=r.far,t.near=r.near,t.left=r.left-r.frustumSize*s/2,t.right=r.left+r.frustumSize*s/2,t.bottom=r.bottom-r.frustumSize/2,t.top=r.bottom+r.frustumSize/2,e.minZoom=r.minZoom,e.maxZoom=r.maxZoom}else if("perspective"===r.cameraType&&e.camera instanceof f){let t=e.camera;t.fov=r.fov,t.focus=r.focus,t.far=r.far,t.near=r.near,t.aspect=s}else throw Error("Camera does not match the config type");e.camera.updateProjectionMatrix(),e.minDistance=i.minDistance,e.maxDistance=i.maxDistance,e.minPolarAngle=i.minPolarAngle,e.maxPolarAngle=i.maxPolarAngle,e.minAzimuthAngle=i.minAzimuthAngle,e.maxAzimuthAngle=i.maxAzimuthAngle,e.smoothTime=i.smoothTime,e.draggingSmoothTime=i.draggingSmoothTime,e.maxSpeed=i.maxSpeed,e.azimuthRotateSpeed=i.azimuthRotateSpeed,e.polarRotateSpeed=i.polarRotateSpeed,e.dollySpeed=i.dollySpeed,e.truckSpeed=i.truckSpeed,e.dollyDragInverted=i.dollyDragInverted,e.mouseButtons=i.mouseButtons,e.touches=i.touches,e.boundaryEnclosesCamera=i.boundaryEnclosesCamera,e.dollyToCursor=i.dollyToCursor,e.dragToOffset=i.dragToOffset,e.verticalDragToForward=i.verticalDragToForward,e.setBoundary(i.boundary);let c={minDistance:i.minDistance,maxDistance:i.maxDistance};if("orthographic"===r.cameraType){let t=r.frustumSize*l,n=y(a).with({fitMode:"static"},t=>K(t,i,c)).with({fitMode:"sphere"},e=>J(e.objectToFit,t,m,e.distance,i,c)).with({fitMode:"box"},e=>H(e.objectToFit,t,m,e.distance,i,c)).exhaustive(),s=this._clampOrthographicGoalTargetToBoundary(n,a.fitMode,i.boundary);e.normalizeRotations(),await Promise.all([e.setLookAt(s.position.x,s.position.y,s.position.z,s.target.x,s.target.y,s.target.z,o),e.zoomTo(s.zoom,o)])}else{let t=te(r.fov,l),n=y(a).with({fitMode:"static"},t=>tt(t,i,c)).with({fitMode:"sphere"},e=>V(e.objectToFit,t,m,e.distance,i,c)).with({fitMode:"box"},e=>Q(e.objectToFit,t,m,e.distance,i,c)).exhaustive(),s=this._clampPerspectiveGoalTargetToBoundary(n,a.fitMode,i.boundary);e.normalizeRotations(),await e.setLookAt(s.position.x,s.position.y,s.position.z,s.target.x,s.target.y,s.target.z,o)}}_clampOrthographicGoalTargetToBoundary(t,e,o){if("static"===e)return t;let n=t.target.clone();return(o.clampPoint(n,n),n.equals(t.target))?t:{position:t.position.clone().add(n.clone().sub(t.target)),target:n,zoom:t.zoom}}_clampPerspectiveGoalTargetToBoundary(t,e,o){if("static"===e)return t;let n=t.target.clone();return(o.clampPoint(n,n),n.equals(t.target))?t:{position:t.position.clone().add(n.clone().sub(t.target)),target:n}}initialize=()=>{let t=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[x]},this._context.domainContextSelectors.getStoreSystem().stateSlices.committed.value.state);this._useDefaultCamera=t?.state.disableDefaultCamera!==!0,this._initializationGeneration$.next(this._initializationGeneration$.value+1)}}
|
|
1
|
+
import{BehaviorSubject as t,EMPTY as e,catchError as o,debounceTime as i,distinctUntilChanged as n,filter as a,from as r,map as s,of as m,shareReplay as c,skip as l,startWith as d,switchMap as p,take as u}from"rxjs";import{Box3 as g,OrthographicCamera as h,PerspectiveCamera as f,Vector3 as C}from"three";import{match as y}from"ts-pattern";import{applicationConfigComponent as x,boundingBoxComponent as S,boundingSphereComponent as v,distanceUnitToDefault as _,fitEntityOrthographicCameraComponent as T,fitEntityPerspectiveCameraComponent as w,fitRootOrthographicCameraComponent as b,fitRootPerspectiveCameraComponent as A,fitSelectionOrthographicCameraComponent as z,fitSelectionPerspectiveCameraComponent as D,isBoundingBoxComponent as M,isBoundingSphereComponent as E,isDomainEntityId as I,isFitEntityOrthographicCameraComponentSnapshot as k,isFitEntityPerspectiveCameraComponentSnapshot as P,isFitRootOrthographicCameraComponent as L,isFitRootPerspectiveCameraComponent as j,isFitSelectionOrthographicCameraComponent as F,isFitSelectionPerspectiveCameraComponent as G,isStaticOrthographicCameraComponent as R,isStaticPerspectiveCameraComponent as O,isVirtualEntityId as B,prepareCameraOrbit as Z,resolveBoundingBox as $,resolveBoundingSphere as U,resolvePreparedCameraRotation as V,staticOrthographicCameraComponent as q,staticPerspectiveCameraComponent as W}from"#package/domain";import{CanvasInitializationCancellationError as X}from"../canvas-system.js";import{computeMorphedBoundingBox as Y,computeMorphedBoundingSphere as N}from"./bounding-box.js";import{orthographicBoxGoal as H,orthographicSphereGoal as J,orthographicStaticGoal as K,perspectiveBoxGoal as Q,perspectiveSphereGoal as tt,perspectiveStaticGoal as te,resolveEffectiveFieldOfView as to,resolveEffectiveFitFrustum as ti}from"./camera-goal.js";let tn=(t,e)=>null!=t&&t===e,ta=t=>L(t)||j(t)||P(t)||k(t)||G(t)||F(t);export class CameraSystem{_context;activeCameraConfig$;_useDefaultCamera=!0;_cachedDefaultCameraComponent=null;_lastAppliedView=null;_initializationGeneration$=new t(0);constructor(t){this._context=t,this.activeCameraConfig$=this._initializationGeneration$.pipe(p(()=>this._context.configuratorExtensionSelectors.getCanvasSystem().firstLoadFinished$.pipe(p(t=>t?this._context.domainContextSelectors.getStoreSystem().stateSlices.committed:e),s(t=>{let e=this._readCameraComponents(t.state);return 0===e.length&&this._useDefaultCamera&&(e=[this._getDefaultCameraComponent(t.state)]),this._parseCameraData(e,t.state)}),a(t=>null!==t),n((t,e)=>t.cameraComponent===e.cameraComponent&&t.boundingBoxSnapshot===e.boundingBoxSnapshot&&t.boundingSphereSnapshot===e.boundingSphereSnapshot&&t.worldMatrix.equals(e.worldMatrix)&&t.dimensionUnit===e.dimensionUnit&&t.fitEntityId===e.fitEntityId&&t.fitShape===e.fitShape&&t.fitMode===e.fitMode))),p(t=>r(this._context.configuratorExtensionSelectors.getCanvasSystem().waitForFlush()).pipe(s(()=>t),o(t=>{if(t instanceof X)return e;throw t}))),p(t=>{let e=m({cameraData:t,objectToFit:null}),o=t.fitEntityId;return null===o?e:y(t.fitMode).with(null,()=>e).with("bounding",()=>e).with("mesh-dynamic",()=>this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d$({entityIds:[o],entityRoots:!0}).pipe(s(e=>({cameraData:t,objectToFit:e[0]??null})))).with("mesh-static",()=>m({cameraData:t,objectToFit:this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d({entityIds:[o],entityRoots:!0})[0]??null})).exhaustive()}),s(({cameraData:t,objectToFit:e})=>{let o=Z({state:t.cameraComponent.state,dimensionUnit:t.dimensionUnit,worldMatrix:t.worldMatrix});return{objectFitConfig:this._getObjectFitConfig(t,e),commonConfig:this._resolveCommonCameraConfig(t.cameraComponent,o),typeSpecificConfig:this._resolveTypeSpecificCameraConfig(t.cameraComponent,t.dimensionUnit)}})).pipe(p(t=>this._context.configuratorExtensionSelectors.getCameraControlSystem().canvasPadding$.pipe(l(1),i(100),s(()=>t),d(t))),p(t=>this._context.configuratorExtensionSelectors.getDragAndDropSystem().draggingState$.pipe(a(t=>null===t),u(1),s(()=>t))),c(1)),this.activeCameraConfig$.subscribe(t=>{t&&this._context.configuratorExtensionSelectors.getCameraControlSystem().scheduleCameraTransition({cameraType:t.typeSpecificConfig.cameraType,run:async({controls:e,firstLoad:o,typeChanged:i,bypassViewLocks:n})=>{let a=this._context.configuratorExtensionSelectors.getCanvasSystem().r3fHandle$.value;if(!a)return void this._context.domainServicesSelectors.getLogger().debug("Camera transition skipped: canvas not ready");let r=y({firstLoad:o,typeChanged:i}).with({firstLoad:!0},()=>t.commonConfig.animateOnLoad).with({firstLoad:!1,typeChanged:!1},()=>t.commonConfig.animateTransition).with({firstLoad:!1,typeChanged:!0},()=>!1).exhaustive(),s=a.get().size,m={width:s.width,height:s.height,padding:this._context.configuratorExtensionSelectors.getCameraControlSystem().canvasPadding$.value},c=this._captureTransitionView(t,e,o,n),l=this.applyConfig(t,e,r,m,c),d={config:t,preservedView:c,settled:!1},p=()=>{d.settled=!0};this._lastAppliedView=d,l.then(p,p),Promise.race([l,new Promise(t=>setTimeout(t,300))]).catch(t=>{this._context.domainServicesSelectors.getLogger().error("Camera transition failed",{},t)})}})})}getAllBoundingShapes(t){let e=this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[S,v]},t),o=this._context.domainContextSelectors.getDimensionSystem().getDataDistanceUnit(t);return e.map(e=>{let i=e.componentInstance.entityId;if(!I(i))return null;let n=this._context.domainContextSelectors.getPositionProjections().getEntityWorldMatrix(i,t,null);return M(e)?{shape:"box",worldBox:$(e.state,n,o),componentSnapshot:e}:E(e)?{shape:"sphere",worldSphere:U(e.state,n,o),componentSnapshot:e}:null}).filter(t=>null!==t)}_readCameraComponents(t){return this._context.domainContextSelectors.getComponentReader().readManyComponents({componentTypes:[A,w,T,W,D,z,b,q]},t)}_parseCameraData(t,e){let o=t.filter(t=>{if(G(t)||F(t)){let o=this._context.domainContextSelectors.getNavigationSystem().getActiveSelectionState(e);return t.componentInstance.entityId===o}return!0});if(0===o.length)return null;let i=o.reduce((t,e)=>(e.state.priority??1)>(t.state.priority??1)?e:t),n=i.componentInstance.entityId;if(B(n))throw Error("Cameras should not be attached to virtual entities");let a=ta(i)?i.state.fitToBox?"box":"sphere":null,r=e.rootEntityId,s=L(i)||j(i)?r:n,m=a?s:null,c=m?this._context.domainContextSelectors.getComponentReader().readOneComponent({componentType:S,entityId:s},e):null,l=m?this._context.domainContextSelectors.getComponentReader().readOneComponent({componentType:v,entityId:s},e):null,d=this._context.domainContextSelectors.getPositionProjections().getEntityWorldMatrix(n,e,null),p=this._context.domainContextSelectors.getDimensionSystem().getDataDistanceUnit(e),u=ta(i)&&!0===i.state.fitMeshChanges?"mesh-dynamic":"mesh-static",g=y(a).with("box",()=>null!==c?"bounding":u).with("sphere",()=>null!==l?"bounding":u).with(null,()=>null).exhaustive();return{cameraComponent:i,boundingBoxSnapshot:c,boundingSphereSnapshot:l,worldMatrix:d,dimensionUnit:p,fitEntityId:m,fitShape:a,fitMode:g}}_getDefaultCameraComponent(t){return null!==this._cachedDefaultCameraComponent&&this._cachedDefaultCameraComponent.componentInstance.entityId===t.rootEntityId||(this._cachedDefaultCameraComponent={componentInstance:{entityId:t.rootEntityId,instanceName:"--default-camera--",componentType:A},state:{animateOnLoad:!0,animateTransition:!0,azimuthAngle:-.2,polarAngle:Math.PI/2-.2}}),this._cachedDefaultCameraComponent}_getObjectFitConfig(t,e){let{cameraComponent:o,boundingBoxSnapshot:i,boundingSphereSnapshot:n,worldMatrix:a,fitShape:r,fitEntityId:s,dimensionUnit:m}=t,c={fitMode:"static",lookAtX:0,lookAtY:0,lookAtZ:0,distance:1e3};return y(r).with(null,()=>{if(O(o)||R(o)){let t=new C(_(o.state.lookAtX,m)??0,_(o.state.lookAtY,m)??0,_(o.state.lookAtZ,m)??0);return!0===o.state.relative&&t.applyMatrix4(a),{fitMode:"static",lookAtX:t.x,lookAtY:t.y,lookAtZ:t.z,distance:_(o.state.distance,m)??0}}throw Error("Static cameras are required when fitting is not specified")}).with("box",()=>i?{objectToFit:$(i.state,a,m),fitMode:"box",distance:_(o.state.distance,m)}:null==e?(this._context.domainServicesSelectors.getLogger()?.warn("Entity object not found",{entityId:s}),c):(e.updateMatrix(),e.updateMatrixWorld(),{objectToFit:Y(e),fitMode:"box",distance:_(o.state.distance,m)})).with("sphere",()=>n?{objectToFit:U(n.state,a,m),fitMode:"sphere",distance:_(o.state.distance,m)}:null==e?(this._context.domainServicesSelectors.getLogger()?.warn("Entity object not found",{entityId:s}),c):(e.updateMatrix(),e.updateMatrixWorld(),{objectToFit:N(e),fitMode:"sphere",distance:_(o.state.distance,m)})).exhaustive()}getActiveCameraConfig(t){let e=this._parseCameraData(this._readCameraComponents(t),t);if(null===e)return null;let o=("mesh-static"===e.fitMode||"mesh-dynamic"===e.fitMode)&&null!==e.fitEntityId?this._context.configuratorExtensionSelectors.getCanvasSystem().findEntity3d({entityIds:[e.fitEntityId],entityRoots:!0}):[],i=Z({state:e.cameraComponent.state,dimensionUnit:e.dimensionUnit,worldMatrix:e.worldMatrix});return{objectFitConfig:this._getObjectFitConfig(e,o[0]??null),commonConfig:this._resolveCommonCameraConfig(e.cameraComponent,i),typeSpecificConfig:this._resolveTypeSpecificCameraConfig(e.cameraComponent,e.dimensionUnit)}}_resolveCommonCameraConfig(t,e){let o=t.state;return{rotationLockGroup:o.rotationLockGroup,priority:o.priority??1,animateOnLoad:o.animateOnLoad??!0,animateTransition:o.animateTransition??!0,minDistance:e.minDistance,maxDistance:e.maxDistance,polarAngle:e.polarAngle,minPolarAngle:e.minPolarAngle,maxPolarAngle:e.maxPolarAngle,azimuthAngle:e.azimuthAngle,minAzimuthAngle:e.minAzimuthAngle,maxAzimuthAngle:e.maxAzimuthAngle,smoothTime:o.smoothTime??.25,draggingSmoothTime:o.draggingSmoothTime??.125,maxSpeed:o.maxSpeed??1/0,azimuthRotateSpeed:o.azimuthRotateSpeed??1,polarRotateSpeed:o.polarRotateSpeed??1,dollySpeed:o.dollySpeed??1,dollyDragInverted:o.dollyDragInverted??!1,truckSpeed:o.truckSpeed??2,mouseButtons:o.mouseButtons??{left:1,middle:8,right:2,wheel:8},touches:o.touches??{one:32,two:1024,three:64},dollyToCursor:o.dollyToCursor??!1,dragToOffset:o.dragToOffset??!1,verticalDragToForward:o.verticalDragToForward??!1,boundaryEnclosesCamera:o.boundaryEnclosesCamera??!1,boundary:o.boundary??new g(new C(-1/0,-1/0,-1/0),new C(1/0,1/0,1/0))}}_resolveOrthographicCameraConfig(t,e){let o=t.state;return{cameraType:"orthographic",zoomLockGroup:o.zoomLockGroup,left:_(o.left,e)??0,bottom:_(o.bottom,e)??0,frustumSize:_(o.frustumSize,e)??1e4,far:_(o.far,e)??1e4,near:_(o.near,e)??100,minZoom:o.minZoom??.01,maxZoom:o.maxZoom??1/0}}_resolvePerspectiveCameraConfig(t,e){let o=t.state;return{cameraType:"perspective",distanceLockGroup:o.distanceLockGroup,fov:o.fov??50,far:_(o.far,e)??5e4,near:_(o.near,e)??100,focus:_(o.focus,e)??10}}_resolveTypeSpecificCameraConfig(t,e){if(R(t)||L(t)||k(t)||F(t))return this._resolveOrthographicCameraConfig(t,e);if(O(t)||j(t)||P(t)||G(t))return this._resolvePerspectiveCameraConfig(t,e);throw Error("Unsupported camera component")}async applyConfig(t,e,o,i,n){let{objectFitConfig:a,commonConfig:r,typeSpecificConfig:s}=t,m=n?.rotation?{...r,...n.rotation}:r,c={...m,...V(m)},{fullAspectRatio:l,effectiveAspectRatio:d,effectiveHeightRatio:p}=ti(i);if("orthographic"===s.cameraType&&e.camera instanceof h){let t=e.camera;t.far=s.far,t.near=s.near,t.left=s.left-s.frustumSize*l/2,t.right=s.left+s.frustumSize*l/2,t.bottom=s.bottom-s.frustumSize/2,t.top=s.bottom+s.frustumSize/2,e.minZoom=s.minZoom,e.maxZoom=s.maxZoom}else if("perspective"===s.cameraType&&e.camera instanceof f){let t=e.camera;t.fov=s.fov,t.focus=s.focus,t.far=s.far,t.near=s.near,t.aspect=l}else throw Error("Camera does not match the config type");e.camera.updateProjectionMatrix(),e.minDistance=c.minDistance,e.maxDistance=c.maxDistance,e.minPolarAngle=c.minPolarAngle,e.maxPolarAngle=c.maxPolarAngle,e.minAzimuthAngle=c.minAzimuthAngle,e.maxAzimuthAngle=c.maxAzimuthAngle,e.smoothTime=c.smoothTime,e.draggingSmoothTime=c.draggingSmoothTime,e.maxSpeed=c.maxSpeed,e.azimuthRotateSpeed=c.azimuthRotateSpeed,e.polarRotateSpeed=c.polarRotateSpeed,e.dollySpeed=c.dollySpeed,e.truckSpeed=c.truckSpeed,e.dollyDragInverted=c.dollyDragInverted,e.mouseButtons=c.mouseButtons,e.touches=c.touches,e.boundaryEnclosesCamera=c.boundaryEnclosesCamera,e.dollyToCursor=c.dollyToCursor,e.dragToOffset=c.dragToOffset,e.verticalDragToForward=c.verticalDragToForward,e.setBoundary(c.boundary);let u={minDistance:c.minDistance,maxDistance:c.maxDistance};if("orthographic"===s.cameraType){let t=s.frustumSize*p,i=y(a).with({fitMode:"static"},t=>K(t,c,u)).with({fitMode:"sphere"},e=>J(e.objectToFit,t,d,e.distance,c,u)).with({fitMode:"box"},e=>H(e.objectToFit,t,d,e.distance,c,u)).exhaustive(),r=this._clampOrthographicGoalTargetToBoundary(i,a.fitMode,c.boundary),m=n?.zoom===void 0?r:{...r,zoom:Math.min(Math.max(n.zoom,s.minZoom),s.maxZoom)};e.normalizeRotations(),await Promise.all([e.setLookAt(m.position.x,m.position.y,m.position.z,m.target.x,m.target.y,m.target.z,o),e.zoomTo(m.zoom,o)])}else{let t=to(s.fov,p),i=n?.distance===void 0?a:{...a,distance:n.distance},r=y(i).with({fitMode:"static"},t=>te(t,c,u)).with({fitMode:"sphere"},e=>tt(e.objectToFit,t,d,e.distance,c,u)).with({fitMode:"box"},e=>Q(e.objectToFit,t,d,e.distance,c,u)).exhaustive(),m=this._clampPerspectiveGoalTargetToBoundary(r,i.fitMode,c.boundary);e.normalizeRotations(),await e.setLookAt(m.position.x,m.position.y,m.position.z,m.target.x,m.target.y,m.target.z,o)}}_capturePreservedView(t,e,o){let i;if(null===t||t.config.typeSpecificConfig.cameraType!==e.typeSpecificConfig.cameraType)return;let n=t.config,a=tn(n.commonConfig.rotationLockGroup,e.commonConfig.rotationLockGroup)&&(i=t.preservedView?.rotation!==void 0,t.settled||i)?{azimuthAngle:o.azimuthAngle,polarAngle:o.polarAngle}:void 0;if("perspective"===n.typeSpecificConfig.cameraType&&"perspective"===e.typeSpecificConfig.cameraType){let i;return{distance:tn(n.typeSpecificConfig.distanceLockGroup,e.typeSpecificConfig.distanceLockGroup)&&(i=t.preservedView?.distance!==void 0,t.settled||i)?o.distance:void 0,rotation:a}}if("orthographic"===n.typeSpecificConfig.cameraType&&"orthographic"===e.typeSpecificConfig.cameraType&&o.camera instanceof h){let i;return{zoom:tn(n.typeSpecificConfig.zoomLockGroup,e.typeSpecificConfig.zoomLockGroup)&&(i=t.preservedView?.zoom!==void 0,t.settled||i)?o.camera.zoom:void 0,rotation:a}}return{rotation:a}}_captureTransitionView(t,e,o,i){return o||i?void 0:this._capturePreservedView(this._lastAppliedView,t,e)}_clampOrthographicGoalTargetToBoundary(t,e,o){if("static"===e)return t;let i=t.target.clone();return(o.clampPoint(i,i),i.equals(t.target))?t:{position:t.position.clone().add(i.clone().sub(t.target)),target:i,zoom:t.zoom}}_clampPerspectiveGoalTargetToBoundary(t,e,o){if("static"===e)return t;let i=t.target.clone();return(o.clampPoint(i,i),i.equals(t.target))?t:{position:t.position.clone().add(i.clone().sub(t.target)),target:i}}initialize=()=>{let t=this._context.domainContextSelectors.getComponentReader().readFirstRootComponent({componentTypes:[x]},this._context.domainContextSelectors.getStoreSystem().stateSlices.committed.value.state);this._useDefaultCamera=t?.state.disableDefaultCamera!==!0,this._lastAppliedView=null,this._initializationGeneration$.next(this._initializationGeneration$.value+1)}}
|
|
@@ -118,7 +118,7 @@ export default createOnConfiguratorReadyProvider(({ children }) => {
|
|
|
118
118
|
|
|
119
119
|
## Camera Control API
|
|
120
120
|
|
|
121
|
-
### `scheduleCameraTransition(transition)`
|
|
121
|
+
### `scheduleCameraTransition(transition, options?)`
|
|
122
122
|
|
|
123
123
|
Schedules a camera transition. Aborts any previously scheduled transition.
|
|
124
124
|
|
|
@@ -133,10 +133,13 @@ Schedules a camera transition. Aborts any previously scheduled transition.
|
|
|
133
133
|
abortSignal: AbortSignal // signals when to stop
|
|
134
134
|
firstLoad: boolean // true if no camera was active before
|
|
135
135
|
typeChanged: boolean // true if switching perspective <-> orthographic
|
|
136
|
+
bypassViewLocks: boolean // true when Reset Camera re-runs the transition
|
|
136
137
|
}) => Promise<void>
|
|
137
138
|
}
|
|
138
139
|
```
|
|
139
140
|
|
|
141
|
+
The optional `options` object accepts `bypassViewLocks`. Reset Camera sets it to re-run the last transition without the [camera locks](?path=/docs/3d-scene-cameras-locks--docs); the built-in camera components read the matching `run` prop, and a custom transition may ignore it.
|
|
142
|
+
|
|
140
143
|
**Returns:**
|
|
141
144
|
|
|
142
145
|
```typescript
|
|
@@ -182,6 +182,8 @@ Notice that:
|
|
|
182
182
|
- Bounding shapes use `relative: true`, so they follow each entity's position offset. The Box is at `position: 0` and the BuckyBall at `position: 2` (meters, via `distanceDataUnit: m`) -- their bounding shapes are correctly centered on each entity despite using zero-centered coordinates
|
|
183
183
|
- Each entity has independent camera angles: Box uses `azimuthAngle: -1` (from the left), BuckyBall uses `azimuthAngle: 1` (from the right)
|
|
184
184
|
|
|
185
|
+
To retain the user's live rotation, distance, or zoom when a different fit-entity camera activates, see the [Camera Locks example](?path=/docs/3d-scene-cameras-locks--docs).
|
|
186
|
+
|
|
185
187
|
> **Debug visualization**: registered bounding shapes (blue), the active camera fit target (green), and perspective/orthographic camera frustums can be highlighted in 3D — open the Editor's **Debug** panel (eye icon in the left sidebar) and flip the **Camera Debug** switch on to see what the camera is fitting to.
|
|
186
188
|
|
|
187
189
|
## Test cases
|
|
@@ -711,6 +711,8 @@ Pay particular attention to the torus examples: the "Torus Box" view demonstrate
|
|
|
711
711
|
|
|
712
712
|
Each camera configuration maintains smooth user interaction within appropriate constraints while providing intelligent automatic positioning that adapts to the specific characteristics of each content type and fitting algorithm, making this system ideal for responsive configurators with diverse content requirements and precise camera control needs.
|
|
713
713
|
|
|
714
|
+
To retain the user's live rotation, distance, or zoom when switching between a fit-root camera and a related target, see the [Camera Locks example](?path=/docs/3d-scene-cameras-locks--docs).
|
|
715
|
+
|
|
714
716
|
> **Debug visualization**: registered bounding shapes (blue), the active camera fit target (green), and perspective/orthographic camera frustums can be highlighted in 3D — open the Editor's **Debug** panel (eye icon in the left sidebar) and flip the **Camera Debug** switch on to see what the camera is fitting to.
|
|
715
717
|
|
|
716
718
|
## Complete Implementation
|
|
@@ -709,6 +709,8 @@ The sphere fitting algorithm (`fitToBox: false`) provides natural, organic camer
|
|
|
709
709
|
|
|
710
710
|
**Dual Projection Modes**: Use the view controls to switch between perspective and orthographic projection modes. In perspective mode, cameras animate smoothly between selections for natural transitions. In orthographic mode, cameras are configured with `animateTransition: false` for immediate, technical switching that's ideal for precise examination and measurement tasks. This demonstrates how the same multi-layered selection system can provide both cinematic user experiences and professional technical workflows.
|
|
711
711
|
|
|
712
|
+
To retain the user's live rotation, distance, or zoom when a different selected camera activates, see the [Camera Locks example](?path=/docs/3d-scene-cameras-locks--docs).
|
|
713
|
+
|
|
712
714
|
> **Debug visualization**: registered bounding shapes (blue), the active camera fit target (green), and perspective/orthographic camera frustums can be highlighted in 3D — open the Editor's **Debug** panel (eye icon in the left sidebar) and flip the **Camera Debug** switch on to see which shape is being framed.
|
|
713
715
|
|
|
714
716
|
## Complete Implementation
|