diff --git a/CHANGELOG.md b/CHANGELOG.md
index ce583c7..fb981c1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,29 @@ All notable changes to the **VS Code Aster** extension will be documented in thi
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [1.9.1] - 2026-04-23
+
+New viewer toolbar actions (auto-rotate, video recording), a reorganized settings popup with a dedicated Toolbar tab, and a round of `.export` editor fixes.
+
+### Added
+- **Auto-rotate** toolbar button with a right-click popover for session-only speed and reverse-direction; persistent defaults in `Settings → Toolbar`.
+- **Record** toolbar button (mp4/h264 when supported, else webm, saved to `.vs-code-aster/recordings/`). Right-click menu offers whole-webview or without-sidebar variants. Whole-webview rasterizes the DOM and may briefly freeze the viewer on UI changes.
+- **Settings → Toolbar tab** grouping each toolbar action with its persisted defaults.
+- `.export` base directory support (`R extension name D/DC/RC unit`).
+- `.export` `max_base`, `testlist`, `expected_diag` form fields.
+- `.export` split basename / extension inputs, smarter default unit, and preservation of unknown lines.
+
+### Changed
+- **Viewer settings** reorganized into `Rendering`, `Groups`, `Visibility`, `Toolbar` tabs.
+- **Dream background** on by default.
+- **Screenshot right-click** now opens a menu (was a direct full-webview capture).
+- **Canvas screenshots and recordings** crop out the sidebar region so the mesh is centered in the output.
+- **Export form error/warning panel** merged into one clickable list with per-field jumps.
+
+### Fixed
+- `.export` file name / extension inputs no longer silently revert due to a state-sync feedback loop.
+- Optional integer parameters no longer flagged as invalid when left empty.
+
## [1.9.0] - 2026-04-21
Volume and edge groups now appear in the viewer alongside face and node groups, a new "Groups" settings tab exposes per-kind display tweaks, the bundled vtk.js is replaced by the tree-shaken npm package, and an optional animated background adds a cosmetic touch.
diff --git a/CITATION.cff b/CITATION.cff
index e1c3832..72abd27 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,4 +1,4 @@
-cff-version: 1.9.0
+cff-version: 1.9.1
title: VS Code Aster
message: >-
If you use this software, please cite it using the
diff --git a/README.md b/README.md
index 14c261d..1a919c7 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
-
+
@@ -210,9 +210,12 @@ There are two ways to open the visualizer :
- Control the camera by rotating or panning it
- **Bounding box** : toggle a wireframe cube with colored axes (X red, Y green, Z blue), corner dots, and dimension labels to quickly read the characteristic size of the structure
- **Wireframe mode** : switch between solid surface and wireframe rendering to inspect mesh density
-- **Screenshot** : save the current 3D view as a PNG file next to your mesh and copy it to the clipboard
+- **Auto-rotate** : hands-free turntable that spins the camera around the current view-up; right-click the button to reveal a popover with a speed slider (5–180 °/s) and a reverse-direction toggle — changes there apply to the current view only, persistent defaults live in _Settings → Toolbar_
+- **Screenshot** : save the current 3D view as a PNG file next to your mesh and copy it to the clipboard; right-click for a menu to capture the whole webview (toolbar + sidebar baked in)
+- **Record** : capture a video of the view (mp4 when the runtime supports h264, webm otherwise), saved to `.vs-code-aster/recordings/`; left-click to start/stop, right-click for whole-webview or without-sidebar variants. The button pulses red with an elapsed-time indicator while recording
- **Per-kind settings** : Settings popup exposes edge-group line thickness, edge-group depth offset (to avoid z-fighting), node-group point size, and the sidebar sort order — plus a toggle to bucket groups by kind or mix them into a single list
-- **Dream background** : optional cosmetic setting that animates EDF orange and blue light blobs behind the mesh for a more vibrant workspace
+- **Remembered toolbar defaults** : every toolbar button (bounding box, wireframe, auto-rotate) is session-only by default — toggling it changes the current view only. Persistent defaults are set in _Settings → Toolbar_, grouped per feature (Bounding box, Wireframe, Auto-rotate with default speed and default direction)
+- **Dream background** : on by default, animates EDF orange and blue light blobs behind the mesh for a more vibrant workspace; can be disabled from _Settings → Rendering_
#### Usage tips
@@ -228,12 +231,17 @@ There are two ways to open the visualizer :
- Use the `Mouse wheel` to zoom in and out
- Click on the `X`, `Y`, and `Z` buttons at the bottom of the sidebar to quickly align the camera along an axis
- Toolbar :
- - The top toolbar provides quick access to the bounding box, wireframe, and screenshot features
- - Right-click the screenshot button to capture the full viewer including the sidebar
+ - The top toolbar provides quick access to the bounding box, wireframe, auto-rotate, screenshot, and record features
+ - Left-click toggles a feature for the current view only; remembered defaults live in _Settings → Toolbar_
+ - Right-click the **auto-rotate** button for a session-only speed slider and reverse-direction toggle
+ - Right-click the **screenshot** button for a menu to capture the whole webview (sidebar included)
+ - Right-click the **record** button for options: whole webview, or without the sidebar
+- Settings tabs : _Rendering_ (mesh-edge mode, orientation widget, dream background), _Groups_ (per-kind display), _Visibility_ (ghosted objects, highlight transparency), _Toolbar_ (default state for toolbar buttons)
- File management :
- Files generated by the extension are stored in a hidden `.vs-code-aster/` folder next to your project files:
- `mesh_cache/` — converted `.obj` files from your `.*med` meshes, reused on subsequent opens
- `screenshots/` — PNGs saved from the viewer's screenshot button
+ - `recordings/` — video files saved from the viewer's record button (mp4 / webm)
- `run_logs/` — one timestamped log per code_aster run (oldest pruned, see `vs-code-aster.maxRunLogs`)
## Troubleshooting
diff --git a/ROADMAP.md b/ROADMAP.md
index 6848a45..511e309 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -4,7 +4,7 @@
The extension aims to reduce friction between modeling, validation, execution, and analysis by bringing **code_aster** native workflows into the editor.
-## Current Capabilities (v1.9.0)
+## Current Capabilities (v1.9.1)
- `.export` file generator
- 3D mesh viewer
diff --git a/package-lock.json b/package-lock.json
index e017f8c..aa905ef 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "vs-code-aster",
- "version": "1.9.0",
+ "version": "1.9.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vs-code-aster",
- "version": "1.9.0",
+ "version": "1.9.1",
"license": "GPL-3.0",
"dependencies": {
"@kitware/vtk.js": "^35.10.0",
diff --git a/package.json b/package.json
index 20521d6..bb48e94 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vs-code-aster",
"displayName": "VS Code Aster",
- "version": "1.9.0",
+ "version": "1.9.1",
"description": "VS Code extension for code_aster",
"publisher": "simvia",
"license": "GPL-3.0",
@@ -362,9 +362,29 @@
"vs-code-aster.viewer.dreamBackground": {
"order": 22,
"type": "boolean",
- "default": false,
+ "default": true,
"markdownDescription": "Cosmetic animated EDF orange and blue light blobs slowly breathing behind the mesh. Purely decorative — does not affect mesh lighting."
},
+ "vs-code-aster.viewer.autoRotate": {
+ "order": 23,
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Automatically rotate the view around the current vertical axis like a turntable."
+ },
+ "vs-code-aster.viewer.autoRotateSpeed": {
+ "order": 24,
+ "type": "number",
+ "default": 15,
+ "minimum": 5,
+ "maximum": 180,
+ "markdownDescription": "Default auto-rotation speed in degrees per second (between `5` and `180`). Transient overrides from the toolbar popover are not persisted."
+ },
+ "vs-code-aster.viewer.autoRotateReverse": {
+ "order": 25,
+ "type": "boolean",
+ "default": false,
+ "markdownDescription": "Default auto-rotation direction. When enabled, the view rotates the opposite way by default. Transient overrides from the toolbar popover are not persisted."
+ },
"vs-code-aster.enableTelemetry": {
"order": 100,
"type": "boolean",
diff --git a/src/WebviewVisu.ts b/src/WebviewVisu.ts
index b522294..435ca00 100644
--- a/src/WebviewVisu.ts
+++ b/src/WebviewVisu.ts
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
-import { getScreenshotsDir } from './projectPaths';
+import { getScreenshotsDir, getRecordingsDir } from './projectPaths';
/**
* Provides basic dialog semantics over a VS Code webview panel for mesh visualization.
@@ -130,6 +130,9 @@ export class WebviewVisu implements vscode.Disposable {
'showBoundingBox',
'showWireframe',
'dreamBackground',
+ 'autoRotate',
+ 'autoRotateSpeed',
+ 'autoRotateReverse',
];
for (const key of settingKeys) {
if (e.settings[key] !== undefined) {
@@ -147,6 +150,18 @@ export class WebviewVisu implements vscode.Disposable {
}
break;
}
+ case 'saveRecording': {
+ if (this.sourceDir) {
+ const dataUrl = e.dataUrl as string;
+ const commaIdx = dataUrl.indexOf(';base64,');
+ const base64 = commaIdx >= 0 ? dataUrl.slice(commaIdx + ';base64,'.length) : dataUrl;
+ const buffer = Buffer.from(base64, 'base64');
+ const recordingsDir = getRecordingsDir(this.sourceDir);
+ const filePath = path.join(recordingsDir, e.filename as string);
+ fs.writeFileSync(filePath, buffer);
+ }
+ break;
+ }
case 'debugPanel':
// Log debug messages from the webview
console.log('[WebviewVisu] Message received from webview:', e.text);
@@ -207,7 +222,10 @@ export class WebviewVisu implements vscode.Disposable {
showOrientationWidget: config.get('viewer.showOrientationWidget', true),
showBoundingBox: config.get('viewer.showBoundingBox', false),
showWireframe: config.get('viewer.showWireframe', false),
- dreamBackground: config.get('viewer.dreamBackground', false),
+ dreamBackground: config.get('viewer.dreamBackground', true),
+ autoRotate: config.get('viewer.autoRotate', false),
+ autoRotateSpeed: config.get('viewer.autoRotateSpeed', 15),
+ autoRotateReverse: config.get('viewer.autoRotateReverse', false),
};
this.panel.webview.postMessage({
type: 'init',
diff --git a/src/projectPaths.ts b/src/projectPaths.ts
index d1414ee..e389faf 100644
--- a/src/projectPaths.ts
+++ b/src/projectPaths.ts
@@ -31,6 +31,10 @@ export function getScreenshotsDir(projectDir: string): string {
return ensureDir(path.join(getProjectDir(projectDir), 'screenshots'));
}
+export function getRecordingsDir(projectDir: string): string {
+ return ensureDir(path.join(getProjectDir(projectDir), 'recordings'));
+}
+
export function getRunLogsDir(projectDir: string): string {
const dir = ensureDir(path.join(getProjectDir(projectDir), 'run_logs'));
migrateLegacyRunLog(projectDir, dir);
diff --git a/webviews/viewer/src/components/layout/TopToolbar.svelte b/webviews/viewer/src/components/layout/TopToolbar.svelte
index 9ef07eb..8c67532 100644
--- a/webviews/viewer/src/components/layout/TopToolbar.svelte
+++ b/webviews/viewer/src/components/layout/TopToolbar.svelte
@@ -1,7 +1,9 @@
+
+
+
+
diff --git a/webviews/viewer/src/components/popups/HelpPopup.svelte b/webviews/viewer/src/components/popups/HelpPopup.svelte
index 8fa5389..94fd7ad 100644
--- a/webviews/viewer/src/components/popups/HelpPopup.svelte
+++ b/webviews/viewer/src/components/popups/HelpPopup.svelte
@@ -14,6 +14,8 @@
import ObjectIcon from '../../icons/ObjectIcon.svelte';
import MouseScrollIcon from '../../icons/MouseScrollIcon.svelte';
import ResetIcon from '../../icons/ResetIcon.svelte';
+ import AutoRotateIcon from '../../icons/AutoRotateIcon.svelte';
+ import RecordIcon from '../../icons/RecordIcon.svelte';
import ScreenshotIcon from '../../icons/ScreenshotIcon.svelte';
import VolumeIcon from '../../icons/VolumeIcon.svelte';
import WireframeIcon from '../../icons/WireframeIcon.svelte';
@@ -132,15 +134,50 @@
inspect mesh density
+
+
+
+ Auto-rotate the view like a turntable.
+ Right click to open a popover with a speed slider and a
+ reverse direction toggle — changes there apply to the current session
+ only; set the remembered defaults in Settings → Toolbar.
+
Left click — save the 3D view as PNG next to your file & copy to
- clipboard. Right click — capture the full viewer including the sidebar.Screenshot the 3D view as PNG (saved next to your mesh & copied to
+ clipboard). Left click — canvas only. Right click —
+ menu with Screenshot whole screen to include the toolbar and sidebar.
+
+
+
+
+ Record a video of the 3D view (mp4 when the webview's Chromium
+ supports h264, webm otherwise; saved to .vs-code-aster/recordings/). The
+ button pulses red with an elapsed timer while recording. Left click —
+ start / stop a canvas-only recording. Right click — menu with
+ Record whole webview (bakes the toolbar, sidebar, popups, and labels into the
+ video) or Record without sidebar. The whole-webview mode rasterizes the DOM on
+ every real UI change, which can briefly freeze the viewer on each update — expect short
+ hitches when you click a toolbar button or toggle a group during recording. Skip the
+ sidebar if you have hundreds of groups and the hitches feel too long.
+
+
+ Toolbar toggles (bounding box, wireframe, auto-rotate) affect only the current viewer —
+ persistent defaults live in Settings → Toolbar.
+
{:else if activeTab === 'Objects'}
Controls the wireframe edges drawn on every cell of the mesh. For the display of edge
@@ -392,6 +502,44 @@
>
{/if}
+
+
+
+
+
+ Orientation widget
+ {@render tip(
+ 'Toggle the XYZ axes indicator in the bottom-right corner of the viewport.'
+ )}
+
+ Show the axes widget in the bottom-right corner.
+
+
+
+
+
+
+
+ Dream background
+ {@render tip(
+ 'Cosmetic only: animated EDF orange and blue light blobs slowly breathing behind the mesh. Does not affect lighting on the mesh itself.'
+ )}
+
+ Animated colored glows behind the mesh, purely decorative.
+
+
{:else if activeTab === 'Groups'}
@@ -564,43 +712,119 @@
>
- {:else if activeTab === 'Display'}
-
-
-
-
-
- Orientation widget
- {@render tip(
- 'Toggle the XYZ axes indicator in the bottom-right corner of the viewport.'
- )}
+ {:else if activeTab === 'Toolbar'}
+
+
+ Defaults for the toolbar at the top of the viewport. Toggling a toolbar button directly
+ only affects the current view — only changes made here are remembered.
+
+
+
+ Bounding box
+
+
+
+
+ Enable bounding box
+ {@render tip(
+ 'Show the bounding box by default on every viewer. Toolbar overrides are not persisted.'
+ )}
+
+ Show the axis-aligned bounding box around meshes on load.
- Show the axes widget in the bottom-right corner.
-
-
-
-
- Dream background
- {@render tip(
- 'Cosmetic only: animated EDF orange and blue light blobs slowly breathing behind the mesh. Does not affect lighting on the mesh itself.'
- )}
+
+ Wireframe
+
+
+
+
+ Enable wireframe
+ {@render tip(
+ 'Render meshes as wireframes by default. Toolbar overrides are not persisted.'
+ )}
+
+ Render meshes in wireframe mode on load.
+
+
+
+
+
+ Auto-rotate
+
+
+
+
+ Enable auto-rotate
+ {@render tip(
+ 'Start auto-rotate by default on every viewer. Toolbar overrides are not persisted.'
+ )}
+
+ Spin the camera around the scene on load.
+
+
+
+
+
+
+
+ {@render tip(
+ 'Default rotation speed in degrees per second. Overrides set from the toolbar popover are not persisted.'
+ )}
+
+ {$settings.autoRotateSpeed}°/s
+
+
+
+
+
+
+
+
+ Default reverse auto-rotate
+ {@render tip(
+ 'Default rotation direction. When enabled, the view rotates the opposite way by default. Overrides set from the toolbar popover are not persisted.'
+ )}
+
+ Rotate the opposite way by default.
- Animated colored glows behind the mesh, purely decorative.