AnasSarkiz/magnetic-shutter-remote-r8
A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.
- Version
- 0.4.0-prototype
- License
- unset
- Stars
- 0
product/assembly.tsx
import { assembly } from "@tscircuit/core";
import { z } from "zod";
import pcbGlb from "./models/r8-pcb.glb";
import parts2Glb from "./models/r8-parts-2.glb";
import parts3Glb from "./models/r8-parts-3.glb";
import parts4Glb from "./models/r8-parts-4.glb";
import parts5Glb from "./models/r8-parts-5.glb";
import parts6Glb from "./models/r8-parts-6.glb";
import parts7Glb from "./models/r8-parts-7.glb";
import parts8Glb from "./models/r8-parts-8.glb";
import l3Obj from "../imports/AHW2012C_24NJTF/AHW2012C_24NJTF.obj";
import nativeObj from "./native-obj-model.json";
import remoteBaseGlb from "./models/mechanical/RemoteBase.glb";
import batterySupportGlb from "./models/mechanical/BatterySupport.glb";
import batteryEnvelopeGlb from "./models/mechanical/ASR00012_MaximumEnvelope.glb";
import shutterPlungerGlb from "./models/mechanical/SideShutterPlunger.glb";
import remoteLidGlb from "./models/mechanical/RemoteLid.glb";
import fingerGripGlb from "./models/mechanical/FingerGripInsert.glb";
import rearCoverGlb from "./models/mechanical/MagSafeRearCover.glb";
import phoneDockGlb from "./models/mechanical/MagSafeGrip.glb";
import phoneCoverGlb from "./models/mechanical/MagSafeFaceCover.glb";
import magnetReferenceGlb from "./models/mechanical/MagSafeArrayReference.glb";
import { pcbPointInProduct, dimensions as d } from "./dimensions";
import { createProductParts } from "./geometry";
import review from "./geometry-review.json";
import pcbReview from "./pcb-model-review.json";
const vector = z.tuple([z.number(), z.number(), z.number()]);
const mechanicalPartName = z.enum([
"RemoteBase",
"BatterySupport",
"ASR00012_MaximumEnvelope",
"SideShutterPlunger",
"RemoteLid",
"FingerGripInsert",
"MagSafeRearCover",
"MagSafeGrip",
"MagSafeFaceCover",
"MagSafeArrayReference",
]);
const mechanicalModelUrls = {
RemoteBase: remoteBaseGlb,
BatterySupport: batterySupportGlb,
ASR00012_MaximumEnvelope: batteryEnvelopeGlb,
SideShutterPlunger: shutterPlungerGlb,
RemoteLid: remoteLidGlb,
FingerGripInsert: fingerGripGlb,
MagSafeRearCover: rearCoverGlb,
MagSafeGrip: phoneDockGlb,
MagSafeFaceCover: phoneCoverGlb,
MagSafeArrayReference: magnetReferenceGlb,
};
const measuredParts = z
.array(
z.object({ name: z.string(), localBoundsMm: z.tuple([vector, vector]) }),
)
.parse(review.parts);
export function R8ProductAssembly({
exploded = false,
mechanicalSource = "imported-models",
}: {
exploded?: boolean;
mechanicalSource?: "native-plans" | "imported-models";
}) {
return (
<assembly.device name="R9MagneticShutterProductPrototype">
{[
pcbGlb,
parts2Glb,
parts3Glb,
parts4Glb,
parts5Glb,
parts6Glb,
parts7Glb,
parts8Glb,
].map((glbUrl, index) => (
<assembly.subassembly
key={pcbReview.nativeAssets[index].filename}
name={
index === 0 ? "R9PCBAndFittedParts1" : `R9FittedParts${index + 1}`
}
displayName={`Actual R9 PCB geometry fragment${index + 1} of8`}
cadModel={{
glbUrl,
modelBoardNormalDirection: "z+",
modelUnitToMmScale: 1,
size: pcbReview.nativeAssets[index].sizeMm,
rotationOffset: { x: 0, y: 0, z: 0 },
modelOriginPosition: { x: 0, y: 0, z: 0 },
positionOffset: {
x: d.remote.centerXMm,
y: d.remote.centerYMm,
z: d.pcb.centerZMm + (exploded ? 26 : 0),
},
}}
/>
))}
<assembly.subassembly
name="L3"
displayName="Actual supplier24nH inductor OBJ"
cadModel={{
objUrl: l3Obj,
modelBoardNormalDirection: "z+",
modelOriginPosition: nativeObj.modelOriginPosition,
modelUnitToMmScale: nativeObj.modelUnitToMmScale,
rotationOffset: {
x: 0,
y: 0,
z: nativeObj.rotation.z + d.remote.ccwRotationDegrees,
},
positionOffset: {
...pcbPointInProduct(nativeObj.position),
z: d.pcb.centerZMm + nativeObj.position.z + (exploded ? 26 : 0),
},
}}
/>
{createProductParts().map((part) => {
const measured = measuredParts.find((row) => row.name === part.name);
if (!measured)
throw new Error(`Missing mechanical measurements: ${part.name}`);
const [min, max] = measured.localBoundsMm;
const center = {
x: (min[0] + max[0]) / 2,
y: (min[1] + max[1]) / 2,
z: (min[2] + max[2]) / 2,
};
const productCenter = center;
return (
<assembly.subassembly
key={part.name}
name={part.name}
displayName={part.description}
cadModel={{
...(mechanicalSource === "native-plans"
? {
jscad: {
type: "translate" as const,
vector: [-center.x, -center.y, -center.z],
shape: part.plan,
},
}
: {
glbUrl:
mechanicalModelUrls[mechanicalPartName.parse(part.name)],
modelBoardNormalDirection: "z+" as const,
modelUnitToMmScale: 1,
}),
size: {
x: max[0] - min[0],
y: max[1] - min[1],
z: max[2] - min[2],
},
modelOriginPosition: { x: 0, y: 0, z: 0 },
positionOffset: {
x: productCenter.x,
y: productCenter.y,
z: center.z + (exploded ? part.explodeZMm : 0),
},
rotationOffset: {
x: 0,
y: 0,
z: 0,
},
showAsTranslucentModel: part.referenceOnly ?? false,
}}
/>
);
})}
</assembly.device>
);
}