demo video: swing the deformation from 0, close on a slow orbit

The results start on the undeformed shape (scale 0) and the deformation
swings, eased, from 0 to x150, back and out again, one picture per frame.
The video ends on a 60-frame camera orbit round the deformed beam
instead of cuts between views. The results camera is drawn back so the
contour legend leaves the beam clear, and each stage holds 2-3 s.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-14 00:40:45 -04:00
1 parent 30812250d3
commit 8a8ed75103
2 files changed
+44 -20

No files matched your search

+7 -4
View File
@@ -25,10 +25,13 @@ What it does:
speaks MCP (JSON-RPC over stdio) to it. Every step is an MCP tool call: `open` the design,
`mesh` (`hex_sweep`, 4 mm), `batch` (steel, a solid section, the root clamp, a kinematic
coupling of the free end at x = 200 mm to a reference point loaded with 1000 N down, −z), `check_model`, `solve`
(`static`), and `show_results` for the von Mises contours and the deformed shape, its `scale`
stepped from 1 to 150 and back. Every picture is the MCP `screenshot` tool with
`source: window` (the whole window drawn offscreen, `ui.capture`), turned to the `iso` or
`front` view by its `view` argument.
(`static`), and `show_results` for the von Mises contours on the undeformed shape
(`scale: 0`) and then the deformed shape, its `scale` swung (eased) from 0 to 150, back to 0
and out to 150 again. Every picture is the MCP `screenshot` tool with `source: window` (the
whole window drawn offscreen, `ui.capture`): the setup in the `iso` view, the results through
its `camera` argument (the iso orientation drawn back to 420 mm so the contour legend leaves
the beam clear), and to close, a slow orbit of 60 pictures, one `camera` angle each, from the
iso view half way round the beam. Each stage holds for 2 to 3 seconds.
4. Captions each stage with the MCP tool it calls and what it does (`open · tests/corpus/…`,
`mesh · hex sweep 4 mm`, `solve · static`, …) in a band under the window. `caption.py`
draws the bars in the standard library: a minimal TrueType reader (`cmap` format 4, `loca`,
+37 -16
View File
@@ -12,6 +12,7 @@ Python standard library only; ffmpeg encodes the frames.
import argparse
import base64
import json
import math
import os
import shutil
import socket
@@ -44,11 +45,19 @@ STEP = "Load"
# Frames per second of the video; each stage holds its picture for a number of frames.
FPS = 10
HOLD = 14 # a workflow stage (1.4 s)
HOLD_SHORT = 10 # one of the model definition steps
HOLD_LONG = 20 # the stress result
SWING = 12 # pictures from undeformed to the largest exaggeration
HOLD = 25 # a workflow stage (2.5 s, long enough to read its caption and the browser)
HOLD_SHORT = 20 # one of the model definition steps
HOLD_LONG = 30 # the stress result
SWING = 15 # pictures from undeformed to the largest exaggeration, eased
MAX_SCALE = 150.0 # the largest displacement (0.18 mm) drawn about 26 mm on the 200 mm beam
# The results camera: the iso orientation (yaw -45, pitch 35.26 degrees), drawn back from the
# fitted distance (about 300 mm for this beam) so the contour legend over the viewport's corner
# leaves the beam clear. The closing orbit turns it round the beam, one picture per frame.
ISO = (-45.0, 35.26)
RESULTS_DISTANCE_MM = 420.0
ORBIT = 60
ORBIT_YAW = (ISO[0], -165.0)
ORBIT_PITCH = (ISO[1], 20.0)
# The captured window, in points (the app's own pixels-per-point scale on top); the video's size.
WINDOW = (1280, 800)
@@ -253,6 +262,11 @@ def against_beam_theory(result):
return result
def ease(i, n):
"""Step i of n of a cosine ease, 0 to 1: slow out, slow in."""
return (1 - math.cos(math.pi * i / n)) / 2
def batch(mcp, commands):
reply = mcp.call("batch", {"commands": commands})
if not reply.get("ok"):
@@ -311,25 +325,32 @@ def analyse(mcp, film, design, work):
log(f"solve: {solve.get('equations')} equations")
film.shoot(HOLD_SHORT)
# 5. Results: the tip deflection, then von Mises contours on the true (scale 1) shape.
# 5. Results: the tip deflection, then von Mises contours on the undeformed shape.
disp = mcp.call("show_results", {"step": STEP, "field": "displacement"})
tip = disp["contour"]["range"]["max"]["value"]
root_stress = root_bending_stress(mcp, work)
film.say("show_results", "von Mises")
vm = mcp.call("show_results", {"step": STEP, "field": "von_mises", "bands": 12, "deformed": True, "scale": 1})
vm = mcp.call("show_results", {"step": STEP, "field": "von_mises", "bands": 12, "deformed": True, "scale": 0})
peak = vm["contour"]["range"]["max"]["value"]
log(f"tip deflection {tip:.4f} mm, peak von Mises {peak:.1f} MPa")
film.shoot(HOLD_LONG, view="iso")
film.shoot(HOLD_LONG, camera={"yaw": ISO[0], "pitch": ISO[1], "distance": RESULTS_DISTANCE_MM})
# 6. The deformed shape, swung from the true shape to an exaggeration and back, in the iso
# view and then from the side, ending held at the exaggeration.
film.say("show_results", f"deformed, up to \u00d7{MAX_SCALE:g}")
ramp = [max(1.0, MAX_SCALE * i / SWING) for i in range(SWING + 1)]
for view, scales in (("iso", ramp[1:] + ramp[-2::-1]), ("front", ramp[1:])):
for i, scale in enumerate(scales):
mcp.call("show_results", {"step": STEP, "deformed": True, "scale": scale})
film.shoot(1, view=view if i == 0 else None)
film.shoot(HOLD_LONG)
# 6. The deformed shape, swung from undeformed to the exaggeration and back, then out again.
eased = [MAX_SCALE * ease(i, SWING) for i in range(1, SWING + 1)]
for scale in eased + eased[-2::-1] + [0.0] + eased:
film.say("show_results", f"deformed \u00d7{scale:.0f}")
mcp.call("show_results", {"step": STEP, "deformed": True, "scale": scale})
film.shoot(1)
film.hold(HOLD_SHORT)
# 7. A slow orbit round the deformed beam, one `screenshot` camera angle per frame.
film.say("screenshot", "camera orbit")
for i in range(1, ORBIT + 1):
t = ease(i, ORBIT)
yaw = ORBIT_YAW[0] + t * (ORBIT_YAW[1] - ORBIT_YAW[0])
pitch = ORBIT_PITCH[0] + t * (ORBIT_PITCH[1] - ORBIT_PITCH[0])
film.shoot(1, camera={"yaw": yaw, "pitch": pitch})
film.hold(HOLD)
return against_beam_theory({"tip_deflection_mm": tip, "root_stress_mpa": root_stress, "peak_von_mises_mpa": peak, "elements": mesh["mesh"]["elements"], "nodes": mesh["mesh"]["nodes"]})