Import CAD projects and cad-router v1

This commit is contained in:
Jerry
2026-07-22 19:38:36 +08:00
parent e4e0199bd5
commit af9f630c11
2253 changed files with 1994207 additions and 0 deletions
@@ -0,0 +1,281 @@
const Z_AXIS = [0, 0, 1];
const FLAT_ON_GROUND_TRANSFORM = [
0, 0, -1, 0,
0, 1, 0, 0,
1, 0, 0, -322.461928,
0, 0, 0, 1
];
// CAD coordinates measured from the STEP occurrence bounds. The side-view
// mechanism is a toggle four-bar: the lower pink rocker drives the yellow/red
// panel coupler out to an over-center tangent pose, then the coupler returns on
// the opposite branch with the panel flipped.
const PIVOTS = {
upperFrame: [-15.002, 229.609, 0],
lowerFrame: [104.981, 29.599, 0],
lowerBoard: [45.03, 229.61, 0],
upperBoard: [44.984, 420.401, 0]
};
const PANEL_COUPLER_PARTS = ["panel", "redLink", "driveRod", "hingeRod", "panelHardware"];
const LOWER_ROCKER_PARTS = ["rocker2", "greenRod", "handleHardware"];
const FRAME_DETAIL_PARTS = ["stopper", "bearings"];
const FRAME_PARTS = ["frame", ...FRAME_DETAIL_PARTS, "frameHardware"];
const HARDWARE = ["frameHardware", "handleHardware", "panelHardware"];
const CROSS_RODS = ["hingeRod", "driveRod"];
const MOVING_PARTS = ["rockerBlue", ...PANEL_COUPLER_PARTS, ...LOWER_ROCKER_PARTS];
const INITIAL_PANEL_ANGLE_DEG = angleDeg2(PIVOTS.lowerBoard, PIVOTS.upperBoard);
const INITIAL_BLUE_ANGLE_DEG = angleDeg2(PIVOTS.upperFrame, PIVOTS.upperBoard);
const INITIAL_LOWER_ROCKER_ANGLE_DEG = angleDeg2(PIVOTS.lowerFrame, PIVOTS.lowerBoard);
const LOWER_LINK_LENGTH = distance2(PIVOTS.lowerFrame, PIVOTS.lowerBoard);
const UPPER_LINK_LENGTH = distance2(PIVOTS.upperFrame, PIVOTS.upperBoard);
const COUPLER_LINK_LENGTH = distance2(PIVOTS.lowerBoard, PIVOTS.upperBoard);
const OVER_CENTER_CRANK_ANGLE_DEG = -3.22068;
function finite(value, fallback = 0) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : fallback;
}
function clamp(value, min, max) {
return Math.min(Math.max(finite(value, min), min), max);
}
function clamp01(value) {
return clamp(value, 0, 1);
}
function smooth01(value) {
const t = clamp01(value);
return t * t * (3 - (2 * t));
}
function lerp(start, end, t) {
return start + ((end - start) * t);
}
function lerpPoint(start, end, t) {
return [
lerp(start[0], end[0], t),
lerp(start[1], end[1], t),
lerp(start[2] || 0, end[2] || 0, t)
];
}
function distance2(start, end) {
return Math.hypot(end[0] - start[0], end[1] - start[1]);
}
function angleDeg2(start, end) {
return Math.atan2(end[1] - start[1], end[0] - start[0]) * 180 / Math.PI;
}
function pointFromAngle(origin, length, angleDeg) {
const angleRad = angleDeg * Math.PI / 180;
return [
origin[0] + (Math.cos(angleRad) * length),
origin[1] + (Math.sin(angleRad) * length),
origin[2] || 0
];
}
function circleIntersections(centerA, radiusA, centerB, radiusB) {
const dx = centerB[0] - centerA[0];
const dy = centerB[1] - centerA[1];
const distance = Math.hypot(dx, dy);
if (distance <= 1e-6) {
return [centerA, centerA];
}
const maxDistance = radiusA + radiusB;
const minDistance = Math.abs(radiusA - radiusB);
const solvedDistance = clamp(distance, minDistance + 1e-6, maxDistance - 1e-6);
const along = ((radiusA * radiusA) - (radiusB * radiusB) + (solvedDistance * solvedDistance)) / (2 * solvedDistance);
const height = Math.sqrt(Math.max(0, (radiusA * radiusA) - (along * along)));
const unitX = dx / distance;
const unitY = dy / distance;
const midX = centerA[0] + (along * unitX);
const midY = centerA[1] + (along * unitY);
const offsetX = -unitY * height;
const offsetY = unitX * height;
return [
[midX + offsetX, midY + offsetY, centerA[2] || 0],
[midX - offsetX, midY - offsetY, centerA[2] || 0]
];
}
function boardBranches(lower) {
const branches = circleIntersections(PIVOTS.upperFrame, UPPER_LINK_LENGTH, lower, COUPLER_LINK_LENGTH);
return branches.sort((a, b) => b[1] - a[1]);
}
function translateVector(start, end) {
return [
end[0] - start[0],
end[1] - start[1],
(end[2] || 0) - (start[2] || 0)
];
}
function cycleFlip(cycle) {
const phase = ((finite(cycle, 0) % 1) + 1) % 1;
return 0.5 - (0.5 * Math.cos(phase * Math.PI * 2));
}
function sampleMotion(rawFlip, { extension = 1, turnover = 1 } = {}) {
const flip = clamp01(rawFlip);
const travel = clamp(extension, 0.2, 1);
const turnoverAmount = clamp(turnover, 0, 1);
const extendedAngle = lerp(INITIAL_LOWER_ROCKER_ANGLE_DEG, OVER_CENTER_CRANK_ANGLE_DEG, travel);
const firstHalf = flip <= 0.5;
const legT = firstHalf ? smooth01(flip / 0.5) : smooth01((flip - 0.5) / 0.5);
const lowerAngleDeg = firstHalf
? lerp(INITIAL_LOWER_ROCKER_ANGLE_DEG, extendedAngle, legT)
: lerp(extendedAngle, INITIAL_LOWER_ROCKER_ANGLE_DEG, legT);
const lower = pointFromAngle(PIVOTS.lowerFrame, LOWER_LINK_LENGTH, lowerAngleDeg);
const [closedBranch, openBranch] = boardBranches(lower);
const upper = !firstHalf && turnoverAmount > 0 ? openBranch : closedBranch;
const angleDeg = angleDeg2(lower, upper);
return { lower, upper, angleDeg, lowerAngleDeg };
}
function applyVisibility(effects, params) {
effects.visible("*", true);
effects.visible("frame", params.showFrame !== false);
effects.visible(FRAME_DETAIL_PARTS, params.showFrame !== false && params.showDetails !== false);
effects.visible("frameHardware", params.showFrame !== false && params.showHardware === true);
effects.visible(["handleHardware", "panelHardware"], params.showHardware === true);
effects.visible(CROSS_RODS, params.showCrossRods !== false);
}
export default {
manifest: {
schemaVersion: 1,
step: {
path: "models/thang010146/180_degree_flip_mechanism.step"
},
label: "180 degree flip mechanism",
description: "Planar four-bar viewer rig reconstructed from the reference video: blue upper link, pink lower rocker, red/board coupler, and an over-center turnover.",
units: {
length: "mm",
angle: "deg",
time: "s"
},
features: {
frame: { ref: "#o1.1.1", label: "Frame", description: "Fixed frame body." },
stopper: { ref: "#o1.1.2", label: "Stopper" },
bearings: { ref: "#o1.1.3,o1.1.4", label: "Bearing blocks" },
frameHardware: { ref: "#o1.1.5,o1.1.6,o1.1.7,o1.1.8,o1.1.9,o1.1.10,o1.1.11,o1.1.12,o1.1.13,o1.1.14,o1.1.15,o1.1.16,o1.1.17,o1.1.18,o1.1.19,o1.1.20,o1.1.21,o1.1.22,o1.1.23,o1.1.24,o1.1.25,o1.1.26,o1.1.27,o1.1.28", label: "Frame hardware" },
rockerBlue: { ref: "#o1.2", label: "Blue upper link", description: "Upper 200 mm link rotating about the upper fixed pivot." },
rocker2: { ref: "#o1.3.1", label: "Pink lower rocker", description: "Lower rocker arm rotating about the lower fixed pivot." },
greenRod: { ref: "#o1.3.2", label: "Handle rod" },
handleHardware: { ref: "#o1.3.3,o1.3.4", label: "Handle hardware" },
redLink: { ref: "#o1.4.1", label: "Red coupler", description: "Rigid coupler between the upper and lower board pivots." },
panel: { ref: "#o1.4.2", label: "Flip panel", description: "Panel fixed to the red coupler." },
driveRod: { ref: "#o1.4.3", label: "Upper board rod" },
hingeRod: { ref: "#o1.4.7", label: "Lower board rod" },
panelHardware: { ref: "#o1.4.4,o1.4.5,o1.4.6,o1.4.8", label: "Panel nuts" }
},
parameters: {
flip: { type: "number", label: "Flip", description: "Closed to open travel. The animation loops this 0 -> 1 -> 0.", default: 0, min: 0, max: 1, step: 0.01 },
extension: { type: "number", label: "Extension", description: "How close the lower rocker drives to the over-center tangent pose.", default: 1, min: 0.2, max: 1, step: 0.01 },
turnover: { type: "number", label: "Turnover", description: "Enables the returning coupler to use the open branch after the over-center pose.", default: 1, min: 0, max: 1, step: 1 },
explode: { type: "number", label: "Explode", description: "Separates moving groups for inspection after the linkage motion is applied.", default: 0, min: 0, max: 1, step: 0.01 },
showFrame: { type: "boolean", label: "Frame", default: true },
showDetails: { type: "boolean", label: "Frame details", default: true },
showCrossRods: { type: "boolean", label: "Cross rods", default: true },
showHardware: { type: "boolean", label: "Hardware", default: true },
highlightLinks: { type: "boolean", label: "Highlight moving parts", default: false }
},
animations: {
flipLoop: {
label: "Reference flip loop",
description: "Closed -> over-center -> open -> over-center -> closed.",
duration: 5,
loop: true,
update({ cycle, set }) {
set("flip", cycleFlip(cycle));
}
},
inspectExplode: {
label: "Inspect explode",
duration: 5,
loop: true,
update({ cycle, set }) {
set("flip", cycleFlip(cycle));
set("explode", Math.sin((((finite(cycle, 0) % 1) + 1) % 1) * Math.PI));
}
}
}
},
update({ params, effects }) {
const extension = clamp(params.extension, 0.2, 1);
const turnover = clamp(params.turnover, 0, 1);
const explode = clamp01(params.explode);
const pose = sampleMotion(params.flip, { extension, turnover });
const panelAngleDelta = pose.angleDeg - INITIAL_PANEL_ANGLE_DEG;
const panelTranslate = translateVector(PIVOTS.lowerBoard, pose.lower);
const blueAngleDelta = angleDeg2(PIVOTS.upperFrame, pose.upper) - INITIAL_BLUE_ANGLE_DEG;
const lowerRockerAngleDelta = pose.lowerAngleDeg - INITIAL_LOWER_ROCKER_ANGLE_DEG;
applyVisibility(effects, params);
effects.style(FRAME_PARTS, { opacity: 1, edgeOpacity: 0.9 });
effects.style(FRAME_DETAIL_PARTS, { opacity: 1, edgeOpacity: 0.8 });
effects.style(HARDWARE, { opacity: 1, edgeOpacity: 0.7 });
effects.style("panel", { opacity: 1, edgeOpacity: 1 });
effects.style(["rocker2", "greenRod"], { opacity: 1, edgeOpacity: 1 });
effects.style("rockerBlue", { opacity: 1, edgeOpacity: 1 });
effects.style("redLink", { opacity: 1, edgeOpacity: 1 });
effects.style(CROSS_RODS, { opacity: 1, edgeOpacity: 1 });
if (params.highlightLinks !== false) {
effects.highlight(MOVING_PARTS, true);
}
effects.transform(PANEL_COUPLER_PARTS, {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: PIVOTS.lowerBoard,
angleDeg: panelAngleDelta
}
},
{ translate: [panelTranslate[0] + (explode * 16), panelTranslate[1] - (explode * 8), panelTranslate[2] + (explode * 24)] }
]
});
effects.transform("rockerBlue", {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: PIVOTS.upperFrame,
angleDeg: blueAngleDelta
}
},
{ translate: [-explode * 10, -explode * 8, explode * 18] }
]
});
effects.transform(LOWER_ROCKER_PARTS, {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: PIVOTS.lowerFrame,
angleDeg: lowerRockerAngleDelta
}
},
{ translate: [0, explode * 10, explode * 14] }
]
});
effects.transform("*", { matrix: FLAT_ON_GROUND_TRANSFORM });
}
};
@@ -0,0 +1,287 @@
const X_AXIS = [1, 0, 0];
const BOTTOM_FIXED_PIVOT = [14.610456, -171.775187, 23.0];
const TOP_FIXED_PIVOT_INITIAL = [14.610456, -171.775187, 69.00033];
const ROLLER_PIVOT_INITIAL = [14.610456, 124.677119, 69.00033];
const LOWER_ROLLER_CENTER_INITIAL = [14.610456, 124.677119, 23.0];
const UPPER_ROLLER_CENTER_INITIAL = [14.610456, 124.677119, 69.00033];
const ACTUATOR_PIVOT_INITIAL = [14.610456, -142.775187, 27.499913];
const INITIAL_HEIGHT = TOP_FIXED_PIVOT_INITIAL[2] - BOTTOM_FIXED_PIVOT[2];
const INITIAL_RUN = ROLLER_PIVOT_INITIAL[1] - BOTTOM_FIXED_PIVOT[1];
const LINK_LENGTH = Math.hypot(INITIAL_RUN, INITIAL_HEIGHT);
const INITIAL_LINK_ANGLE_DEG = angleDeg(INITIAL_HEIGHT, INITIAL_RUN);
const ACTUATOR_Y_OFFSET = ACTUATOR_PIVOT_INITIAL[1] - BOTTOM_FIXED_PIVOT[1];
const ROLLER_RADIUS = 20.0;
const RUNWAY_MIN_Y = -2.775187;
const RUNWAY_END_CLEARANCE = 12.0;
const MAX_RAISED_HEIGHT = maxSafeRaisedHeight();
const DEFAULT_RAISED_HEIGHT = 215;
const TOP_ASSEMBLY = ["topAssembly"];
const RISING_LINKAGE = ["risingLinkage"];
const DESCENDING_LINKAGE = ["descendingLinkage"];
const LOWER_ROLLERS = ["lowerRollers"];
const UPPER_ROLLERS = ["upperRollers"];
const ACTUATOR_ROD = ["piston", "actuatorCrossShaft"];
const ACTUATOR_SLIDER = ["actuatorSlider"];
const MOVING_PARTS = [
...TOP_ASSEMBLY,
...RISING_LINKAGE,
...DESCENDING_LINKAGE,
...LOWER_ROLLERS,
...UPPER_ROLLERS,
...ACTUATOR_ROD,
...ACTUATOR_SLIDER
];
function finite(value, fallback = 0) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : fallback;
}
function clamp(value, min, max) {
return Math.min(Math.max(finite(value, min), min), max);
}
function clamp01(value) {
return clamp(value, 0, 1);
}
function smooth01(value) {
const t = clamp01(value);
return t * t * (3 - (2 * t));
}
function lerp(start, end, t) {
return start + ((end - start) * t);
}
function angleDeg(height, run) {
return Math.atan2(height, run) * 180 / Math.PI;
}
function maxSafeRaisedHeight() {
const minRollerCenterY = RUNWAY_MIN_Y + ROLLER_RADIUS + RUNWAY_END_CLEARANCE;
const minRun = minRollerCenterY - BOTTOM_FIXED_PIVOT[1];
return Math.sqrt(Math.max(0, (LINK_LENGTH * LINK_LENGTH) - (minRun * minRun)));
}
function cycleLift(cycle) {
const phase = ((finite(cycle, 0) % 1) + 1) % 1;
if (phase < 0.42) {
return smooth01(phase / 0.42);
}
if (phase < 0.52) {
return 1;
}
if (phase < 0.94) {
return 1 - smooth01((phase - 0.52) / 0.42);
}
return 0;
}
function sampleLift(rawLift, rawRaisedHeight) {
const lift = smooth01(rawLift);
const raisedHeight = clamp(rawRaisedHeight, INITIAL_HEIGHT + 5, MAX_RAISED_HEIGHT);
const height = lerp(INITIAL_HEIGHT, raisedHeight, lift);
const run = Math.sqrt(Math.max(1e-6, (LINK_LENGTH * LINK_LENGTH) - (height * height)));
const angle = angleDeg(height, run);
const rollerY = BOTTOM_FIXED_PIVOT[1] + run;
const heightDelta = height - INITIAL_HEIGHT;
const rollerYDelta = rollerY - ROLLER_PIVOT_INITIAL[1];
// The reference video shows the actuator rising vertically while a green
// slider follows the lower blue link. Solve that slider by intersecting the
// lower link with the fixed actuator centerline.
const actuatorRatio = clamp(ACTUATOR_Y_OFFSET / run, 0, 1);
const actuatorZ = BOTTOM_FIXED_PIVOT[2] + (height * actuatorRatio);
const actuatorZDelta = actuatorZ - ACTUATOR_PIVOT_INITIAL[2];
return {
angle,
heightDelta,
rollerYDelta,
actuatorZDelta,
risingAngleDelta: angle - INITIAL_LINK_ANGLE_DEG,
descendingAngleDelta: -(angle - INITIAL_LINK_ANGLE_DEG),
wheelSpinDeg: -(rollerYDelta / (2 * Math.PI * ROLLER_RADIUS)) * 360
};
}
function applyVisibility(effects, params) {
effects.visible("*", true);
effects.visible("actuator", params.showActuator !== false);
effects.visible("hardware", params.showHardware !== false);
}
export default {
manifest: {
schemaVersion: 1,
step: {
path: "models/thang010146/adjustable_height_table_2.step"
},
label: "Adjustable height table 2",
description: "Viewer-time scissor-lift animation matched to the reference video: the top stays level, right pivots roll in the runways, and the vertical actuator drives the lower-link slider.",
units: {
length: "mm",
angle: "deg",
time: "s"
},
features: {
baseAssembly: { ref: "#o1.1", label: "Base assembly" },
basePlate: { ref: "#o1.1.1", label: "Base plate" },
baseBearings: { ref: "#o1.1.2,o1.1.3", label: "Base fixed bearings" },
baseRunways: { ref: "#o1.1.9,o1.1.10", label: "Base roller runways" },
cylinder: { ref: "#o1.1.8", label: "Fixed cylinder" },
topAssembly: { ref: "#o1.2", label: "Moving table top" },
topPlate: { ref: "#o1.2.1", label: "Table top" },
topBearings: { ref: "#o1.2.2,o1.2.3", label: "Top fixed bearings" },
topRunways: { ref: "#o1.2.8,o1.2.9", label: "Top roller runways" },
risingLinkage: { ref: "#o1.3", label: "Rising scissor links", description: "Links from the lower fixed pivots to the upper rolling pivots." },
descendingLinkage: { ref: "#o1.4", label: "Descending scissor links", description: "Links from the upper fixed pivots to the lower rolling pivots." },
centralPivot: { ref: "#o1.3.3,o1.3.4,o1.3.5", label: "Central scissor pivot" },
upperRollerShafts: { ref: "#o1.3.6,o1.3.7,o1.3.8,o1.3.9,o1.3.10,o1.3.11", label: "Upper roller shafts" },
lowerRollerShafts: { ref: "#o1.4.3,o1.4.4,o1.4.5,o1.4.6,o1.4.7,o1.4.8", label: "Lower roller shafts" },
upperRollers: { ref: "#o1.8,o1.9", label: "Upper pink rollers" },
lowerRollers: { ref: "#o1.6,o1.7", label: "Lower pink rollers" },
piston: { ref: "#o1.5", label: "Piston rod" },
actuatorSlider: { ref: "#o1.10", label: "Green actuator slider" },
actuatorCrossShaft: { ref: "#o1.11", label: "Actuator cross shaft" },
actuator: { ref: "#o1.1.8,o1.5,o1.10,o1.11", label: "Hydraulic actuator" },
hardware: {
ref: "#o1.1.4,o1.1.5,o1.1.6,o1.1.7,o1.2.4,o1.2.5,o1.2.6,o1.2.7,o1.3.4,o1.3.5,o1.3.7,o1.3.9,o1.3.10,o1.3.11,o1.4.4,o1.4.5,o1.4.7,o1.4.8",
label: "Nuts and bolts"
}
},
parameters: {
lift: { type: "number", label: "Lift", description: "Collapsed to raised scissor-lift travel.", default: 0, min: 0, max: 1, step: 0.01 },
raisedHeightMm: { type: "number", label: "Raised height", description: "Upper fixed-pivot height above the base fixed pivots at full lift; clamped so rollers stay inside the runways.", default: DEFAULT_RAISED_HEIGHT, min: 80, max: MAX_RAISED_HEIGHT, step: 1, unit: "mm" },
explode: { type: "number", label: "Explode", description: "Separates the motion groups for inspection after the linkage pose is applied.", default: 0, min: 0, max: 1, step: 0.01 },
showActuator: { type: "boolean", label: "Actuator", default: true },
showHardware: { type: "boolean", label: "Hardware", default: true },
highlightMoving: { type: "boolean", label: "Highlight moving parts", default: false }
},
animations: {
liftLoop: {
label: "Reference lift loop",
description: "Low table, hydraulic lift to raised position, brief dwell, and return.",
duration: 8,
loop: true,
update({ cycle, set }) {
set("lift", cycleLift(cycle));
}
},
inspectExplode: {
label: "Lift with exploded groups",
duration: 8,
loop: true,
update({ cycle, set }) {
const lift = cycleLift(cycle);
set("lift", lift);
set("explode", Math.sin(lift * Math.PI));
}
}
}
},
update({ params, effects }) {
const pose = sampleLift(params.lift, params.raisedHeightMm);
const explode = clamp01(params.explode);
applyVisibility(effects, params);
effects.style("basePlate", { opacity: 1, edgeOpacity: 0.8 });
effects.style(["baseBearings", "topBearings"], { opacity: 1, edgeOpacity: 0.8 });
effects.style(["baseRunways", "topRunways"], { opacity: 1, edgeOpacity: 0.9 });
effects.style("topPlate", { opacity: 1, edgeOpacity: 0.85 });
effects.style("risingLinkage", { opacity: 1, edgeOpacity: 1 });
effects.style("descendingLinkage", { opacity: 1, edgeOpacity: 1 });
effects.style("centralPivot", { opacity: 1, edgeOpacity: 1 });
effects.style(["lowerRollerShafts", "upperRollerShafts", "actuatorCrossShaft"], { opacity: 1, edgeOpacity: 0.9 });
effects.style(["lowerRollers", "upperRollers"], { opacity: 1, edgeOpacity: 1 });
effects.style("cylinder", { opacity: 1, edgeOpacity: 1 });
effects.style("piston", { opacity: 1, edgeOpacity: 1 });
effects.style("actuatorSlider", { opacity: 1, edgeOpacity: 1 });
effects.style("hardware", { opacity: 1, edgeOpacity: 0.7 });
if (params.highlightMoving !== false) {
effects.highlight(MOVING_PARTS, true);
}
effects.transform("topAssembly", {
translate: [0, explode * -10, pose.heightDelta + (explode * 22)]
});
effects.transform("risingLinkage", {
transforms: [
{
rotate: {
axis: X_AXIS,
origin: BOTTOM_FIXED_PIVOT,
angleDeg: pose.risingAngleDelta
}
},
{ translate: [explode * 12, explode * -8, explode * 12] }
]
});
effects.transform("descendingLinkage", {
transforms: [
{
rotate: {
axis: X_AXIS,
origin: TOP_FIXED_PIVOT_INITIAL,
angleDeg: pose.descendingAngleDelta
}
},
{ translate: [explode * -12, explode * 8, pose.heightDelta + (explode * 12)] }
]
});
effects.transform("lowerRollers", {
transforms: [
{
rotate: {
axis: X_AXIS,
origin: LOWER_ROLLER_CENTER_INITIAL,
angleDeg: pose.wheelSpinDeg
}
},
{ translate: [0, pose.rollerYDelta + (explode * 10), explode * 8] }
]
});
effects.transform("upperRollers", {
transforms: [
{
rotate: {
axis: X_AXIS,
origin: UPPER_ROLLER_CENTER_INITIAL,
angleDeg: pose.wheelSpinDeg
}
},
{ translate: [0, pose.rollerYDelta + (explode * -10), pose.heightDelta + (explode * 18)] }
]
});
effects.transform(ACTUATOR_ROD, {
translate: [0, 0, pose.actuatorZDelta + (explode * 10)]
});
effects.transform("actuatorSlider", {
transforms: [
{
rotate: {
axis: X_AXIS,
origin: ACTUATOR_PIVOT_INITIAL,
angleDeg: pose.risingAngleDelta
}
},
{ translate: [0, 0, pose.actuatorZDelta + (explode * 14)] }
]
});
}
};
@@ -0,0 +1,288 @@
const Z_AXIS = [0, 0, 1];
const GEAR_PITCH_RADIUS_MM = 20;
const MAX_GEAR_ANGLE_DEG = 82;
const LEFT_GEAR_CENTER = [-40, 0, 14.000004];
const RIGHT_GEAR_CENTER = [40, 0, 14.000004];
const LEFT_CRANK_PIN_INITIAL = [-40, -15, 14.000004];
const RIGHT_CRANK_PIN_INITIAL = [40, -15, 14.000004];
const LEFT_PISTON_PIN_INITIAL = [-20, -10, 14.000004];
const RIGHT_PISTON_PIN_INITIAL = [20, -10, 14.000004];
const LINK_LENGTH_MM = distance2(LEFT_PISTON_PIN_INITIAL, LEFT_CRANK_PIN_INITIAL);
const INITIAL_LEFT_LINK_ANGLE_DEG = angleDeg2(LEFT_PISTON_PIN_INITIAL, LEFT_CRANK_PIN_INITIAL);
const INITIAL_RIGHT_LINK_ANGLE_DEG = angleDeg2(RIGHT_PISTON_PIN_INITIAL, RIGHT_CRANK_PIN_INITIAL);
const MOVING_FEATURES = [
"piston",
"leftConrod",
"rightConrod",
"leftPinion",
"rightPinion",
"leftRack",
"rightRack"
];
function finite(value, fallback = 0) {
const numericValue = Number(value);
return Number.isFinite(numericValue) ? numericValue : fallback;
}
function clamp(value, min, max) {
return Math.min(Math.max(finite(value, min), min), max);
}
function clamp01(value) {
return clamp(value, 0, 1);
}
function smooth01(value) {
const t = clamp01(value);
return t * t * (3 - (2 * t));
}
function distance2(start, end) {
return Math.hypot(end[0] - start[0], end[1] - start[1]);
}
function angleDeg2(start, end) {
return Math.atan2(end[1] - start[1], end[0] - start[0]) * 180 / Math.PI;
}
function rotatePointZ(point, origin, angleDeg) {
const angleRad = angleDeg * Math.PI / 180;
const cosA = Math.cos(angleRad);
const sinA = Math.sin(angleRad);
const dx = point[0] - origin[0];
const dy = point[1] - origin[1];
return [
origin[0] + (dx * cosA) - (dy * sinA),
origin[1] + (dx * sinA) + (dy * cosA),
point[2]
];
}
function normalizeAngleDeg(value) {
let angle = finite(value, 0);
while (angle > 180) angle -= 360;
while (angle < -180) angle += 360;
return angle;
}
function solvePistonY(leftCrankPin) {
const dx = LEFT_PISTON_PIN_INITIAL[0] - leftCrankPin[0];
const allowedDx = clamp(dx, -LINK_LENGTH_MM + 1e-6, LINK_LENGTH_MM - 1e-6);
const dy = Math.sqrt(Math.max(1e-6, (LINK_LENGTH_MM * LINK_LENGTH_MM) - (allowedDx * allowedDx)));
// The uploaded STEP starts with the piston pin slightly ahead of the crank
// pin. Keep that same branch so the conrod never flips through the gear.
return leftCrankPin[1] + dy;
}
function samplePose(rawStroke) {
const stroke = smooth01(rawStroke);
const leftGearAngleDeg = stroke * MAX_GEAR_ANGLE_DEG;
const rightGearAngleDeg = -leftGearAngleDeg;
const rackTravelMm = (leftGearAngleDeg * Math.PI / 180) * GEAR_PITCH_RADIUS_MM;
const leftCrankPin = rotatePointZ(LEFT_CRANK_PIN_INITIAL, LEFT_GEAR_CENTER, leftGearAngleDeg);
const rightCrankPin = rotatePointZ(RIGHT_CRANK_PIN_INITIAL, RIGHT_GEAR_CENTER, rightGearAngleDeg);
const pistonY = solvePistonY(leftCrankPin);
const pistonDeltaY = pistonY - LEFT_PISTON_PIN_INITIAL[1];
const leftPistonPin = [LEFT_PISTON_PIN_INITIAL[0], pistonY, LEFT_PISTON_PIN_INITIAL[2]];
const rightPistonPin = [RIGHT_PISTON_PIN_INITIAL[0], pistonY, RIGHT_PISTON_PIN_INITIAL[2]];
return {
stroke,
pistonDeltaY,
rackTravelMm,
leftGearAngleDeg,
rightGearAngleDeg,
leftLinkAngleDeltaDeg: normalizeAngleDeg(angleDeg2(leftPistonPin, leftCrankPin) - INITIAL_LEFT_LINK_ANGLE_DEG),
rightLinkAngleDeltaDeg: normalizeAngleDeg(angleDeg2(rightPistonPin, rightCrankPin) - INITIAL_RIGHT_LINK_ANGLE_DEG)
};
}
function cycleStroke(cycle) {
const phase = ((finite(cycle, 0) % 1) + 1) % 1;
if (phase < 0.38) {
return smooth01(phase / 0.38);
}
if (phase < 0.50) {
return 1;
}
if (phase < 0.88) {
return 1 - smooth01((phase - 0.50) / 0.38);
}
return 0;
}
function applyVisibility(effects, params) {
effects.visible("*", true);
effects.visible("hardware", params.showHardware !== false);
}
export default {
manifest: {
schemaVersion: 1,
step: {
path: "models/thang010146/gear_rack_gripper.step"
},
label: "Robot gripper gear-rack assembly",
description: "Uploaded STEP assembly with viewer-time animation based on the reference video: a sliding piston drives two blue conrods, counter-rotating pinions, and opposing rack jaws.",
units: {
length: "mm",
angle: "deg",
time: "s"
},
features: {
base: { ref: "#o1.1.10", label: "Fixed base plate" },
cylinder: { ref: "#o1.1.9", label: "Fixed green cylinder" },
piston: { ref: "#o1.1.17", label: "Sliding piston rod" },
leftRack: { ref: "#o1.1.11", label: "Left moving rack jaw" },
rightRack: { ref: "#o1.1.12", label: "Right moving rack jaw" },
leftPinion: {
ref: "#o1.1.13",
label: "Left yellow pinion and crank",
origin: LEFT_GEAR_CENTER,
axis: Z_AXIS
},
rightPinion: {
ref: "#o1.1.14",
label: "Right yellow pinion and crank",
origin: RIGHT_GEAR_CENTER,
axis: Z_AXIS
},
leftConrod: { ref: "#o1.1.15", label: "Left blue connecting rod" },
rightConrod: { ref: "#o1.1.16", label: "Right blue connecting rod" },
hardware: {
ref: "#o1.1.1,#o1.1.2,#o1.1.3,#o1.1.4,#o1.1.5,#o1.1.6,#o1.1.7,#o1.1.8",
label: "Fixed bolts and pins"
},
movingAssembly: {
ref: "#o1.1.11,#o1.1.12,#o1.1.13,#o1.1.14,#o1.1.15,#o1.1.16,#o1.1.17",
label: "Moving drive train"
}
},
parameters: {
stroke: {
type: "number",
label: "Stroke",
description: "0 is the imported closed pose; 1 pushes the piston forward and opens the two rack jaws with the pinion pitch relationship preserved.",
default: 0,
min: 0,
max: 1,
step: 0.01
},
explode: {
type: "number",
label: "Explode",
description: "Separates the moving groups after the solved pose for mechanism inspection.",
default: 0,
min: 0,
max: 1,
step: 0.01
},
showHardware: { type: "boolean", label: "Hardware", default: true },
highlightMoving: { type: "boolean", label: "Highlight moving", default: false }
},
animations: {
driveLoop: {
label: "Piston rack drive",
description: "Closed pose, piston-driven rack opening, short dwell, then return.",
duration: 6,
loop: true,
update({ cycle, set }) {
set("stroke", cycleStroke(cycle));
}
},
inspectDrive: {
label: "Exploded drive inspection",
duration: 6,
loop: true,
update({ cycle, set }) {
const stroke = cycleStroke(cycle);
set("stroke", stroke);
set("explode", Math.sin(stroke * Math.PI));
}
}
}
},
update({ params, effects }) {
const pose = samplePose(params.stroke);
const explode = clamp01(params.explode);
applyVisibility(effects, params);
if (params.highlightMoving === true) {
effects.highlight(MOVING_FEATURES, true);
}
effects.transform("piston", {
translate: [0, pose.pistonDeltaY, explode * 12]
});
effects.transform("leftConrod", {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: LEFT_PISTON_PIN_INITIAL,
angleDeg: pose.leftLinkAngleDeltaDeg
}
},
{ translate: [0, pose.pistonDeltaY, explode * 16] }
]
});
effects.transform("rightConrod", {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: RIGHT_PISTON_PIN_INITIAL,
angleDeg: pose.rightLinkAngleDeltaDeg
}
},
{ translate: [0, pose.pistonDeltaY, explode * 16] }
]
});
effects.transform("leftPinion", {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: LEFT_GEAR_CENTER,
angleDeg: pose.leftGearAngleDeg
}
},
{ translate: [-explode * 8, -explode * 5, explode * 18] }
]
});
effects.transform("rightPinion", {
transforms: [
{
rotate: {
axis: Z_AXIS,
origin: RIGHT_GEAR_CENTER,
angleDeg: pose.rightGearAngleDeg
}
},
{ translate: [explode * 8, -explode * 5, explode * 18] }
]
});
effects.transform("leftRack", {
translate: [-pose.rackTravelMm - (explode * 14), 0, explode * 8]
});
effects.transform("rightRack", {
translate: [pose.rackTravelMm + (explode * 14), 0, explode * 8]
});
}
};
+45
View File
@@ -0,0 +1,45 @@
# Mechanism STEP Demos
Flattened STEP mechanism assemblies inspired by the YouTube channel
[thang010146](https://www.youtube.com/@thang010146/videos). Original
mechanism design, animation, and downloadable source files are credited to
`thang010146`.
This folder is intentionally flat so CAD Viewer can catalog the top-level STEP
assemblies and their hidden sidecars directly.
## Contents
- `*.step`: canonical mechanism assemblies.
- `.*.step.glb`: generated render/selector sidecars paired with STEP files.
- `.*.step.js`: optional interaction sidecars paired with STEP files.
Extracted source archives, Inventor files, SDF working files, videos, and
intermediate generation scripts are intentionally omitted from this fixture
bundle.
## Manifest
| Mechanism | STEP | Required sidecar | Optional sidecar | Source |
|---|---|---|---|---|
| 180 deg. flip mechanism | [180_degree_flip_mechanism.step](180_degree_flip_mechanism.step) | `.180_degree_flip_mechanism.step.glb` | `.180_degree_flip_mechanism.step.js` | [Video](https://www.youtube.com/watch?v=IGexfslM_5Y), [STEP archive](https://www.mediafire.com/file/pcjk004x96r6ibu/180FlipMechanismSTEP.zip/file) |
| Table of adjustable height 2 | [adjustable_height_table_2.step](adjustable_height_table_2.step) | `.adjustable_height_table_2.step.glb` | `.adjustable_height_table_2.step.js` | [Video](https://www.youtube.com/watch?v=c30g2UszMws), [STEP archive](https://www.mediafire.com/file/ulf0n6zbbp1veo4/TableAdjustHeight2STEP.zip/file) |
| Circular window iris shutter 2 | [circular_iris_shutter_2.step](circular_iris_shutter_2.step) | `.circular_iris_shutter_2.step.glb` | | [Video](https://www.youtube.com/watch?v=4ckPpHpe4TM), [STEP archive](https://www.mediafire.com/file/sz86hz0w9aknnen/CircularIrisShutter2STEP.zip/file) |
| Deployable drone | [deployable_drone.step](deployable_drone.step) | `.deployable_drone.step.glb` | | [Video](https://www.youtube.com/watch?v=QYt_wUP52T0), [STEP archive](https://www.mediafire.com/file/7533syysab1zhrg/DeployableDroneSTEP.zip/file) |
| Robot gripper of gear-rack drives | [gear_rack_gripper.step](gear_rack_gripper.step) | `.gear_rack_gripper.step.glb` | `.gear_rack_gripper.step.js` | [Video](https://www.youtube.com/watch?v=CP5q6YxyeQ8), user upload `RobotGripperGearRackSTEP.zip` |
| Hinge of 2 DoF | [hinge_2_dof.step](hinge_2_dof.step) | `.hinge_2_dof.step.glb` | | [Video](https://www.youtube.com/watch?v=Mvmi2AyNqx0), [STEP archive](https://www.mediafire.com/file/nl2m3asaa31dvnf/Hinge2DoFSTEP.zip/file) |
| Kinetic pyramid facade | [kinetic_pyramid_facade.step](kinetic_pyramid_facade.step) | `.kinetic_pyramid_facade.step.glb` | | [Video](https://www.youtube.com/watch?v=wA9d9uJIBGw), [STEP archive](https://www.mediafire.com/file/vri2hopfzj0k731/KineticPyramidFacadeSTEP.zip/file) |
| Knuckle joint press | [knuckle_joint_press.step](knuckle_joint_press.step) | `.knuckle_joint_press.step.glb` | | [Video](https://www.youtube.com/watch?v=Vd-vErlQPPk), [STEP archive](https://www.mediafire.com/file/8cy26hg2dlovi1b/KnuckleJointPressSTEP.zip/file) |
| Meat hash machine | [meat_hash_machine.step](meat_hash_machine.step) | `.meat_hash_machine.step.glb` | | [Video](https://www.youtube.com/watch?v=QVlepLAtYJk), [STEP archive](https://www.mediafire.com/file/q1hksit7cbdm8c6/MeatHashMachineSTEP.zip/file) |
| Object moving along 8-shaped runway | [object_8_shaped_runway.step](object_8_shaped_runway.step) | `.object_8_shaped_runway.step.glb` | | [Video](https://www.youtube.com/watch?v=KUD4qqCNmZk), [STEP archive](https://www.mediafire.com/file/lrpeog3wavxumek/Object8runwaySTEP.zip/file) |
| Planetary gearless mechanism 2 | [planetary_gearless_mechanism_2.step](planetary_gearless_mechanism_2.step) | `.planetary_gearless_mechanism_2.step.glb` | | [Video](https://www.youtube.com/watch?v=Vf1mpbv8DO4), [STEP archive](https://www.mediafire.com/file/jpwetalgq5ctqvr/PlanetaryGearlessMechanism2STEP.zip/file) |
| RC plane retractable landing gear | [rc_plane_retractable_landing_gear.step](rc_plane_retractable_landing_gear.step) | `.rc_plane_retractable_landing_gear.step.glb` | | [Video](https://www.youtube.com/watch?v=J8K3uAWMSRo), [STEP archive](https://www.mediafire.com/file/ma5cw3isx7oncfw/RCPlaneRetractableLandingGearSTEP.zip/file) |
| Screw 3-stage telescopic actuator 2b | [screw_3_stage_telescopic_actuator_2b.step](screw_3_stage_telescopic_actuator_2b.step) | `.screw_3_stage_telescopic_actuator_2b.step.glb` | | [Video](https://www.youtube.com/watch?v=LvDbXBtNrlQ), [STEP archive](https://www.mediafire.com/file/ohra998t0bjy8qj/Screw3stageTelescopicActuator2bSTEP.zip/file) |
| Spatial planetary belt drive | [spatial_planetary_belt_drive.step](spatial_planetary_belt_drive.step) | `.spatial_planetary_belt_drive.step.glb` | | [Video](https://www.youtube.com/watch?v=wnk6a-lBYCg), [STEP archive](https://www.mediafire.com/file/gbmj0t4ad11ely9/SpatialPlanetaryBeltSTEP.zip/file) |
| Static balancing mechanism using gears and spring | [static_balancing_gears_spring.step](static_balancing_gears_spring.step) | `.static_balancing_gears_spring.step.glb` | | [Video](https://www.youtube.com/watch?v=WnDc7tv79HM), [STEP archive](https://www.mediafire.com/file/1y4nqt4jer40uyc/StaticBalancingMechanismGearSpringSTEP.zip/file) |
| Two cam mechanism for drawing rectangles | [two_cam_draw_rectangles.step](two_cam_draw_rectangles.step) | `.two_cam_draw_rectangles.step.glb` | | [Video](https://www.youtube.com/watch?v=rEmLvLY65E8), [STEP archive](https://www.mediafire.com/file/09cbcf5of21szpn/TwoCamDrawRectangleSTEP.zip/file) |
| Two input shaft differential bevel gearbox | [two_input_shaft_differential_bevel_gearbox.step](two_input_shaft_differential_bevel_gearbox.step) | `.two_input_shaft_differential_bevel_gearbox.step.glb` | | [Video](https://www.youtube.com/watch?v=MLw1eTFPnDE), [STEP archive](https://www.mediafire.com/file/e09s8h9yoxe5luz/TwoInputShaftGearboxSTEP.zip/file) |
| Two stage gear multiplier | [two_stage_gear_multiplier.step](two_stage_gear_multiplier.step) | `.two_stage_gear_multiplier.step.glb` | | [Video](https://www.youtube.com/watch?v=y9Z5eSPG2TU), [STEP archive](https://www.mediafire.com/file/yt8nu74susrgcv3/TwoStageGearMultiplierSTEP.zip/file) |
| Converting two way linear motion into one way rotation 9b | [two_way_linear_to_one_way_rotation_9b.step](two_way_linear_to_one_way_rotation_9b.step) | `.two_way_linear_to_one_way_rotation_9b.step.glb` | | [Video](https://www.youtube.com/watch?v=PE_Wfw5BZXk), [STEP archive](https://www.mediafire.com/file/gue5rvk6b4dgt1s/Convert2WLto1WR9bSTEP.zip/file) |
| Typewriter desk | [typewriter_desk.step](typewriter_desk.step) | `.typewriter_desk.step.glb` | | [Video](https://www.youtube.com/watch?v=OTLIxTTfnWk), [STEP archive](https://www.mediafire.com/file/xk4fsefxcdl3mtr/TypewriterDeskSTEP.zip/file) |
| Wall mounted car shed | [wall_mounted_car_shed.step](wall_mounted_car_shed.step) | `.wall_mounted_car_shed.step.glb` | | [Video](https://www.youtube.com/watch?v=SnVo3Zg83Pk), [STEP archive](https://www.mediafire.com/file/gepccrx2g9h3d3b/WallMountedCarShedSTEP.zip/file) |