288 lines
11 KiB
JavaScript
288 lines
11 KiB
JavaScript
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)] }
|
|
]
|
|
});
|
|
}
|
|
};
|