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 @@

Simvia Logo

- Version + Version License CI Status GitHub issues 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(); }