Skip to content

Merge dev branch into main for release#19

Open
deslaughter wants to merge 19 commits intomainfrom
dev
Open

Merge dev branch into main for release#19
deslaughter wants to merge 19 commits intomainfrom
dev

Conversation

@deslaughter
Copy link
Collaborator

No description provided.

@deslaughter deslaughter requested a review from Copilot February 10, 2026 18:36
@deslaughter deslaughter self-assigned this Feb 10, 2026
@deslaughter deslaughter added the enhancement New feature or request label Feb 10, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request merges the dev branch into main for a release. The changes include significant enhancements to VTK file processing with blade root coordinate transformations, additions to the MBC (Multi-Blade Coordinate) transformation workflow, comprehensive theory documentation, orientation visualization features, and various dependency updates.

Changes:

  • Enhanced VTK processing: Added blade root orientation extraction and global-to-local coordinate conversion with orientation tracking
  • MBC workflow improvements: Added storage of non-rotating A matrices for each operating point and average A matrix export
  • Comprehensive theory documentation: Added detailed explanations of Campbell diagram generation workflow, MBC transformation, eigenanalysis, and mode tracking algorithms including MAC/MACXP metrics and spectral clustering
  • Frontend enhancements: Added 3D node orientation visualization with controls, improved UI conditional rendering, and better chart tick formatting
  • File structure updates: New BeamDynBlade file type and additional DOF fields for platform motion (PtfmSgDOF, PtfmSwDOF, etc.)

Reviewed changes

Copilot reviewed 19 out of 27 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
viz/vtk.go Added GetBladeRootOrientation function, Global2Local transformation, and blade-specific coordinate conversion logic
viz/vtk_test.go Updated test to handle LoadVTK now returning two VTKFile pointers plus error
viz/viz.go Extended Point struct with orientation vectors and added orientation data parsing in ParseModeData
lin/mbc.go Added ANRs field to store all non-rotating A matrices per operating point
lin/mat_data.go Changed blade number iteration to hardcoded [1,2,3] array
lin/linfile.go Increased scanner buffer size from 64KB to 1MB for handling larger files
results.go Added CSV export for ANRs matrices and AvgA matrix
fio.go Added BeamDynBlade file type and new fields (OrderElem, DampType, platform DOFs)
frontend/wailsjs/runtime/runtime.js Added EventsOffAll export for Wails runtime
frontend/wailsjs/go/models.ts Generated TypeScript models for new Go struct fields
frontend/src/components/Results.vue Added showNodeOrientation toggle and improved chart tick formatting
frontend/src/components/Model.vue Fixed conditional rendering to check for undefined instead of falsy values
frontend/src/components/ModeViz.vue Implemented 3D orientation vector visualization with X/Y axes (Z commented out)
docs/content/docs/theory/index.md Complete theory documentation covering workflow, MBC, eigenanalysis, mode tracking, MAC/MACXP, assignment problem, and spectral clustering
docs/hugo.toml Added MathJax configuration for mathematical notation
docs/themes/hugo-whisper-theme/layouts/ Added math.html partial for MathJax rendering
go.mod Updated wails to v2.11.0, added go-chart v2.1.2, and bumped various dependencies
go.sum Corresponding checksum updates for dependency changes
.github/workflows/build-packages.yml Changed Go version to 1.25.3, removed go version debug step, formatting cleanup

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


// Lookup descriptions in map with all blade numbers
for j := 1; j <= numBlades; j++ {
for _, j := range []int{1, 2, 3} {
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop now hardcodes blade numbers as [1, 2, 3] instead of using the numBlades parameter. This breaks functionality for turbines with a different number of blades (e.g., 2-bladed turbines). The original code for j := 1; j <= numBlades; j++ was correct and should be restored.

Suggested change
for _, j := range []int{1, 2, 3} {
for j := 1; j <= numBlades; j++ {

Copilot uses AI. Check for mistakes.
}

// Write AvgA data to file
lin.ToCSV(linOP.MBC.AvgA, linOP.RootPath+"_avg_a.csv", "%g")
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for lin.ToCSV. Unlike the loop above which checks and returns errors from ToCSV calls, this call ignores the returned error. If writing AvgA data fails, the function will continue silently without reporting the issue.

Copilot uses AI. Check for mistakes.
Comment on lines 162 to +169
ov.X = da.MatrixF32
} else if da.Name == "OrientationY" {
copy(om.X[:], da.MatrixF32[0])
case "orientationy":
ov.Y = da.MatrixF32
} else if da.Name == "OrientationZ" {
copy(om.Y[:], da.MatrixF32[0])
case "orientationz":
ov.Z = da.MatrixF32
copy(om.Z[:], da.MatrixF32[0])
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential index out of bounds error. The code assumes da.MatrixF32[0] exists when copying to om.X[:], om.Y[:], and om.Z[:]. If MatrixF32 is empty or nil, this will panic. Add validation to check that da.MatrixF32 has at least one element before accessing da.MatrixF32[0].

Copilot uses AI. Check for mistakes.
Comment on lines +317 to +325
if orientationIndices[0] > -1 {
copy(component.Line[j].OrientationX[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[0]].MatrixF32[c])
}
if orientationIndices[1] > -1 {
copy(component.Line[j].OrientationY[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[1]].MatrixF32[c])
}
if orientationIndices[2] > -1 {
copy(component.Line[j].OrientationZ[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[2]].MatrixF32[c])
}
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential index out of bounds error. The code accesses vtk.PolyData.Piece.PointData.DataArray[orientationIndices[0]].MatrixF32[c] without validating that the MatrixF32 slice has enough elements. If c is out of bounds for MatrixF32, this will panic. Consider adding bounds checking before accessing these arrays.

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +96
const indices = new Uint16Array(c.Line.map((_, i) => i * 2).flatMap(i => [i, i + 1]));

const pointsX = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationX[0] * 4, p.XYZ[1] + p.OrientationX[1] * 4, p.XYZ[2] + p.OrientationX[2] * 4]));
const geometryX = new THREE.BufferGeometry();
geometryX.setAttribute('position', new THREE.BufferAttribute(pointsX, 3));
geometryX.setIndex(new THREE.BufferAttribute(indices, 1));
const lineX = new THREE.LineSegments(geometryX, xMaterial);
orientationFrameGroup.add(lineX);

const pointsY = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationY[0] * 4, p.XYZ[1] + p.OrientationY[1] * 4, p.XYZ[2] + p.OrientationY[2] * 4]));
const geometryY = new THREE.BufferGeometry();
geometryY.setAttribute('position', new THREE.BufferAttribute(pointsY, 3));
geometryY.setIndex(new THREE.BufferAttribute(indices, 1));
const lineY = new THREE.LineSegments(geometryY, yMaterial);
orientationFrameGroup.add(lineY);

Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential runtime error if OrientationX, OrientationY, or OrientationZ arrays are missing or have fewer than 3 elements. The code accesses p.OrientationX[0], p.OrientationX[1], p.OrientationX[2] (and similarly for Y) without validation. Since orientation data is optional (based on the conditional checks in viz.go lines 317-325), add validation to ensure these arrays exist and have at least 3 elements before accessing them, or handle the case where they might be undefined.

Suggested change
const indices = new Uint16Array(c.Line.map((_, i) => i * 2).flatMap(i => [i, i + 1]));
const pointsX = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationX[0] * 4, p.XYZ[1] + p.OrientationX[1] * 4, p.XYZ[2] + p.OrientationX[2] * 4]));
const geometryX = new THREE.BufferGeometry();
geometryX.setAttribute('position', new THREE.BufferAttribute(pointsX, 3));
geometryX.setIndex(new THREE.BufferAttribute(indices, 1));
const lineX = new THREE.LineSegments(geometryX, xMaterial);
orientationFrameGroup.add(lineX);
const pointsY = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationY[0] * 4, p.XYZ[1] + p.OrientationY[1] * 4, p.XYZ[2] + p.OrientationY[2] * 4]));
const geometryY = new THREE.BufferGeometry();
geometryY.setAttribute('position', new THREE.BufferAttribute(pointsY, 3));
geometryY.setIndex(new THREE.BufferAttribute(indices, 1));
const lineY = new THREE.LineSegments(geometryY, yMaterial);
orientationFrameGroup.add(lineY);
// Build X-orientation lines only for points with valid OrientationX data
const linePointsX = c.Line.filter(p => p.OrientationX && p.OrientationX.length >= 3);
if (linePointsX.length > 0) {
const indicesX = new Uint16Array(
linePointsX.map((_, i) => i * 2).flatMap(i => [i, i + 1])
);
const pointsX = new Float32Array(
linePointsX.flatMap(p => [
p.XYZ[0], p.XYZ[1], p.XYZ[2],
p.XYZ[0] + p.OrientationX[0] * 4,
p.XYZ[1] + p.OrientationX[1] * 4,
p.XYZ[2] + p.OrientationX[2] * 4,
])
);
const geometryX = new THREE.BufferGeometry();
geometryX.setAttribute('position', new THREE.BufferAttribute(pointsX, 3));
geometryX.setIndex(new THREE.BufferAttribute(indicesX, 1));
const lineX = new THREE.LineSegments(geometryX, xMaterial);
orientationFrameGroup.add(lineX);
}
// Build Y-orientation lines only for points with valid OrientationY data
const linePointsY = c.Line.filter(p => p.OrientationY && p.OrientationY.length >= 3);
if (linePointsY.length > 0) {
const indicesY = new Uint16Array(
linePointsY.map((_, i) => i * 2).flatMap(i => [i, i + 1])
);
const pointsY = new Float32Array(
linePointsY.flatMap(p => [
p.XYZ[0], p.XYZ[1], p.XYZ[2],
p.XYZ[0] + p.OrientationY[0] * 4,
p.XYZ[1] + p.OrientationY[1] * 4,
p.XYZ[2] + p.OrientationY[2] * 4,
])
);
const geometryY = new THREE.BufferGeometry();
geometryY.setAttribute('position', new THREE.BufferAttribute(pointsY, 3));
geometryY.setIndex(new THREE.BufferAttribute(indicesY, 1));
const lineY = new THREE.LineSegments(geometryY, yMaterial);
orientationFrameGroup.add(lineY);
}

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +102
// const pointsZ = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationZ[0] * 4, p.XYZ[1] + p.OrientationZ[1] * 4, p.XYZ[2] + p.OrientationZ[2] * 4]));
// const geometryZ = new THREE.BufferGeometry();
// geometryZ.setAttribute('position', new THREE.BufferAttribute(pointsZ, 3));
// geometryZ.setIndex(new THREE.BufferAttribute(indices, 1));
// const lineZ = new THREE.LineSegments(geometryZ, zMaterial);
// orientationFrameGroup.add(lineZ);
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Z orientation visualization code is commented out. If this is intentional for this release, consider adding a comment explaining why (e.g., "Z orientation visualization disabled - TODO: re-enable after testing"). If this was accidentally left commented, it should be uncommented to provide complete orientation visualization.

Suggested change
// const pointsZ = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationZ[0] * 4, p.XYZ[1] + p.OrientationZ[1] * 4, p.XYZ[2] + p.OrientationZ[2] * 4]));
// const geometryZ = new THREE.BufferGeometry();
// geometryZ.setAttribute('position', new THREE.BufferAttribute(pointsZ, 3));
// geometryZ.setIndex(new THREE.BufferAttribute(indices, 1));
// const lineZ = new THREE.LineSegments(geometryZ, zMaterial);
// orientationFrameGroup.add(lineZ);
const pointsZ = new Float32Array(c.Line.flatMap(p => [p.XYZ[0], p.XYZ[1], p.XYZ[2], p.XYZ[0] + p.OrientationZ[0] * 4, p.XYZ[1] + p.OrientationZ[1] * 4, p.XYZ[2] + p.OrientationZ[2] * 4]));
const geometryZ = new THREE.BufferGeometry();
geometryZ.setAttribute('position', new THREE.BufferAttribute(pointsZ, 3));
geometryZ.setIndex(new THREE.BufferAttribute(indices, 1));
const lineZ = new THREE.LineSegments(geometryZ, zMaterial);
orientationFrameGroup.add(lineZ);

Copilot uses AI. Check for mistakes.
![](assignment_op.png) ![](assignment_cm.png)


A simple greedy approach, solely based on pairing modes with maximum similairty, can lead to discontinuities and suboptimal global matching. A more robust approach can optimize the overall assignment by:
Copy link

Copilot AI Feb 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: "similairty" should be "similarity".

Suggested change
A simple greedy approach, solely based on pairing modes with maximum similairty, can lead to discontinuities and suboptimal global matching. A more robust approach can optimize the overall assignment by:
A simple greedy approach, solely based on pairing modes with maximum similarity, can lead to discontinuities and suboptimal global matching. A more robust approach can optimize the overall assignment by:

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants