diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
index 281e7e0..83dd26c 100644
--- a/.github/workflows/build-packages.yml
+++ b/.github/workflows/build-packages.yml
@@ -7,11 +7,12 @@ jobs:
strategy:
fail-fast: false
matrix:
- build: [
- {name: ACDC, platform: linux/amd64, os: ubuntu-latest},
- {name: ACDC, platform: windows/amd64, os: windows-latest},
- {name: ACDC, platform: darwin/universal, os: macos-latest},
- ]
+ build:
+ [
+ { name: ACDC, platform: linux/amd64, os: ubuntu-latest },
+ { name: ACDC, platform: windows/amd64, os: windows-latest },
+ { name: ACDC, platform: darwin/universal, os: macos-latest },
+ ]
runs-on: ${{ matrix.build.os }}
steps:
- uses: actions/checkout@v4
@@ -21,9 +22,7 @@ jobs:
uses: actions/setup-go@v5
with:
check-latest: true
- go-version: 1.24.3
- - run: go version
- shell: bash
+ go-version: 1.25.3
- name: Setup NodeJS
uses: actions/setup-node@v4
with:
@@ -46,7 +45,7 @@ jobs:
run: wails build -platform ${{ matrix.build.platform }} -webview2 download -o ACDC
shell: bash
- name: Build Linux App
- if: runner.os == 'Linux'
+ if: runner.os == 'Linux'
working-directory: .
run: wails build -platform ${{ matrix.build.platform }} -webview2 download -tags webkit2_41 -o ACDC
shell: bash
@@ -79,4 +78,4 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.build.name }}-${{ runner.os }}
- path: 'build/bin/*.zip'
+ path: "build/bin/*.zip"
diff --git a/docs/content/docs/theory/assignment_cm.png b/docs/content/docs/theory/assignment_cm.png
new file mode 100644
index 0000000..da4c9d4
Binary files /dev/null and b/docs/content/docs/theory/assignment_cm.png differ
diff --git a/docs/content/docs/theory/assignment_op.png b/docs/content/docs/theory/assignment_op.png
new file mode 100644
index 0000000..3b09f01
Binary files /dev/null and b/docs/content/docs/theory/assignment_op.png differ
diff --git a/docs/content/docs/theory/cluster1.png b/docs/content/docs/theory/cluster1.png
new file mode 100644
index 0000000..d6c7ed4
Binary files /dev/null and b/docs/content/docs/theory/cluster1.png differ
diff --git a/docs/content/docs/theory/cluster2.png b/docs/content/docs/theory/cluster2.png
new file mode 100644
index 0000000..b5388d1
Binary files /dev/null and b/docs/content/docs/theory/cluster2.png differ
diff --git a/docs/content/docs/theory/index.md b/docs/content/docs/theory/index.md
index e556c0d..22ddb8f 100644
--- a/docs/content/docs/theory/index.md
+++ b/docs/content/docs/theory/index.md
@@ -4,14 +4,180 @@ date: 2024-05-14T19:27:37+10:00
weight: 10
---
-Coming soon!
+This page describes the Campbell diagram generation workflow and its theoretical background.
-### Multi-Blade Coordinate Transform (MBC)
+## Overview
-### Eigenanalysis
+A Campbell diagram is a powerful visualization tool used in wind turbine analysis to understand how natural frequencies and damping ratios vary with rotor and wind speeds. It helps identify potential resonance conditions and instabilities where structural modes may be excited by harmonic frequencies, which is critical for avoiding fatigue damage and ensuring safe operation.
-### Modal Assurance Criteria (MAC)
+Campbell diagram generation with OpenFAST follows a systematic automated workflow consisting of five main steps:
-### Assignment Problem
+1. **Import OpenFAST Model** - Analyze the turbine to get time domain and linearization data
+2. **Define Operating Points** - Specify rotor speeds and other operating conditions for linearization
+3. **Run Simulations** - Execute OpenFAST linearization at each operating point
+4. **Multi-Blade Coordinate (MBC) Transformation** - Transform rotating blade coordinates to fixed reference frame
+5. **Eigenanalysis and Mode Identification** - Extract natural frequencies and mode shapes, then track modes across operating points
-### Spectral Clustering
\ No newline at end of file
+
+
+## Multi-Blade Coordinate (MBC) Transformation
+
+For wind turbines with multiple blades, the dynamics in the rotating reference frame are periodic and coupled due to rotor rotation. The Multi-Blade Coordinate transformation converts the rotating blade degrees of freedom into a fixed (non-rotating) reference frame, producing:
+
+- **Collective modes** - All blades move in phase (symmetric motion)
+- **Cyclic modes** - Blades move with phase differences (asymmetric motion)
+- **Fixed-frame representation** - Time-invariant system matrices suitable for eigenanalysis
+
+This transformation is essential because it:
+- Decouples the periodic equations of motion into time-invariant form
+- Enables conventional eigenanalysis techniques
+- Separates symmetric and asymmetric rotor modes
+- Simplifies the identification of structural modes
+
+The MBC transformation yields state-space matrices in the fixed reference frame, which can then be analyzed using standard linear algebra techniques.
+
+## Eigenanalysis
+
+Eigenanalysis is a mathematical technique that decomposes a linear system into its fundamental components: eigenvalues and eigenvectors.
+
+- **Eigenvalues** - Complex numbers representing natural frequencies and damping ratios of the system modes
+- **Eigenvectors** - Complex vectors describing the mode shapes, indicating how different degrees of freedom participate in each mode
+
+For wind turbine analysis, eigenanalysis of the MBC-transformed system provides:
+- Natural frequencies at each operating point
+- Mode shapes that characterize the physical motion (e.g., tower bending, blade flap/edge, drivetrain torsion)
+- Damping characteristics indicating stability of each mode
+
+The eigenvalues are typically plotted against rotor speed to create the Campbell diagram, with rotational harmonic lines overlaid to identify potential resonances.
+
+## Mode Identification
+
+Mode identification is the process of tracking the same physical mode across different operating points based on the eigenvectors and eigenvalues produced by MBC transformation and eigenanalysis. This is a challenging task because:
+
+- Natural frequencies of different modes may cross or veer as operating conditions change
+- Mode shapes may gradually evolve with rotor speed
+- Multiple modes with similar characteristics may be present
+- Numerical noise can affect mode ordering
+
+As seen from the figure below, some modes can be clearly distinguished across the operating range, whereas others may cross or come very close to each other, making automated tracking difficult.
+
+
+
+### Similarity Metrics
+
+Modal identification relies on quantitative similarity measurements to determine which modes at different operating points correspond to the same physical phenomenon.
+
+**Modal Assurance Criteria (MAC)**
+
+The Modal Assurance Criteria compares the complex eigenvectors of two modes to quantify their similarity. The MAC value ranges from 0 (completely dissimilar) to 1 (identical mode shapes).
+
+
+
+$$
+\text{MAC}(\mu_1, \mu_2) = \left( \frac{|\mu_1^*\ \mu_2|}{||\mu_1||\ ||\mu_2||} \right)^2
+$$
+
+where \(\mu_1\) and \(\mu_2\) are complex eigenvectors from two different operating points, and \(^*\) denotes the complex conjugate transpose.
+
+**Pole-Weighted MAC (MACXP)**
+
+MACXP enhances the standard MAC by incorporating both eigenvector similarity and eigenvalue proximity:
+
+
+
+$$
+\text{MACXP}(\mu_1, \mu_2) = \frac{\left(\frac{|\mu_1^*\ \mu_2|}{|\overline{\lambda_1} + \lambda_2|} + \frac{|\mu_1^{\top}\ \mu_2|}{|\lambda_1 + \lambda_2|}\right)^2}{\left(\frac{\mu_1^*\ \mu_1}{2|\text{Re } \lambda_1|} + \frac{|\mu_1^{\top}\ \mu_1|}{2|\lambda_1|}\right) \left(\frac{\mu_2^*\ \mu_2}{2|\text{Re } \lambda_2|} + \frac{|\mu_2^{\top}\ \mu_2|}{2|\lambda_2|}\right)}
+$$
+
+where \(\lambda_1\) and \(\lambda_2\) are the eigenvalues corresponding to each mode. MACXP penalizes modes with dissimilar eigenvalues, providing a more robust similarity measure that considers both shape and frequency content.
+
+## Mode Tracking via Assignment Problem
+
+Mode tracking between consecutive operating points can be formulated as an assignment problem. Given *m* modes at operating points *n* and *n+1*, the goal is to find the optimal one-to-one assignment that maximizes total similarity (MAC or MACXP).
+
+ 
+
+
+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:
+
+1. **Maximum Weighted Bipartite Matching** - Treat the mode assignment as a bipartite graph matching problem
+2. **Find combination of pairings which maximizes total weights** - Rather than greedy selection, find the global optimum
+3. **Reformulate as the Assignment Problem** - Cast as a linear optimization problem
+4. **Convert similarity matrix to cost matrix and solve via Hungarian algorithm**:
+ - Create cost matrix \(\text{C}\) where \(C_{ij} = 1 - \text{MAC}_{ij}\) (or use MACXP)
+ - Apply Hungarian algorithm to find minimum cost assignment
+ - Guarantees optimal one-to-one matching that maximizes total similarity
+
+This approach allows tracking of modes as they evolve between operating points. However, even this method can accumulate errors when applied sequentially across many operating points, especially when modes cross or when there are multiple similar modes. More sophisticated approaches using spectral clustering can address these challenges by considering all operating points simultaneously.
+
+## Spectral Clustering
+
+When tracking modes between operating points (OPs), modes can follow paths that evolve continuously with rotor speed. However, a fundamental challenge arises when mode paths intersect as shown on below figure:
+
+
+
+To determine which mode paths should be connected across all operating points given mode similarity metrics (MAC or MACXP), spectral clustering provides a robust global approach. Unlike sequential assignment, spectral clustering considers the entire similarity structure simultaneously.
+
+**Key Concept**: Spectral clustering uses eigenvectors derived from the similarity matrix as coordinates in a lower-dimensional embedding space. K-means clustering then groups modes in this embedded space based on Euclidean distance. As a result, modes with the highest overall similarity across all operating points are grouped together, enabling robust mode identification even at intersections or crossings.
+
+### Spectral Clustering Algorithm
+
+The spectral clustering algorithm for mode identification follows these steps:
+
+1. **Collect all modes** - Gather all modes from all operating points that need to be grouped into continuous paths
+
+2. **Construct similarity (adjacency) matrix, A** - Compute pairwise similarity between all modes using MAC or MACXP
+ - \(A_{ij}\) represents the similarity between mode *i* and mode *j*
+ - Diagonal elements are zero (a mode is not compared to itself)
+ - Only compute similarities between modes at different operating points
+
+3. **Construct degree matrix, D** - Create a diagonal matrix where each diagonal element is the sum of similarities for that mode
+ - \(D_{ii} = \sum_{j=1}^{n} A_{ij}\)
+ - This normalizes for modes that have many strong connections
+
+4. **Compute Laplacian** - Construct Laplacian matrix as \(L = D - A\) and normalize it as \(L_{norm} = D^{-\frac{1}{2}}LD^{-\frac{1}{2}}\)
+
+5. **Eigendecomposition** - Compute eigenvalues and eigenvectors of \(L_{norm}\)
+ - Find the *n* smallest eigenvalues of the normalized Laplacian
+ - *n* is the number of paths or number of clusters to partition the modes into
+ - Extract the corresponding eigenvectors
+
+6. **Construct observation (coordinate) matrix** - Build matrix where columns are eigenvectors of *n* smallest eigenvalues
+ - Each row is effectively a mode coordinate in multidimensional space
+ - Each mode is now represented as a point in *n*-dimensional space
+ - Rows are normalized so their magnitude is 1 (each row vector has unit length)
+
+7. **Apply K-means clustering** - Cluster the modes in this embedded space to identify which modes belong to the same path
+
+### K-means Clustering
+
+K-means clustering aims to partition *n* observations into *k* clusters, where each observation belongs to the cluster with the nearest centroid (mean).
+
+**How it Works**:
+- Uses coordinates derived from eigenvectors (the observation matrix from spectral clustering)
+- Iteratively partitions the data to minimize the squared Euclidean distance from point coordinates to centroid
+- Finds a local minimum, so results are highly dependent on initial guess of cluster centroids
+
+**Challenge**: K-means returns a cluster number for each mode, but those assignments may not be optimal for mode tracking. It may group modes from the same operating point into the same cluster, whereas the goal is to have clusters that correspond to paths through operating points, with each cluster containing at most one mode per operating point.
+
+To obtain physically meaningful mode paths:
+
+1. Run K-means multiple times with random initial seeds
+2. Check number of modes in each cluster that have the same OP
+3. Stop iterating when few modes have the same OP in the same cluster
+
+This iterative approach with validation ensures that the final clusters represent continuous mode evolution across operating points rather than arbitrary groupings.
+
+
+
+
+## Reference
+
+These are the links to sources to get more details:
+
+- MBC: https://docs.nrel.gov/docs/fy10osti/44327.pdf
+- Similarity metrics: https://past.isma-isaac.be/downloads/isma2010/papers/isma2010_0103.pdf
+- Assignment problem: https://en.wikipedia.org/wiki/Assignment_problem
+- Hungarian algorithm: https://en.wikipedia.org/wiki/Hungarian_algorithm
+- Spectral clustering: https://arxiv.org/abs/0711.0189 and https://en.wikipedia.org/wiki/Spectral_clustering
+- K-means clustering: https://en.wikipedia.org/wiki/K-means_clustering
\ No newline at end of file
diff --git a/docs/content/docs/theory/mode_identification.png b/docs/content/docs/theory/mode_identification.png
new file mode 100644
index 0000000..30ba3fc
Binary files /dev/null and b/docs/content/docs/theory/mode_identification.png differ
diff --git a/docs/content/docs/theory/spectral_clustering.png b/docs/content/docs/theory/spectral_clustering.png
new file mode 100644
index 0000000..d727790
Binary files /dev/null and b/docs/content/docs/theory/spectral_clustering.png differ
diff --git a/docs/content/docs/theory/workflow.png b/docs/content/docs/theory/workflow.png
new file mode 100644
index 0000000..7c01c3e
Binary files /dev/null and b/docs/content/docs/theory/workflow.png differ
diff --git a/docs/hugo.toml b/docs/hugo.toml
index c081034..d46816e 100644
--- a/docs/hugo.toml
+++ b/docs/hugo.toml
@@ -34,6 +34,17 @@ homepage_image = 'images/cd_tn.png'
## You can enable to add anchor links to header elements
# enable_anchor_link = true
+math = true
+
+[markup]
+ [markup.goldmark]
+ [markup.goldmark.extensions]
+ [markup.goldmark.extensions.passthrough]
+ enable = true
+ [markup.goldmark.extensions.passthrough.delimiters]
+ block = [['\[', '\]'], ['$$', '$$']]
+ inline = [['\(', '\)']]
+
[params.homepage_meta_tags]
meta_description = "ACDC is an app for generating Campbell Diagrams with OpenFAST"
meta_og_title = "ACDC: Automated Campbell Diagram Code"
diff --git a/docs/themes/hugo-whisper-theme/layouts/_default/baseof.html b/docs/themes/hugo-whisper-theme/layouts/_default/baseof.html
index ed31424..528449f 100644
--- a/docs/themes/hugo-whisper-theme/layouts/_default/baseof.html
+++ b/docs/themes/hugo-whisper-theme/layouts/_default/baseof.html
@@ -26,6 +26,10 @@
{{ block "header_css" . }}{{ end }}
+ {{ if .Param "math" }}
+ {{ partialCached "math.html" . }}
+ {{ end }}
+
diff --git a/docs/themes/hugo-whisper-theme/layouts/partials/math.html b/docs/themes/hugo-whisper-theme/layouts/partials/math.html
new file mode 100644
index 0000000..6919a51
--- /dev/null
+++ b/docs/themes/hugo-whisper-theme/layouts/partials/math.html
@@ -0,0 +1,13 @@
+
+
+
\ No newline at end of file
diff --git a/fio.go b/fio.go
index 1fb4107..d80ad0a 100644
--- a/fio.go
+++ b/fio.go
@@ -74,39 +74,41 @@ type String struct {
// Files structure contains slices of all file types
// file types must be slices for the parsing and writing code to work
type Files struct {
- Main []Main `json:"Main"`
- ElastoDyn []ElastoDyn `json:"ElastoDyn"`
- BeamDyn []BeamDyn `json:"BeamDyn"`
- SubDyn []SubDyn `json:"SubDyn"`
- AeroDyn []AeroDyn `json:"AeroDyn"`
- AeroDyn14 []AeroDyn14 `json:"AeroDyn14"`
- HydroDyn []HydroDyn `json:"HydroDyn"`
- ServoDyn []ServoDyn `json:"ServoDyn"`
- InflowWind []InflowWind `json:"InflowWind"`
- OLAF []OLAF `json:"OLAF"`
- Misc []Misc `json:"Misc"`
- StControl []StControl `json:"StControl"`
- AirfoilInfo []AirfoilInfo `json:"AirfoilInfo"`
- PathMap map[string]string `json:"-"`
+ Main []Main `json:"Main"`
+ ElastoDyn []ElastoDyn `json:"ElastoDyn"`
+ BeamDyn []BeamDyn `json:"BeamDyn"`
+ BeamDynBlade []BeamDynBlade `json:"BeamDynBlade"`
+ SubDyn []SubDyn `json:"SubDyn"`
+ AeroDyn []AeroDyn `json:"AeroDyn"`
+ AeroDyn14 []AeroDyn14 `json:"AeroDyn14"`
+ HydroDyn []HydroDyn `json:"HydroDyn"`
+ ServoDyn []ServoDyn `json:"ServoDyn"`
+ InflowWind []InflowWind `json:"InflowWind"`
+ OLAF []OLAF `json:"OLAF"`
+ Misc []Misc `json:"Misc"`
+ StControl []StControl `json:"StControl"`
+ AirfoilInfo []AirfoilInfo `json:"AirfoilInfo"`
+ PathMap map[string]string `json:"-"`
}
// NewFiles returns the Model structure with all slices initialized to empty
func NewFiles() *Files {
return &Files{
- Main: []Main{},
- ElastoDyn: []ElastoDyn{},
- BeamDyn: []BeamDyn{},
- SubDyn: []SubDyn{},
- AeroDyn: []AeroDyn{},
- AeroDyn14: []AeroDyn14{},
- HydroDyn: []HydroDyn{},
- InflowWind: []InflowWind{},
- OLAF: []OLAF{},
- ServoDyn: []ServoDyn{},
- StControl: []StControl{},
- Misc: []Misc{},
- AirfoilInfo: []AirfoilInfo{},
- PathMap: map[string]string{},
+ Main: []Main{},
+ ElastoDyn: []ElastoDyn{},
+ BeamDyn: []BeamDyn{},
+ BeamDynBlade: []BeamDynBlade{},
+ SubDyn: []SubDyn{},
+ AeroDyn: []AeroDyn{},
+ AeroDyn14: []AeroDyn14{},
+ HydroDyn: []HydroDyn{},
+ InflowWind: []InflowWind{},
+ OLAF: []OLAF{},
+ ServoDyn: []ServoDyn{},
+ StControl: []StControl{},
+ Misc: []Misc{},
+ AirfoilInfo: []AirfoilInfo{},
+ PathMap: map[string]string{},
}
}
@@ -157,6 +159,9 @@ type Main struct {
FileBase
TMax Real `json:"TMax"`
DT Real `json:"DT"`
+ RhoInf Real `json:"RhoInf"`
+ ConvTol Real `json:"ConvTol"`
+ MaxConvIter Integer `json:"MaxConvIter"`
CompElast Integer `json:"CompElast"`
CompInflow Integer `json:"CompInflow"`
CompAero Integer `json:"CompAero"`
@@ -257,34 +262,46 @@ type AeroDyn14 struct {
type BeamDyn struct {
FileBase
- RotStates Bool `json:"RotStates"`
- BldFile Path `json:"BldFile" ftype:"Misc"` // BeamDynBlade
+ RotStates Bool `json:"RotStates"`
+ BldFile Path `json:"BldFile" ftype:"BeamDynBlade"`
+ OrderElem Integer `json:"OrderElem" key:"order_elem"`
+}
+
+type BeamDynBlade struct {
+ FileBase
+ DampType Integer `json:"DampType" key:"damp_type"`
}
type ElastoDyn struct {
FileBase
- FlapDOF1 Bool `json:"FlapDOF1"`
- FlapDOF2 Bool `json:"FlapDOF2"`
- EdgeDOF Bool `json:"EdgeDOF"`
- TeetDOF Bool `json:"TeetDOF"`
- DrTrDOF Bool `json:"DrTrDOF"`
- GenDOF Bool `json:"GenDOF"`
- YawDOF Bool `json:"YawDOF"`
- TwFADOF1 Bool `json:"TwFADOF1"`
- TwFADOF2 Bool `json:"TwFADOF2"`
- TwSSDOF1 Bool `json:"TwSSDOF1"`
- TwSSDOF2 Bool `json:"TwSSDOF2"`
- BlPitch1 Real `json:"BlPitch1" key:"BlPitch(1)"`
- BlPitch2 Real `json:"BlPitch2" key:"BlPitch(2)"`
- BlPitch3 Real `json:"BlPitch3" key:"BlPitch(3)"`
- RotSpeed Real `json:"RotSpeed"`
- NumBl Integer `json:"NumBl"`
- TipRad Real `json:"TipRad"`
- ShftTilt Real `json:"ShftTilt"`
- BldFile1 Path `json:"BldFile1" key:"BldFile(1)" ftype:"Misc"` // ElastoDynBlade
- BldFile2 Path `json:"BldFile2" key:"BldFile(2)" ftype:"Misc"` // ElastoDynBlade
- BldFile3 Path `json:"BldFile3" key:"BldFile(3)" ftype:"Misc"` // ElastoDynBlade
- TwrFile Path `json:"TwrFile" ftype:"Misc"` // ElastoDynTower
+ FlapDOF1 Bool `json:"FlapDOF1"`
+ FlapDOF2 Bool `json:"FlapDOF2"`
+ EdgeDOF Bool `json:"EdgeDOF"`
+ PitchDOF Bool `json:"PitchDOF"`
+ DrTrDOF Bool `json:"DrTrDOF"`
+ GenDOF Bool `json:"GenDOF"`
+ YawDOF Bool `json:"YawDOF"`
+ TwFADOF1 Bool `json:"TwFADOF1"`
+ TwFADOF2 Bool `json:"TwFADOF2"`
+ TwSSDOF1 Bool `json:"TwSSDOF1"`
+ TwSSDOF2 Bool `json:"TwSSDOF2"`
+ PtfmSgDOF Bool `json:"PtfmSgDOF"`
+ PtfmSwDOF Bool `json:"PtfmSwDOF"`
+ PtfmHvDOF Bool `json:"PtfmHvDOF"`
+ PtfmRDOF Bool `json:"PtfmRDOF"`
+ PtfmPDOF Bool `json:"PtfmPDOF"`
+ PtfmYDOF Bool `json:"PtfmYDOF"`
+ BlPitch1 Real `json:"BlPitch1" key:"BlPitch(1)"`
+ BlPitch2 Real `json:"BlPitch2" key:"BlPitch(2)"`
+ BlPitch3 Real `json:"BlPitch3" key:"BlPitch(3)"`
+ RotSpeed Real `json:"RotSpeed"`
+ NumBl Integer `json:"NumBl"`
+ TipRad Real `json:"TipRad"`
+ ShftTilt Real `json:"ShftTilt"`
+ BldFile1 Path `json:"BldFile1" key:"BldFile(1)" ftype:"Misc"` // ElastoDynBlade
+ BldFile2 Path `json:"BldFile2" key:"BldFile(2)" ftype:"Misc"` // ElastoDynBlade
+ BldFile3 Path `json:"BldFile3" key:"BldFile(3)" ftype:"Misc"` // ElastoDynBlade
+ TwrFile Path `json:"TwrFile" ftype:"Misc"` // ElastoDynTower
}
type HydroDyn struct {
@@ -350,7 +367,6 @@ type StControl struct {
type SubDyn struct {
FileBase
- CBMod Bool `json:"CBMod"`
Nmodes Integer `json:"Nmodes"`
}
diff --git a/frontend/src/components/ModeViz.vue b/frontend/src/components/ModeViz.vue
index 9cee9fc..e6a154c 100644
--- a/frontend/src/components/ModeViz.vue
+++ b/frontend/src/components/ModeViz.vue
@@ -10,6 +10,7 @@ const project = useProjectStore()
const props = defineProps<{
ModeData: viz.ModeData
showNodePaths: boolean
+ showNodeOrientation: boolean
}>()
watch(
@@ -19,13 +20,16 @@ watch(
)
watch(() => props.showNodePaths, (snp) => (nodePaths.visible = snp))
+watch(() => props.showNodeOrientation, (sno) => {
+ for (const ofr of orientationFrames) {
+ ofr.visible = sno && lineFrames.indexOf(ofr) === frameNum
+ }
+})
-const showNodePaths = ref(true)
-
-let frames = new Array;
+let lineFrames = new Array;
+let orientationFrames = new Array;
let nodePaths = new THREE.Group;
let frameNum = 0;
-let capturing = false;
let frameCenter = new THREE.Vector3;
let frameSize = new THREE.Vector3;
let clock = new THREE.Clock();
@@ -35,16 +39,29 @@ const FOV = 10
function createFrames(modeData: viz.ModeData) {
if (modeData.Frames == null) return
scene.clear()
- frames = [] as THREE.Group[];
+
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));
const material = new THREE.PointsMaterial({ color: 0x888888 });
const origin = new THREE.Points(geometry, material);
origin.visible = false
+
+ // Clear existing frames
+ lineFrames = [] as THREE.Group[];
+ orientationFrames = [] as THREE.Group[];
+
const allFramesGroup = new THREE.Group()
allFramesGroup.add(origin)
+
+ const xMaterial = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 2 });
+ const yMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 2 });
+ const zMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, linewidth: 2 });
+
+ // Loop through frames
for (const f of modeData.Frames) {
- const frameGroup = new THREE.Group()
+
+ // Lines
+ const lineFrameGroup = new THREE.Group()
for (const c of Object.values(f.Components)) {
const curve = new THREE.CatmullRomCurve3(
c.Line.map((p) => new THREE.Vector3(p.XYZ[0], p.XYZ[1], p.XYZ[2])))
@@ -52,13 +69,45 @@ function createFrames(modeData: viz.ModeData) {
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 1 });
const curveObject = new THREE.Line(geometry, material);
- frameGroup.add(curveObject)
+ lineFrameGroup.add(curveObject)
allFramesGroup.add(curveObject.clone()) // Add clone of object to be used for view sizing
}
- frameGroup.visible = false // Initialize each group to not visible for animation
- frames.push(frameGroup)
- scene.add(frameGroup)
+ lineFrameGroup.visible = false // Initialize each group to not visible for animation
+ lineFrames.push(lineFrameGroup)
+
+ // Orientations
+ const orientationFrameGroup = new THREE.Group()
+ for (const c of Object.values(f.Components)) {
+ 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);
+
+ // 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);
+ }
+ orientationFrameGroup.visible = false // Initialize each group to not visible for animation
+ orientationFrames.push(orientationFrameGroup)
+
+ scene.add(lineFrameGroup)
+ scene.add(orientationFrameGroup)
}
+
// Node paths
const componentNames = Object.keys(modeData.Frames[0].Components)
const curves = new Array
@@ -172,12 +221,23 @@ const views = [
function animate() {
requestAnimationFrame(animate);
delta += clock.getDelta()
- if (delta > 1.5 / frames.length) {
+ if (delta > 1.5 / lineFrames.length) {
delta = 0
- frames[frameNum].visible = false;
+
+ // Hide current frame
+ lineFrames[frameNum].visible = false;
+ orientationFrames[frameNum].visible = false;
+
+ // Increment frame number
frameNum++
- if (frameNum >= frames.length) frameNum = 0
- frames[frameNum].visible = true;
+ if (frameNum >= lineFrames.length) frameNum = 0
+
+ // Show next frame
+ lineFrames[frameNum].visible = true;
+ if (props.showNodeOrientation) {
+ orientationFrames[frameNum].visible = true;
+ }
+
render();
}
}
diff --git a/frontend/src/components/Model.vue b/frontend/src/components/Model.vue
index 04b5baa..c2184bd 100644
--- a/frontend/src/components/Model.vue
+++ b/frontend/src/components/Model.vue
@@ -114,6 +114,9 @@ function setDefaults() {
+
+
+
@@ -189,8 +192,8 @@ function setDefaults() {
-
-
+
+
No fields in file
can be modified
diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue
index 6bd3d93..ef3c225 100644
--- a/frontend/src/components/Results.vue
+++ b/frontend/src/components/Results.vue
@@ -22,6 +22,7 @@ const selectedPoint = ref
(null)
const freqChart = ref | null>(null)
const dampChart = ref | null>(null)
const showNodePaths = ref(true)
+const showNodeOrientation = ref(false)
const xAxisWS = ref(true)
const rotorSpeedMods = [1, 3, 6, 9, 12, 15]
const vizScale = ref(20)
@@ -220,11 +221,11 @@ const charts = computed(() => {
title: { display: true, text: xLabel, font: { size: 18 } },
min: Math.min(...xValues),
max: Math.max(...xValues),
- ticks: { font: { size: 16 } }
+ ticks: { font: { size: 16 }, callback: (value) => Number(value).toFixed(3) },
},
y: {
title: { display: true, text: cfg.label, font: { size: 18 } },
- ticks: { font: { size: 16 } },
+ ticks: { font: { size: 16 }, callback: (value) => Number(value).toFixed(3) },
min: 0.95 * (cfg.isNatFreq ? freqMin : dampMin),
max: 1.05 * (cfg.isNatFreq ? freqMax : dampMax),
},
@@ -261,27 +262,27 @@ const bladeTipChart = computed(() => {
// Loop over each component (blade) - similar to PlotTipDeflection
for (const componentName of componentNames) {
console.log("Adding series for", componentName)
-
+
// For the blade tip, we only need the last point of each frame
- const tipFlapData: {x: number, y: number}[] = []
- const tipEdgeData: {x: number, y: number}[] = []
-
+ const tipFlapData: { x: number, y: number }[] = []
+ const tipEdgeData: { x: number, y: number }[] = []
+
for (let frameIndex = 0; frameIndex < currentModeData.Frames.length; frameIndex++) {
const frame = currentModeData.Frames[frameIndex]
-
+
if (frame.Components[componentName]) {
const component = frame.Components[componentName]
-
+
// Check if LocalLine exists and has data
if (component.LocalLine && component.LocalLine.length > 0) {
// Get the last point (tip)
const tipPoint = component.LocalLine[component.LocalLine.length - 1]
-
+
tipFlapData.push({
x: frameIndex + 1, // Frame numbers start from 1
y: tipPoint.XYZ[0] // X coordinate (Flap direction)
})
-
+
tipEdgeData.push({
x: frameIndex + 1, // Frame numbers start from 1
y: tipPoint.XYZ[1] // Y coordinate (Edge direction)
@@ -289,10 +290,10 @@ const bladeTipChart = computed(() => {
}
}
}
-
+
console.log("Tip Flap data:", tipFlapData)
console.log("Tip Edge data:", tipEdgeData)
-
+
// Create Flap series
if (tipFlapData.length > 0) {
datasets.push({
@@ -306,7 +307,7 @@ const bladeTipChart = computed(() => {
borderWidth: 2,
})
}
-
+
// Create Edge series
if (tipEdgeData.length > 0) {
datasets.push({
@@ -321,7 +322,7 @@ const bladeTipChart = computed(() => {
borderDash: [5, 5], // Dashed line to distinguish from Flap
})
}
-
+
colorIndex += 2 // Increment by 2 since we use 2 colors per component
}
@@ -329,7 +330,7 @@ const bladeTipChart = computed(() => {
responsive: true,
maintainAspectRatio: false,
plugins: {
- legend: {
+ legend: {
display: true,
position: 'right',
labels: {
@@ -383,7 +384,7 @@ const bladeTipChart = computed(() => {
Browse
@@ -658,7 +659,8 @@ const bladeTipChart = computed(() => {
this.convertValues(elem, classs));
+ } else if ("object" === typeof a) {
+ if (asMap) {
+ for (const key of Object.keys(a)) {
+ a[key] = new classs(a[key]);
+ }
+ return a;
+ }
+ return new classs(a);
+ }
+ return a;
+ }
+ }
+ export class BeamDynBlade {
+ Name: string;
+ Type: string;
+ Lines: string[];
+ DampType: Integer;
+
+ static createFrom(source: any = {}) {
+ return new BeamDynBlade(source);
+ }
+
+ constructor(source: any = {}) {
+ if ('string' === typeof source) source = JSON.parse(source);
+ this.Name = source["Name"];
+ this.Type = source["Type"];
+ this.Lines = source["Lines"];
+ this.DampType = this.convertValues(source["DampType"], Integer);
}
convertValues(a: any, classs: any, asMap: boolean = false): any {
@@ -679,7 +717,7 @@ export namespace main {
FlapDOF1: Bool;
FlapDOF2: Bool;
EdgeDOF: Bool;
- TeetDOF: Bool;
+ PitchDOF: Bool;
DrTrDOF: Bool;
GenDOF: Bool;
YawDOF: Bool;
@@ -687,6 +725,12 @@ export namespace main {
TwFADOF2: Bool;
TwSSDOF1: Bool;
TwSSDOF2: Bool;
+ PtfmSgDOF: Bool;
+ PtfmSwDOF: Bool;
+ PtfmHvDOF: Bool;
+ PtfmRDOF: Bool;
+ PtfmPDOF: Bool;
+ PtfmYDOF: Bool;
BlPitch1: Real;
BlPitch2: Real;
BlPitch3: Real;
@@ -711,7 +755,7 @@ export namespace main {
this.FlapDOF1 = this.convertValues(source["FlapDOF1"], Bool);
this.FlapDOF2 = this.convertValues(source["FlapDOF2"], Bool);
this.EdgeDOF = this.convertValues(source["EdgeDOF"], Bool);
- this.TeetDOF = this.convertValues(source["TeetDOF"], Bool);
+ this.PitchDOF = this.convertValues(source["PitchDOF"], Bool);
this.DrTrDOF = this.convertValues(source["DrTrDOF"], Bool);
this.GenDOF = this.convertValues(source["GenDOF"], Bool);
this.YawDOF = this.convertValues(source["YawDOF"], Bool);
@@ -719,6 +763,12 @@ export namespace main {
this.TwFADOF2 = this.convertValues(source["TwFADOF2"], Bool);
this.TwSSDOF1 = this.convertValues(source["TwSSDOF1"], Bool);
this.TwSSDOF2 = this.convertValues(source["TwSSDOF2"], Bool);
+ this.PtfmSgDOF = this.convertValues(source["PtfmSgDOF"], Bool);
+ this.PtfmSwDOF = this.convertValues(source["PtfmSwDOF"], Bool);
+ this.PtfmHvDOF = this.convertValues(source["PtfmHvDOF"], Bool);
+ this.PtfmRDOF = this.convertValues(source["PtfmRDOF"], Bool);
+ this.PtfmPDOF = this.convertValues(source["PtfmPDOF"], Bool);
+ this.PtfmYDOF = this.convertValues(source["PtfmYDOF"], Bool);
this.BlPitch1 = this.convertValues(source["BlPitch1"], Real);
this.BlPitch2 = this.convertValues(source["BlPitch2"], Real);
this.BlPitch3 = this.convertValues(source["BlPitch3"], Real);
@@ -1040,7 +1090,6 @@ export namespace main {
Name: string;
Type: string;
Lines: string[];
- CBMod: Bool;
Nmodes: Integer;
static createFrom(source: any = {}) {
@@ -1052,7 +1101,6 @@ export namespace main {
this.Name = source["Name"];
this.Type = source["Type"];
this.Lines = source["Lines"];
- this.CBMod = this.convertValues(source["CBMod"], Bool);
this.Nmodes = this.convertValues(source["Nmodes"], Integer);
}
@@ -1120,6 +1168,9 @@ export namespace main {
Lines: string[];
TMax: Real;
DT: Real;
+ RhoInf: Real;
+ ConvTol: Real;
+ MaxConvIter: Integer;
CompElast: Integer;
CompInflow: Integer;
CompAero: Integer;
@@ -1173,6 +1224,9 @@ export namespace main {
this.Lines = source["Lines"];
this.TMax = this.convertValues(source["TMax"], Real);
this.DT = this.convertValues(source["DT"], Real);
+ this.RhoInf = this.convertValues(source["RhoInf"], Real);
+ this.ConvTol = this.convertValues(source["ConvTol"], Real);
+ this.MaxConvIter = this.convertValues(source["MaxConvIter"], Integer);
this.CompElast = this.convertValues(source["CompElast"], Integer);
this.CompInflow = this.convertValues(source["CompInflow"], Integer);
this.CompAero = this.convertValues(source["CompAero"], Integer);
@@ -1238,6 +1292,7 @@ export namespace main {
Main: Main[];
ElastoDyn: ElastoDyn[];
BeamDyn: BeamDyn[];
+ BeamDynBlade: BeamDynBlade[];
SubDyn: SubDyn[];
AeroDyn: AeroDyn[];
AeroDyn14: AeroDyn14[];
@@ -1258,6 +1313,7 @@ export namespace main {
this.Main = this.convertValues(source["Main"], Main);
this.ElastoDyn = this.convertValues(source["ElastoDyn"], ElastoDyn);
this.BeamDyn = this.convertValues(source["BeamDyn"], BeamDyn);
+ this.BeamDynBlade = this.convertValues(source["BeamDynBlade"], BeamDynBlade);
this.SubDyn = this.convertValues(source["SubDyn"], SubDyn);
this.AeroDyn = this.convertValues(source["AeroDyn"], AeroDyn);
this.AeroDyn14 = this.convertValues(source["AeroDyn14"], AeroDyn14);
@@ -1490,6 +1546,9 @@ export namespace viz {
export class Point {
XYZ: number[];
+ OrientationX: number[];
+ OrientationY: number[];
+ OrientationZ: number[];
static createFrom(source: any = {}) {
return new Point(source);
@@ -1498,6 +1557,9 @@ export namespace viz {
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.XYZ = source["XYZ"];
+ this.OrientationX = source["OrientationX"];
+ this.OrientationY = source["OrientationY"];
+ this.OrientationZ = source["OrientationZ"];
}
}
export class Component {
diff --git a/frontend/wailsjs/runtime/runtime.js b/frontend/wailsjs/runtime/runtime.js
index 623397b..7cb89d7 100644
--- a/frontend/wailsjs/runtime/runtime.js
+++ b/frontend/wailsjs/runtime/runtime.js
@@ -48,6 +48,10 @@ export function EventsOff(eventName, ...additionalEventNames) {
return window.runtime.EventsOff(eventName, ...additionalEventNames);
}
+export function EventsOffAll() {
+ return window.runtime.EventsOffAll();
+}
+
export function EventsOnce(eventName, callback) {
return EventsOnMultiple(eventName, callback, 1);
}
diff --git a/go.mod b/go.mod
index d54d433..5dcad14 100644
--- a/go.mod
+++ b/go.mod
@@ -9,18 +9,21 @@ require (
github.com/labstack/gommon v0.4.2
github.com/mkmik/argsort v1.1.0
github.com/parallelo-ai/kmeans v0.1.1
- github.com/wailsapp/wails/v2 v2.10.1
- golang.org/x/sync v0.14.0
+ github.com/wailsapp/wails/v2 v2.11.0
+ github.com/wcharczuk/go-chart/v2 v2.1.2
+ golang.org/x/sync v0.18.0
gonum.org/v1/gonum v0.16.0
)
require (
github.com/bep/debounce v1.2.1 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
- github.com/godbus/dbus/v5 v5.1.0 // indirect
+ github.com/godbus/dbus/v5 v5.2.0 // indirect
+ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/google/uuid v1.6.0 // indirect
+ github.com/gorilla/websocket v1.5.3 // indirect
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
- github.com/labstack/echo/v4 v4.13.3 // indirect
+ github.com/labstack/echo/v4 v4.13.4 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/gosod v1.0.4 // indirect
github.com/leaanthony/slicer v1.6.0 // indirect
@@ -30,14 +33,15 @@ require (
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
- github.com/samber/lo v1.50.0 // indirect
+ github.com/samber/lo v1.52.0 // indirect
github.com/tkrajina/go-reflector v0.5.8 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect
- github.com/wailsapp/go-webview2 v1.0.21 // indirect
+ github.com/wailsapp/go-webview2 v1.0.23 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
- golang.org/x/crypto v0.38.0 // indirect
- golang.org/x/net v0.40.0 // indirect
- golang.org/x/sys v0.33.0 // indirect
- golang.org/x/text v0.25.0 // indirect
+ golang.org/x/crypto v0.45.0 // indirect
+ golang.org/x/image v0.25.0 // indirect
+ golang.org/x/net v0.47.0 // indirect
+ golang.org/x/sys v0.38.0 // indirect
+ golang.org/x/text v0.31.0 // indirect
)
diff --git a/go.sum b/go.sum
index ec766d2..71d1bcf 100644
--- a/go.sum
+++ b/go.sum
@@ -11,14 +11,19 @@ github.com/dominikbraun/graph v0.23.0 h1:TdZB4pPqCLFxYhdyMFb1TBdFxp8XLcJfTTBQucV
github.com/dominikbraun/graph v0.23.0/go.mod h1:yOjYyogZLY1LSG9E33JWZJiq5k83Qy2C6POAuiViluc=
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
-github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
-github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/godbus/dbus/v5 v5.2.0 h1:3WexO+U+yg9T70v9FdHr9kCxYlazaAXUhx2VMkbfax8=
+github.com/godbus/dbus/v5 v5.2.0/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
+github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
+github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 h1:njuLRcjAuMKr7kI3D85AXWkw6/+v9PwtV6M6o11sWHQ=
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs=
-github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY=
-github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g=
+github.com/labstack/echo/v4 v4.13.4 h1:oTZZW+T3s9gAu5L8vmzihV7/lkXGZuITzTQkTEhcXEA=
+github.com/labstack/echo/v4 v4.13.4/go.mod h1:g63b33BZ5vZzcIUF8AtRH40DrTlXnx4UMC8rBdndmjQ=
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc=
@@ -52,8 +57,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
-github.com/samber/lo v1.50.0 h1:XrG0xOeHs+4FQ8gJR97zDz5uOFMW7OwFWiFVzqopKgY=
-github.com/samber/lo v1.50.0/go.mod h1:RjZyNk6WSnUFRKK6EyOhsRJMqft3G+pg7dCWHQCWvsc=
+github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
+github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tkrajina/go-reflector v0.5.8 h1:yPADHrwmUbMq4RGEyaOUpz2H90sRsETNVpjzo3DLVQQ=
@@ -62,31 +67,92 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
-github.com/wailsapp/go-webview2 v1.0.21 h1:k3dtoZU4KCoN/AEIbWiPln3P2661GtA2oEgA2Pb+maA=
-github.com/wailsapp/go-webview2 v1.0.21/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
+github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
+github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
-github.com/wailsapp/wails/v2 v2.10.1 h1:QWHvWMXII2nI/nXz77gpPG8P3ehl6zKe+u4su5BWIns=
-github.com/wailsapp/wails/v2 v2.10.1/go.mod h1:zrebnFV6MQf9kx8HI4iAv63vsR5v67oS7GTEZ7Pz1TY=
-golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8=
-golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw=
+github.com/wailsapp/wails/v2 v2.11.0 h1:seLacV8pqupq32IjS4Y7V8ucab0WZwtK6VvUVxSBtqQ=
+github.com/wailsapp/wails/v2 v2.11.0/go.mod h1:jrf0ZaM6+GBc1wRmXsM8cIvzlg0karYin3erahI4+0k=
+github.com/wcharczuk/go-chart/v2 v2.1.2 h1:Y17/oYNuXwZg6TFag06qe8sBajwwsuvPiJJXcUcLL6E=
+github.com/wcharczuk/go-chart/v2 v2.1.2/go.mod h1:Zi4hbaqlWpYajnXB2K22IUYVXRXaLfSGNNR7P4ukyyQ=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
+golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
+golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
+golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
+golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
+golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ=
+golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY=
-golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds=
-golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
-golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
+golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
+golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
+golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
-golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
+golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
+golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
-golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
+golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
+golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/lin/linfile.go b/lin/linfile.go
index da608cc..df896b7 100644
--- a/lin/linfile.go
+++ b/lin/linfile.go
@@ -159,6 +159,8 @@ func ReadLinFile(filePath string) (*LinData, error) {
// Create scanner to read linearization file
scanner := bufio.NewScanner(linFile)
+ buf := make([]byte, 0, 64*1024)
+ scanner.Buffer(buf, 1024*1024)
// Initialize linearization data structure
ld := &LinData{ID: ID}
diff --git a/lin/mat_data.go b/lin/mat_data.go
index 027f2cc..abbf46e 100644
--- a/lin/mat_data.go
+++ b/lin/mat_data.go
@@ -83,7 +83,7 @@ func NewOPOrder(ops OPSlice, numBlades int) OPOrder {
triplet := []int{}
// Lookup descriptions in map with all blade numbers
- for j := 1; j <= numBlades; j++ {
+ for _, j := range []int{1, 2, 3} {
// Create description from blade number to look up in map
testDesc := strings.Replace(desc, matches[0], matches[1]+strconv.Itoa(j), 1)
diff --git a/lin/mbc.go b/lin/mbc.go
index 08f7c66..db0ddf3 100644
--- a/lin/mbc.go
+++ b/lin/mbc.go
@@ -28,6 +28,7 @@ type MBC struct {
DOFsEigen []string `json:"DOFsEigen"`
AvgA *mat.Dense `json:"-"`
ANR *mat.Dense `json:"-"` // Non-rotating A matrix
+ ANRs []*mat.Dense `json:"-"` // Non-rotating A matrix for each OP
AvgX *mat.VecDense `json:"-"`
AvgXdot *mat.VecDense `json:"-"`
}
@@ -105,7 +106,7 @@ func (md *MatData) MBC3() (*MBC, error) {
PX.Permutation(mbc.OrderX.Num, mbc.OrderX.Indices)
// Loop through linearization data
- for i := 0; i < md.NumStep; i++ {
+ for i := range md.NumStep {
// Rotor speed in radians/sec and rotor speed squared
omega := md.Omega[i]
@@ -211,6 +212,9 @@ func (md *MatData) MBC3() (*MBC, error) {
A_NR = append(A_NR, ANR)
}
+ // Save all OP non-rotating A matrices
+ mbc.ANRs = A_NR
+
// Average the A matrix
mbc.AvgA = mat.NewDense(len(md.OP_x), len(md.OP_x), nil)
for _, A_NR := range A_NR {
diff --git a/results.go b/results.go
index c82217f..fad9b08 100644
--- a/results.go
+++ b/results.go
@@ -129,6 +129,16 @@ func (r *Results) Save(caseDir string) error {
return err
}
+ for i, A_NR := range linOP.MBC.ANRs {
+ // Write non-rotating A matrix for each operating point
+ if err := lin.ToCSV(A_NR, fmt.Sprintf("%s_a_nr_%d.csv", linOP.RootPath, i+1), "%g"); err != nil {
+ return fmt.Errorf("error writing A_NR for OP %d: %w", i+1, err)
+ }
+ }
+
+ // Write AvgA data to file
+ lin.ToCSV(linOP.MBC.AvgA, linOP.RootPath+"_avg_a.csv", "%g")
+
// Write Eigen analysis mode results data to file
w := &bytes.Buffer{}
linOP.Modes.ToCSV(w)
diff --git a/viz/viz.go b/viz/viz.go
index 260e9ec..1d1c4f4 100644
--- a/viz/viz.go
+++ b/viz/viz.go
@@ -23,7 +23,10 @@ type Options struct {
}
type Point struct {
- XYZ [3]float32 `json:"XYZ"`
+ XYZ [3]float32 `json:"XYZ"`
+ OrientationX [3]float32 `json:"OrientationX"`
+ OrientationY [3]float32 `json:"OrientationY"`
+ OrientationZ [3]float32 `json:"OrientationZ"`
}
type Component struct {
@@ -294,10 +297,32 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) {
return nil, err
}
+ // Determine indices of data arrays containing orientation data
+ orientationIndices := []int{-1, -1, -1}
+ for i, da := range vtk.PolyData.Piece.PointData.DataArray {
+ switch strings.ToLower(da.Name) {
+ case "orientationx":
+ orientationIndices[0] = i
+ case "orientationy":
+ orientationIndices[1] = i
+ case "orientationz":
+ orientationIndices[2] = i
+ }
+ }
+
// Copy line data into component
component.Line = make([]Point, len(conn))
for j, c := range conn {
copy(component.Line[j].XYZ[:], vtk.PolyData.Piece.Points.DataArray.MatrixF32[c])
+ 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 local line data into component
diff --git a/viz/vtk.go b/viz/vtk.go
index da05d61..f769633 100644
--- a/viz/vtk.go
+++ b/viz/vtk.go
@@ -24,9 +24,9 @@ type OrientationVectors struct {
}
type OrientationMatrix struct {
- X []float32 `json:"x"` // 3x3 matrix for X orientation vectors
- Y []float32 `json:"y"` // 3x3 matrix for Y orientation vectors
- Z []float32 `json:"z"` // 3x3 matrix for Z orientation vectors
+ X [3]float32 `json:"x"` // 3x3 matrix for X orientation vectors
+ Y [3]float32 `json:"y"` // 3x3 matrix for Y orientation vectors
+ Z [3]float32 `json:"z"` // 3x3 matrix for Z orientation vectors
}
type PolyData struct {
@@ -139,41 +139,37 @@ func LoadVTK(path string) (*VTKFile, *VTKFile, error) {
return vf, nil, nil
}
+ // Convert blade root coordinates from global to local
local_vf, err := Global2Local(vf)
if err != nil {
- return nil, nil, fmt.Errorf("Failed to convert global coordinates to local: %w", err)
+ return nil, nil, fmt.Errorf("failed to convert global coordinates to local: %w", err)
}
return vf, local_vf, nil
}
-func GetOrientations(vf *VTKFile) (*OrientationVectors, *OrientationMatrix, error) {
+func GetBladeRootOrientation(vf *VTKFile) (*OrientationVectors, *OrientationMatrix, error) {
// Create orientation vectors
ov := &OrientationVectors{}
- // Find Orientation vectors
+ // Create a new OrientationMatrix
+ om := &OrientationMatrix{}
+
+ // Find Orientation vectors by name and copy first three components
for _, da := range vf.PolyData.Piece.PointData.DataArray {
- if da.Name == "OrientationX" {
+ switch strings.ToLower(da.Name) {
+ case "orientationx":
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])
}
}
- // Create a new OrientationMatrix
- om := &OrientationMatrix{
- X: make([]float32, 3),
- Y: make([]float32, 3),
- Z: make([]float32, 3),
- }
-
- // Fill the OrientationMatrix with the first 3 vectors from each orientation
- om.X = ov.X[0]
- om.Y = ov.Y[0]
- om.Z = ov.Z[0]
-
return ov, om, nil
}
@@ -182,36 +178,30 @@ func Global2Local(vf *VTKFile) (*VTKFile, error) {
// Copy vf (Deep Copy -- so that it works independently)
var local_vf *VTKFile
err := DeepCopy(&vf, &local_vf)
+ if err != nil {
+ return nil, fmt.Errorf("failed to deep copy VTKFile: %w", err)
+ }
local_coords := local_vf.PolyData.Piece.Points.DataArray.MatrixF32
- // fmt.Println("\nLocal coordinates:", local_coords)
// Get Orientation vectors and matrices
- ov, om, err := GetOrientations(local_vf)
+ ov, om, err := GetBladeRootOrientation(local_vf)
if err != nil {
- return nil, fmt.Errorf("Failed to extract orientation vectors and matrices: %w", err)
+ return nil, fmt.Errorf("failed to extract orientation vectors and matrices: %w", err)
}
- // fmt.Println("\nOrientation Matrix:", om)
// Translational/Rotational operations for the points
local_coords = TranslateMatrix(local_coords, local_coords[0]) // Translate by the first point -- so that first point will be moved to the origin
- // fmt.Println("\nLocal coordinates:", local_coords)
transposed_om := TransposeMatrix(om)
- // fmt.Println("\nTransposed Orientation Matrix:", transposed_om)
local_coords = DotProduct(local_coords, transposed_om) // Rotate by the first orientation vector
local_vf.PolyData.Piece.Points.DataArray.MatrixF32 = local_coords
- // fmt.Println("\nLocal coordinates from LoadVTK() :", local_coords)
// Rotational operations for the Orientation vectors
ov.X = DotProduct(ov.X, transposed_om)
ov.Y = DotProduct(ov.Y, transposed_om)
ov.Z = DotProduct(ov.Z, transposed_om)
- // fmt.Println("\nOrientationX:", ov.X)
- // fmt.Println("\nOrientationY:", ov.Y)
- // fmt.Println("\nOrientationZ:", ov.Z)
-
return local_vf, nil
}
@@ -219,7 +209,7 @@ func TranslateMatrix(points [][]float32, translation []float32) [][]float32 {
result := make([][]float32, len(points))
for i, point := range points {
resPoint := make([]float32, 3)
- for j := 0; j < 3; j++ {
+ for j := range 3 {
resPoint[j] = point[j] + (-translation[j])
}
result[i] = resPoint
@@ -231,8 +221,8 @@ func DotProduct(vectors [][]float32, matrix [][]float32) [][]float32 {
result := make([][]float32, len(vectors))
for i, vec := range vectors {
resVec := make([]float32, 3)
- for j := 0; j < 3; j++ {
- for k := 0; k < 3; k++ {
+ for j := range 3 {
+ for k := range 3 {
resVec[j] += vec[k] * matrix[k][j]
}
}
diff --git a/viz/vtk_test.go b/viz/vtk_test.go
index ebb6147..fe65ec9 100644
--- a/viz/vtk_test.go
+++ b/viz/vtk_test.go
@@ -17,7 +17,7 @@ func TestLoadVTK(t *testing.T) {
}
for _, testFile := range testFiles {
- _, err := viz.LoadVTK(testFile)
+ _, _, err := viz.LoadVTK(testFile)
if err != nil {
t.Fatal(err)
}