fix: isolate HiCAD manufacturing extension
This commit is contained in:
parent
1038adae46
commit
36bcb3f5a5
@ -142,6 +142,10 @@ function patchOfficialSource() {
|
|||||||
path.join(customizationDir, "HicadManufacturingPanel.tsx"),
|
path.join(customizationDir, "HicadManufacturingPanel.tsx"),
|
||||||
path.join(sourceDir, "src/components/HicadManufacturingPanel.tsx")
|
path.join(sourceDir, "src/components/HicadManufacturingPanel.tsx")
|
||||||
);
|
);
|
||||||
|
copyFileSync(
|
||||||
|
path.join(customizationDir, "HicadManufacturingLauncher.tsx"),
|
||||||
|
path.join(sourceDir, "src/components/HicadManufacturingLauncher.tsx")
|
||||||
|
);
|
||||||
copyFileSync(
|
copyFileSync(
|
||||||
path.join(customizationDir, "localization.ts"),
|
path.join(customizationDir, "localization.ts"),
|
||||||
path.join(sourceDir, "src/lib/hicadLocalization.ts")
|
path.join(sourceDir, "src/lib/hicadLocalization.ts")
|
||||||
@ -197,15 +201,25 @@ function patchOfficialSource() {
|
|||||||
"import { isDesktop } from '@src/lib/isDesktop'\n",
|
"import { isDesktop } from '@src/lib/isDesktop'\n",
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
|
replaceSource(
|
||||||
|
"src/components/OpenedProject.tsx",
|
||||||
|
"import { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'\n",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
replaceSource(
|
||||||
|
"src/components/OpenedProject.tsx",
|
||||||
|
" <HicadManufacturingPanel projectName={projectName ?? 'HiCAD 模型'} />\n",
|
||||||
|
""
|
||||||
|
);
|
||||||
replaceSource(
|
replaceSource(
|
||||||
"src/components/OpenedProject.tsx",
|
"src/components/OpenedProject.tsx",
|
||||||
"import { AppHeader } from '@src/components/AppHeader'\n",
|
"import { AppHeader } from '@src/components/AppHeader'\n",
|
||||||
"import { AppHeader } from '@src/components/AppHeader'\nimport { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'\n"
|
"import { AppHeader } from '@src/components/AppHeader'\nimport { HicadManufacturingLauncher } from '@src/components/HicadManufacturingLauncher'\n"
|
||||||
);
|
);
|
||||||
replaceSource(
|
replaceSource(
|
||||||
"src/components/OpenedProject.tsx",
|
"src/components/OpenedProject.tsx",
|
||||||
" <StatusBar\n",
|
" <StatusBar\n",
|
||||||
" <HicadManufacturingPanel projectName={projectName ?? 'HiCAD 模型'} />\n <StatusBar\n"
|
" <HicadManufacturingLauncher projectName={projectName ?? 'HiCAD 模型'} />\n <StatusBar\n"
|
||||||
);
|
);
|
||||||
replaceSource(
|
replaceSource(
|
||||||
"src/lib/socket.ts",
|
"src/lib/socket.ts",
|
||||||
|
|||||||
@ -45,16 +45,25 @@ test.describe('HiCAD manufacturing extension', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
expect(vendorScript).toContain(
|
expect(vendorScript).toContain(
|
||||||
'vendor/zoo-modeling-app/HicadManufacturingPanel.tsx'
|
'vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx'
|
||||||
.split('/')
|
.split('/')
|
||||||
.at(-1)
|
.at(-1)
|
||||||
)
|
)
|
||||||
expect(vendorScript).toContain(
|
expect(vendorScript).toContain(
|
||||||
"import { HicadManufacturingPanel } from '@src/components/HicadManufacturingPanel'"
|
"import { HicadManufacturingLauncher } from '@src/components/HicadManufacturingLauncher'"
|
||||||
)
|
)
|
||||||
expect(vendorScript).toContain(
|
expect(vendorScript).toContain(
|
||||||
"<HicadManufacturingPanel projectName={projectName ?? 'HiCAD 模型'} />"
|
"<HicadManufacturingLauncher projectName={projectName ?? 'HiCAD 模型'} />"
|
||||||
)
|
)
|
||||||
|
const launcher = fs.readFileSync(
|
||||||
|
path.join(
|
||||||
|
projectRoot,
|
||||||
|
'vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx'
|
||||||
|
),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
expect(launcher).toContain("lazy(() =>")
|
||||||
|
expect(launcher).toContain('ManufacturingBoundary')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('creates non-zero reversible offsets for every independent body', () => {
|
test('creates non-zero reversible offsets for every independent body', () => {
|
||||||
|
|||||||
100
vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx
vendored
Normal file
100
vendor/zoo-modeling-app/HicadManufacturingLauncher.tsx
vendored
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import {
|
||||||
|
Component,
|
||||||
|
lazy,
|
||||||
|
Suspense,
|
||||||
|
useState,
|
||||||
|
type ErrorInfo,
|
||||||
|
type ReactNode,
|
||||||
|
} from 'react'
|
||||||
|
|
||||||
|
const ManufacturingPanel = lazy(() =>
|
||||||
|
import('./HicadManufacturingPanel').then((module) => ({
|
||||||
|
default: module.HicadManufacturingPanel,
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
|
||||||
|
class ManufacturingBoundary extends Component<
|
||||||
|
{
|
||||||
|
children: ReactNode
|
||||||
|
onClose: () => void
|
||||||
|
},
|
||||||
|
{ failed: boolean }
|
||||||
|
> {
|
||||||
|
state = { failed: false }
|
||||||
|
|
||||||
|
static getDerivedStateFromError() {
|
||||||
|
return { failed: true }
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: unknown, info: ErrorInfo) {
|
||||||
|
console.error('[HiCAD Manufacturing] Extension failed safely', error, info)
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (!this.state.failed) return this.props.children
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="fixed inset-0 z-[100] grid place-items-center bg-black/50 p-4"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="工程工具加载失败"
|
||||||
|
>
|
||||||
|
<div className="w-full max-w-md border border-chalkboard-30 bg-chalkboard-10 p-6 shadow-2xl dark:border-chalkboard-70 dark:bg-chalkboard-100">
|
||||||
|
<h2 className="text-lg font-semibold">工程工具暂时无法打开</h2>
|
||||||
|
<p className="mt-2 text-sm text-chalkboard-60 dark:text-chalkboard-40">
|
||||||
|
HiCAD 主编辑器没有受到影响。关闭后可以继续建模并稍后重试。
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="mt-5 h-9 bg-chalkboard-100 px-4 text-sm font-medium text-chalkboard-10 dark:bg-chalkboard-10 dark:text-chalkboard-100"
|
||||||
|
onClick={this.props.onClose}
|
||||||
|
>
|
||||||
|
关闭
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HicadManufacturingLauncher({
|
||||||
|
projectName,
|
||||||
|
}: {
|
||||||
|
projectName: string
|
||||||
|
}) {
|
||||||
|
const [active, setActive] = useState(false)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid="hicad-manufacturing-trigger"
|
||||||
|
onClick={() => setActive(true)}
|
||||||
|
className="absolute right-4 top-14 z-20 h-9 border border-chalkboard-30 bg-chalkboard-10 px-3 text-sm font-medium shadow-sm hover:bg-chalkboard-20 dark:border-chalkboard-70 dark:bg-chalkboard-90 dark:hover:bg-chalkboard-80"
|
||||||
|
>
|
||||||
|
工程出图
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{active && (
|
||||||
|
<ManufacturingBoundary onClose={() => setActive(false)}>
|
||||||
|
<Suspense
|
||||||
|
fallback={
|
||||||
|
<div className="fixed inset-0 z-[100] grid place-items-center bg-black/50">
|
||||||
|
<div className="border border-chalkboard-30 bg-chalkboard-10 px-5 py-4 text-sm shadow-xl dark:border-chalkboard-70 dark:bg-chalkboard-100">
|
||||||
|
正在加载工程工具…
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ManufacturingPanel
|
||||||
|
hideTrigger
|
||||||
|
initialOpen
|
||||||
|
onRequestClose={() => setActive(false)}
|
||||||
|
projectName={projectName}
|
||||||
|
/>
|
||||||
|
</Suspense>
|
||||||
|
</ManufacturingBoundary>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
import type { BoundingBox, ModelingCmd } from '@kittycad/lib'
|
import type { BoundingBox, ModelingCmd } from '@kittycad/lib'
|
||||||
import { useSignals } from '@preact/signals-react/runtime'
|
|
||||||
import { getBodiesFromArtifactGraph } from '@src/lang/std/artifactGraph'
|
import { getBodiesFromArtifactGraph } from '@src/lang/std/artifactGraph'
|
||||||
import { useSingletons } from '@src/lib/boot'
|
import { useSingletons } from '@src/lib/boot'
|
||||||
import { isModelingResponse } from '@src/lib/kcSdkGuards'
|
import { isModelingResponse } from '@src/lib/kcSdkGuards'
|
||||||
@ -109,13 +108,18 @@ function dataUrlFromSnapshot(contents: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HicadManufacturingPanel({
|
export function HicadManufacturingPanel({
|
||||||
|
hideTrigger = false,
|
||||||
|
initialOpen = false,
|
||||||
|
onRequestClose,
|
||||||
projectName,
|
projectName,
|
||||||
}: {
|
}: {
|
||||||
|
hideTrigger?: boolean
|
||||||
|
initialOpen?: boolean
|
||||||
|
onRequestClose?: () => void
|
||||||
projectName: string
|
projectName: string
|
||||||
}) {
|
}) {
|
||||||
useSignals()
|
|
||||||
const { kclManager } = useSingletons()
|
const { kclManager } = useSingletons()
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(initialOpen)
|
||||||
const [busy, setBusy] = useState(false)
|
const [busy, setBusy] = useState(false)
|
||||||
const [bodies, setBodies] = useState<BodyMeasurement[]>([])
|
const [bodies, setBodies] = useState<BodyMeasurement[]>([])
|
||||||
const [snapshot, setSnapshot] = useState<string>()
|
const [snapshot, setSnapshot] = useState<string>()
|
||||||
@ -131,37 +135,29 @@ export function HicadManufacturingPanel({
|
|||||||
const explodeOffsetsRef = useRef<ExplodeOffset[]>([])
|
const explodeOffsetsRef = useRef<ExplodeOffset[]>([])
|
||||||
const resolvedProjectName = projectName || 'HiCAD 模型'
|
const resolvedProjectName = projectName || 'HiCAD 模型'
|
||||||
|
|
||||||
useEffect(() => {
|
const loadBrandSettings = async () => {
|
||||||
let active = true
|
try {
|
||||||
void fetch('/api/site-settings?scope=chat', {
|
const response = await fetch('/api/site-settings?scope=chat', {
|
||||||
credentials: 'same-origin',
|
credentials: 'same-origin',
|
||||||
})
|
|
||||||
.then((response) => {
|
|
||||||
if (!response.ok) throw new Error('Unable to load site settings')
|
|
||||||
return response.json() as Promise<Record<string, unknown>>
|
|
||||||
})
|
})
|
||||||
.then((settings) => {
|
if (!response.ok) return
|
||||||
if (!active) return
|
const settings = (await response.json()) as Record<string, unknown>
|
||||||
const name =
|
const name =
|
||||||
typeof settings.siteName === 'string' && settings.siteName.trim()
|
typeof settings.siteName === 'string' && settings.siteName.trim()
|
||||||
? settings.siteName.trim()
|
? settings.siteName.trim()
|
||||||
: '由壹点AI'
|
: '由壹点AI'
|
||||||
const configuredLogo =
|
const configuredLogo =
|
||||||
typeof settings.siteLogo === 'string' && settings.siteLogo.trim()
|
typeof settings.siteLogo === 'string' && settings.siteLogo.trim()
|
||||||
? settings.siteLogo.trim()
|
? settings.siteLogo.trim()
|
||||||
: typeof settings.siteLogoDark === 'string' &&
|
: typeof settings.siteLogoDark === 'string' &&
|
||||||
settings.siteLogoDark.trim()
|
settings.siteLogoDark.trim()
|
||||||
? settings.siteLogoDark.trim()
|
? settings.siteLogoDark.trim()
|
||||||
: '/api/favicon?surface=hicad'
|
: '/api/favicon?surface=hicad'
|
||||||
setBrand({ name, logoUrl: configuredLogo })
|
setBrand({ name, logoUrl: configuredLogo })
|
||||||
})
|
} catch {
|
||||||
.catch(() => {
|
// Keep the built-in website branding when settings are unavailable.
|
||||||
// Keep the built-in website branding when settings are unavailable.
|
|
||||||
})
|
|
||||||
return () => {
|
|
||||||
active = false
|
|
||||||
}
|
}
|
||||||
}, [])
|
}
|
||||||
|
|
||||||
const sendModelingCommand = async (cmd: ModelingCmd) => {
|
const sendModelingCommand = async (cmd: ModelingCmd) => {
|
||||||
const response = await kclManager.engineCommandManager.sendSceneCommand({
|
const response = await kclManager.engineCommandManager.sendSceneCommand({
|
||||||
@ -304,8 +300,17 @@ export function HicadManufacturingPanel({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This component is lazy-mounted by the launcher, so automatic analysis only
|
||||||
|
// starts after the user explicitly opens the engineering tools.
|
||||||
|
useEffect(() => {
|
||||||
|
if (!initialOpen) return
|
||||||
|
void loadBrandSettings()
|
||||||
|
void analyzeModel()
|
||||||
|
}, [initialOpen])
|
||||||
|
|
||||||
const handleOpen = () => {
|
const handleOpen = () => {
|
||||||
setOpen(true)
|
setOpen(true)
|
||||||
|
void loadBrandSettings()
|
||||||
if (bodies.length === 0) {
|
if (bodies.length === 0) {
|
||||||
void analyzeModel()
|
void analyzeModel()
|
||||||
}
|
}
|
||||||
@ -450,6 +455,7 @@ export function HicadManufacturingPanel({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
setOpen(false)
|
setOpen(false)
|
||||||
|
onRequestClose?.()
|
||||||
} finally {
|
} finally {
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
}
|
}
|
||||||
@ -457,14 +463,16 @@ export function HicadManufacturingPanel({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<button
|
{!hideTrigger && (
|
||||||
type="button"
|
<button
|
||||||
data-testid="hicad-manufacturing-trigger"
|
type="button"
|
||||||
onClick={handleOpen}
|
data-testid="hicad-manufacturing-trigger"
|
||||||
className="absolute right-4 top-14 z-20 h-9 px-3 border border-chalkboard-30 dark:border-chalkboard-70 bg-chalkboard-10 dark:bg-chalkboard-90 hover:bg-chalkboard-20 dark:hover:bg-chalkboard-80 shadow-sm text-sm font-medium"
|
onClick={handleOpen}
|
||||||
>
|
className="absolute right-4 top-14 z-20 h-9 border border-chalkboard-30 bg-chalkboard-10 px-3 text-sm font-medium shadow-sm hover:bg-chalkboard-20 dark:border-chalkboard-70 dark:bg-chalkboard-90 dark:hover:bg-chalkboard-80"
|
||||||
工程出图
|
>
|
||||||
</button>
|
工程出图
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
|
||||||
{open && (
|
{open && (
|
||||||
<div
|
<div
|
||||||
|
|||||||
3
vendor/zoo-modeling-app/README.md
vendored
3
vendor/zoo-modeling-app/README.md
vendored
@ -8,6 +8,9 @@ reviewed official `KittyCAD/modeling-app` source.
|
|||||||
- `HicadManufacturingPanel.tsx` integrates engine measurements, reversible
|
- `HicadManufacturingPanel.tsx` integrates engine measurements, reversible
|
||||||
exploded views, BOM editing, CSV export, snapshots, and printable production
|
exploded views, BOM editing, CSV export, snapshots, and printable production
|
||||||
sheets.
|
sheets.
|
||||||
|
- `HicadManufacturingLauncher.tsx` lazy-loads the extension only after the user
|
||||||
|
clicks **工程出图** and contains a dedicated error boundary, so an extension
|
||||||
|
failure cannot take down the official HiCAD editor.
|
||||||
- `manufacturing.ts` contains the engine-independent geometry, BOM, and report
|
- `manufacturing.ts` contains the engine-independent geometry, BOM, and report
|
||||||
generation logic.
|
generation logic.
|
||||||
- `scripts/vendor-zoo-modeling-app.mjs` copies both files into the official
|
- `scripts/vendor-zoo-modeling-app.mjs` copies both files into the official
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user