diff --git a/CHANGELOG.md b/CHANGELOG.md
index fb981c1..47b0896 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ 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.2] - 2026-04-23
+
+Better rendering for 1D meshes and a flatter, more readable face shading.
+
+### Added
+- **Standalone 1D edges** (edges not shared with any face, e.g. beam elements) now render by default in the object color with a thin black contour. Line style adapts to parent opacity, wireframe mode, and the face-edge visibility rules (hide / show / threshold / gradual).
+
+### Fixed
+- User-defined face groups whose name contains `all_` (e.g. `all_plates`) are no longer mistakenly treated as file-level object actors.
+- Face specular highlight removed, so colors no longer shift toward white (e.g. blue → turquoise) at camera-facing angles.
+
## [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.
diff --git a/CITATION.cff b/CITATION.cff
index 72abd27..db09f96 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -1,4 +1,4 @@
-cff-version: 1.9.1
+cff-version: 1.9.2
title: VS Code Aster
message: >-
If you use this software, please cite it using the
diff --git a/README.md b/README.md
index 1a919c7..18ca399 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@

-
+
diff --git a/ROADMAP.md b/ROADMAP.md
index 511e309..a66e2f0 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.1)
+## Current Capabilities (v1.9.2)
- `.export` file generator
- 3D mesh viewer
diff --git a/package-lock.json b/package-lock.json
index aa905ef..20634c0 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "vs-code-aster",
- "version": "1.9.1",
+ "version": "1.9.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "vs-code-aster",
- "version": "1.9.1",
+ "version": "1.9.2",
"license": "GPL-3.0",
"dependencies": {
"@kitware/vtk.js": "^35.10.0",
diff --git a/package.json b/package.json
index bb48e94..8a429cd 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "vs-code-aster",
"displayName": "VS Code Aster",
- "version": "1.9.1",
+ "version": "1.9.2",
"description": "VS Code extension for code_aster",
"publisher": "simvia",
"license": "GPL-3.0",
diff --git a/webviews/viewer/src/lib/commands/VisibilityManager.ts b/webviews/viewer/src/lib/commands/VisibilityManager.ts
index f3dceb4..dc0d5ea 100644
--- a/webviews/viewer/src/lib/commands/VisibilityManager.ts
+++ b/webviews/viewer/src/lib/commands/VisibilityManager.ts
@@ -118,16 +118,16 @@ export class VisibilityManager {
const fileGroup = this.groups[object];
if (fileGroup) {
if (nowVisible) {
- fileGroup.actor.setVisibility(true);
+ fileGroup.setVisibility(true);
const opacity =
this.visibleGroupsByObject[object] > 0 ? GlobalSettings.Instance.groupTransparency : 1.0;
fileGroup.setOpacity(opacity);
} else {
const hiddenOpacity = GlobalSettings.Instance.hiddenObjectOpacity;
if (hiddenOpacity === 0) {
- fileGroup.actor.setVisibility(false);
+ fileGroup.setVisibility(false);
} else {
- fileGroup.actor.setVisibility(true);
+ fileGroup.setVisibility(true);
fileGroup.setOpacity(hiddenOpacity);
}
}
@@ -165,8 +165,9 @@ export class VisibilityManager {
applyEdgeGroupThickness(): void {
const thickness = GlobalSettings.Instance.edgeGroupThickness;
for (const group of Object.values(this.groups)) {
- if (group.kind !== 'edge') continue;
- group.actor.getProperty().setLineWidth(thickness);
+ if (group.kind === 'edge') {
+ group.actor.getProperty().setLineWidth(thickness);
+ }
}
VtkApp.Instance.getRenderWindow().render();
}
@@ -174,8 +175,9 @@ export class VisibilityManager {
applyEdgeGroupDepthOffset(): void {
const enabled = GlobalSettings.Instance.edgeGroupDepthOffset;
for (const group of Object.values(this.groups)) {
- if (group.kind !== 'edge') continue;
- EdgeActorCreator.applyDepthOffset(group.actor.getMapper(), enabled);
+ if (group.kind === 'edge') {
+ EdgeActorCreator.applyDepthOffset(group.actor.getMapper(), enabled);
+ }
}
VtkApp.Instance.getRenderWindow().render();
}
@@ -213,9 +215,9 @@ export class VisibilityManager {
const fileGroup = this.groups[object];
if (!fileGroup) continue;
if (hiddenOpacity === 0) {
- fileGroup.actor.setVisibility(false);
+ fileGroup.setVisibility(false);
} else {
- fileGroup.actor.setVisibility(true);
+ fileGroup.setVisibility(true);
fileGroup.setOpacity(hiddenOpacity);
}
}
diff --git a/webviews/viewer/src/lib/data/CreateGroups.ts b/webviews/viewer/src/lib/data/CreateGroups.ts
index 3c823bd..d4dc0b4 100644
--- a/webviews/viewer/src/lib/data/CreateGroups.ts
+++ b/webviews/viewer/src/lib/data/CreateGroups.ts
@@ -53,6 +53,31 @@ export class CreateGroups {
const nodeActorCreator = new NodeActorCreator(vertices, nodes, nodeIndexToGroup);
const edgeActorCreator = new EdgeActorCreator(vertices, edges, edgeIndexToGroup);
+ const edgeKey = (a: number, b: number) => (a < b ? `${a}-${b}` : `${b}-${a}`);
+ const faceEdgeSet = new Set();
+ for (const cell of cells) {
+ for (let i = 0; i < cell.length; i++) {
+ faceEdgeSet.add(edgeKey(cell[i], cell[(i + 1) % cell.length]));
+ }
+ }
+ const standaloneByFile: Record = {};
+ for (let i = 0; i < edges.length; i++) {
+ const e = edges[i];
+ let isStandalone = false;
+ for (let j = 0; j + 1 < e.length; j++) {
+ if (!faceEdgeSet.has(edgeKey(e[j], e[j + 1]))) {
+ isStandalone = true;
+ break;
+ }
+ }
+ if (!isStandalone) continue;
+ const egId = edgeIndexToGroup[i];
+ if (egId < 0) continue;
+ const fileGroup = edgeGroups[egId]?.split('::')[0];
+ if (!fileGroup) continue;
+ (standaloneByFile[fileGroup] ||= []).push(i);
+ }
+
const groupKeys = Object.keys(groupHierarchy);
const yield_ = () => new Promise((r) => setTimeout(r, 0));
@@ -70,7 +95,7 @@ export class CreateGroups {
colorIndex: fileColorIndex,
isObjectActor: fileIsObj,
cellCount: fileCellCount,
- } = faceActorCreator.create(fileGroup, groupId);
+ } = faceActorCreator.create(fileGroup, groupId, true);
const groupInstance = new Group(
actor,
@@ -84,6 +109,15 @@ export class CreateGroups {
);
this.groups[fileGroup] = groupInstance;
+ const standaloneIdx = standaloneByFile[fileGroup];
+ if (standaloneIdx && standaloneIdx.length > 0) {
+ const result = edgeActorCreator.createStandalone(standaloneIdx, objColor);
+ if (result) {
+ groupInstance.standaloneEdgesActor = result.actor;
+ groupInstance.standaloneEdgesContourActor = result.contourActor;
+ }
+ }
+
const size = this.computeSize(actor);
for (const volumeGroup of groupHierarchy[fileGroup].volumes) {
diff --git a/webviews/viewer/src/lib/data/Group.ts b/webviews/viewer/src/lib/data/Group.ts
index 79292aa..d0c1cac 100644
--- a/webviews/viewer/src/lib/data/Group.ts
+++ b/webviews/viewer/src/lib/data/Group.ts
@@ -11,7 +11,14 @@ export class Group {
colorIndex: number | null;
isObjectActor: boolean;
cellCount: number | null;
+ standaloneEdgesActor: any = null;
+ standaloneEdgesContourActor: any = null;
private _edgeT?: number;
+ private _visible = true;
+ private _opacity = 1;
+ private _wireframe = false;
+ private _edgeVisible = true;
+ private _edgeFade = 1;
constructor(
actor: any,
@@ -44,6 +51,9 @@ export class Group {
: GlobalSettings.Instance.meshGroupColors;
const color = colors[this.colorIndex % colors.length];
this.actor.getProperty().setColor(color);
+ if (this.standaloneEdgesActor) {
+ this.standaloneEdgesActor.getProperty().setColor(color[0], color[1], color[2]);
+ }
this._applyEdgeColor();
}
@@ -54,11 +64,17 @@ export class Group {
if (mode === 'hide') {
prop.setEdgeVisibility(false);
+ this._edgeVisible = false;
+ this._edgeFade = 0;
+ this._updateStandaloneEdges();
return;
}
if (mode === 'show') {
prop.setEdgeVisibility(true);
this._applyFlatEdgeColor(prop);
+ this._edgeVisible = true;
+ this._edgeFade = 1;
+ this._updateStandaloneEdges();
return;
}
@@ -68,14 +84,21 @@ export class Group {
GlobalSettings.Instance.edgeThresholdMultiplier;
if (mode === 'threshold') {
- prop.setEdgeVisibility(currentDistance < threshold);
+ const visible = currentDistance < threshold;
+ prop.setEdgeVisibility(visible);
this._applyFlatEdgeColor(prop);
+ this._edgeVisible = visible;
+ this._edgeFade = visible ? 1 : 0;
+ this._updateStandaloneEdges();
return;
}
prop.setEdgeVisibility(true);
this._edgeT = Math.min(1, Math.max(0, threshold / currentDistance));
this._applyEdgeColor();
+ this._edgeVisible = true;
+ this._edgeFade = this._edgeT;
+ this._updateStandaloneEdges();
}
private _applyFlatEdgeColor(prop: any): void {
@@ -103,10 +126,34 @@ export class Group {
}
setVisibility(visible: boolean): void {
+ this._visible = visible;
this.actor.setVisibility(visible);
+ this._updateStandaloneEdges();
}
setOpacity(opacity: number): void {
+ this._opacity = opacity;
this.actor.getProperty().setOpacity(opacity);
+ this._updateStandaloneEdges();
+ }
+
+ setWireframeMode(wireframe: boolean): void {
+ this._wireframe = wireframe;
+ this._updateStandaloneEdges();
+ }
+
+ private _updateStandaloneEdges(): void {
+ if (!this.standaloneEdgesActor) return;
+ const transparent = this._opacity < 1;
+ const thin = transparent || this._wireframe;
+ const lineOpacity = transparent ? this._opacity * 0.4 : this._opacity;
+ this.standaloneEdgesActor.setVisibility(this._visible);
+ this.standaloneEdgesActor.getProperty().setOpacity(lineOpacity);
+ this.standaloneEdgesActor.getProperty().setLineWidth(thin ? 1 : 2);
+ if (this.standaloneEdgesContourActor) {
+ const contourVisible = this._visible && !thin && this._edgeVisible;
+ this.standaloneEdgesContourActor.setVisibility(contourVisible);
+ this.standaloneEdgesContourActor.getProperty().setOpacity(lineOpacity * this._edgeFade);
+ }
}
}
diff --git a/webviews/viewer/src/lib/data/create/EdgeActorCreator.ts b/webviews/viewer/src/lib/data/create/EdgeActorCreator.ts
index 531d2d1..42e3fcb 100644
--- a/webviews/viewer/src/lib/data/create/EdgeActorCreator.ts
+++ b/webviews/viewer/src/lib/data/create/EdgeActorCreator.ts
@@ -37,6 +37,61 @@ export class EdgeActorCreator {
return { actor, colorIndex, cellCount };
}
+ createStandalone(
+ edgeIndices: number[],
+ color: number[]
+ ): { actor: any; contourActor: any; cellCount: number } | null {
+ if (edgeIndices.length === 0) return null;
+
+ const pd = vtkPolyData.newInstance();
+ const pts = vtkPoints.newInstance();
+ const coords = new Float32Array(this.vertices.length * 3);
+ this.vertices.forEach((v, i) => {
+ coords[3 * i] = v.x;
+ coords[3 * i + 1] = v.y;
+ coords[3 * i + 2] = v.z;
+ });
+ pts.setData(coords, 3);
+ pd.setPoints(pts);
+
+ const lineArray = vtkCellArray.newInstance({
+ values: Uint32Array.from(
+ edgeIndices.flatMap((i) => {
+ const e = this.edges[i];
+ return [e.length, ...e];
+ })
+ ),
+ });
+ pd.setLines(lineArray);
+
+ const contourMapper = vtkMapper.newInstance();
+ contourMapper.setInputData(pd);
+ contourMapper.setResolveCoincidentTopologyToPolygonOffset();
+ contourMapper.setRelativeCoincidentTopologyLineOffsetParameters(2, 2);
+ const contourActor = vtkActor.newInstance();
+ contourActor.setMapper(contourMapper);
+ const contourProp = contourActor.getProperty();
+ contourProp.setColor(0, 0, 0);
+ contourProp.setLineWidth(4);
+ contourProp.setLighting(false);
+ VtkApp.Instance.getRenderer().addActor(contourActor);
+
+ const mapper = vtkMapper.newInstance();
+ mapper.setInputData(pd);
+ EdgeActorCreator.applyDepthOffset(mapper, false);
+
+ const actor = vtkActor.newInstance();
+ actor.setMapper(mapper);
+
+ const prop = actor.getProperty();
+ prop.setColor(color[0], color[1], color[2]);
+ prop.setLineWidth(2);
+ prop.setLighting(false);
+
+ VtkApp.Instance.getRenderer().addActor(actor);
+ return { actor, contourActor, cellCount: edgeIndices.length };
+ }
+
static applyDepthOffset(mapper: any, enabled: boolean): void {
if (enabled) {
mapper.setResolveCoincidentTopologyToPolygonOffset();
diff --git a/webviews/viewer/src/lib/data/create/FaceActorCreator.ts b/webviews/viewer/src/lib/data/create/FaceActorCreator.ts
index c092d68..8dd1462 100644
--- a/webviews/viewer/src/lib/data/create/FaceActorCreator.ts
+++ b/webviews/viewer/src/lib/data/create/FaceActorCreator.ts
@@ -22,8 +22,9 @@ export class FaceActorCreator {
}
create(
- groupName: string,
- groupId: number
+ _groupName: string,
+ groupId: number,
+ isObject = false
): { actor: any; colorIndex: number; isObjectActor: boolean; cellCount: number } {
const { polyData, cellCount } = this.prepare(groupId);
@@ -31,13 +32,13 @@ export class FaceActorCreator {
const mapper = vtkMapper.newInstance();
mapper.setInputData(polyData);
- if (!groupName.includes('all_')) {
+ if (!isObject) {
mapper.setResolveCoincidentTopologyToPolygonOffset();
mapper.setRelativeCoincidentTopologyPolygonOffsetParameters(-2, -2);
}
actor.setMapper(mapper);
- const { colorIndex, isObjectActor } = this.setProperty(actor, groupName, cellCount);
+ const { colorIndex, isObjectActor } = this.setProperty(actor, isObject, cellCount);
VtkApp.Instance.getRenderer().addActor(actor);
return { actor, colorIndex, isObjectActor, cellCount };
@@ -78,14 +79,14 @@ export class FaceActorCreator {
private setProperty(
actor: any,
- groupName: string,
+ isObject: boolean,
_cellCount: number
): { colorIndex: number; isObjectActor: boolean } {
const prop = actor.getProperty();
let colorIndex: number;
let isObjectActor: boolean;
- if (groupName.includes('all_')) {
+ if (isObject) {
isObjectActor = true;
colorIndex = GlobalSettings.Instance.objIndex;
prop.setColor(GlobalSettings.Instance.getColorForObject());
@@ -103,8 +104,7 @@ export class FaceActorCreator {
prop.setLineWidth(0.3);
prop.setInterpolationToPhong();
prop.setAmbient(GlobalSettings.Instance.ambientIntensity);
- prop.setSpecular(GlobalSettings.Instance.specular);
- prop.setSpecularPower(GlobalSettings.Instance.specularPower);
+ prop.setSpecular(0);
return { colorIndex, isObjectActor };
}
diff --git a/webviews/viewer/src/lib/interaction/CameraManager.ts b/webviews/viewer/src/lib/interaction/CameraManager.ts
index f26a519..c4c7feb 100644
--- a/webviews/viewer/src/lib/interaction/CameraManager.ts
+++ b/webviews/viewer/src/lib/interaction/CameraManager.ts
@@ -215,6 +215,7 @@ export class CameraManager {
const rep = wireframe ? 1 : 2;
for (const group of Object.values(this.faceGroups)) {
group.actor.getProperty().setRepresentation(rep);
+ group.setWireframeMode(wireframe);
}
VtkApp.Instance.getRenderWindow().render();
}