forked from 1b-agent/fieldcraft-upstream
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm
This commit is contained in:
1 parent
30812250d3
commit
8a8ed75103
2 files changed
+44
-20
No files matched your search
@@ -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,
|
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
|
`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`
|
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`
|
(`static`), and `show_results` for the von Mises contours on the undeformed shape
|
||||||
stepped from 1 to 150 and back. Every picture is the MCP `screenshot` tool with
|
(`scale: 0`) and then the deformed shape, its `scale` swung (eased) from 0 to 150, back to 0
|
||||||
`source: window` (the whole window drawn offscreen, `ui.capture`), turned to the `iso` or
|
and out to 150 again. Every picture is the MCP `screenshot` tool with `source: window` (the
|
||||||
`front` view by its `view` argument.
|
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/…`,
|
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`
|
`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`,
|
draws the bars in the standard library: a minimal TrueType reader (`cmap` format 4, `loca`,
|
||||||
|
|||||||
+37
-16
@@ -12,6 +12,7 @@ Python standard library only; ffmpeg encodes the frames.
|
|||||||
import argparse
|
import argparse
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
|
import math
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
@@ -44,11 +45,19 @@ STEP = "Load"
|
|||||||
|
|
||||||
# Frames per second of the video; each stage holds its picture for a number of frames.
|
# Frames per second of the video; each stage holds its picture for a number of frames.
|
||||||
FPS = 10
|
FPS = 10
|
||||||
HOLD = 14 # a workflow stage (1.4 s)
|
HOLD = 25 # a workflow stage (2.5 s, long enough to read its caption and the browser)
|
||||||
HOLD_SHORT = 10 # one of the model definition steps
|
HOLD_SHORT = 20 # one of the model definition steps
|
||||||
HOLD_LONG = 20 # the stress result
|
HOLD_LONG = 30 # the stress result
|
||||||
SWING = 12 # pictures from undeformed to the largest exaggeration
|
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
|
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.
|
# The captured window, in points (the app's own pixels-per-point scale on top); the video's size.
|
||||||
WINDOW = (1280, 800)
|
WINDOW = (1280, 800)
|
||||||
@@ -253,6 +262,11 @@ def against_beam_theory(result):
|
|||||||
return 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):
|
def batch(mcp, commands):
|
||||||
reply = mcp.call("batch", {"commands": commands})
|
reply = mcp.call("batch", {"commands": commands})
|
||||||
if not reply.get("ok"):
|
if not reply.get("ok"):
|
||||||
@@ -311,25 +325,32 @@ def analyse(mcp, film, design, work):
|
|||||||
log(f"solve: {solve.get('equations')} equations")
|
log(f"solve: {solve.get('equations')} equations")
|
||||||
film.shoot(HOLD_SHORT)
|
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"})
|
disp = mcp.call("show_results", {"step": STEP, "field": "displacement"})
|
||||||
tip = disp["contour"]["range"]["max"]["value"]
|
tip = disp["contour"]["range"]["max"]["value"]
|
||||||
root_stress = root_bending_stress(mcp, work)
|
root_stress = root_bending_stress(mcp, work)
|
||||||
film.say("show_results", "von Mises")
|
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"]
|
peak = vm["contour"]["range"]["max"]["value"]
|
||||||
log(f"tip deflection {tip:.4f} mm, peak von Mises {peak:.1f} MPa")
|
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
|
# 6. The deformed shape, swung from undeformed to the exaggeration and back, then out again.
|
||||||
# view and then from the side, ending held at the exaggeration.
|
eased = [MAX_SCALE * ease(i, SWING) for i in range(1, SWING + 1)]
|
||||||
film.say("show_results", f"deformed, up to \u00d7{MAX_SCALE:g}")
|
for scale in eased + eased[-2::-1] + [0.0] + eased:
|
||||||
ramp = [max(1.0, MAX_SCALE * i / SWING) for i in range(SWING + 1)]
|
film.say("show_results", f"deformed \u00d7{scale:.0f}")
|
||||||
for view, scales in (("iso", ramp[1:] + ramp[-2::-1]), ("front", ramp[1:])):
|
mcp.call("show_results", {"step": STEP, "deformed": True, "scale": scale})
|
||||||
for i, scale in enumerate(scales):
|
film.shoot(1)
|
||||||
mcp.call("show_results", {"step": STEP, "deformed": True, "scale": scale})
|
film.hold(HOLD_SHORT)
|
||||||
film.shoot(1, view=view if i == 0 else None)
|
|
||||||
film.shoot(HOLD_LONG)
|
# 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"]})
|
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"]})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user