Skip to content

Commit 8a1402b

Browse files
committed
Merge remote-tracking branch 'origin/main'
2 parents c9b8055 + 5df9429 commit 8a1402b

File tree

8 files changed

+403
-10
lines changed

8 files changed

+403
-10
lines changed
85.9 KB
Loading

docs/content/docs/results/index.md

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,28 @@ To visualize a mode shape, click the `Visualize` button on the `Mode` card or th
8181

8282
The white lines are the blades and tower. The colored lines are the paths that the nodes travel through during the animation. The color, from blue to yellow, is based on the length of the path, with longer paths being a brighter color. The display of the node paths can be toggled by clicking the `Hide Node Paths` button.
8383

84-
The `Clear` button is used to clear modes that have been visualized. Below the `Clear` and `Hide Node Paths` buttons is a list of buttons indicating which line and operating point is being visualized. Each time the `Visualize` button is clicked, a new line-operating-point button appears. The user can select which mode to visualize by clicking these buttons.
84+
The `Clear` button is used to clear modes that have been visualized. Below the `Clear` and `Hide Node Paths` buttons is a list of buttons indicating which line and operating point is being visualized. Each time the `Visualize` button is clicked, a new line-operating-point button appears. The user can select which mode to visualize by clicking these buttons.
85+
86+
87+
When a mode shape is visualized, an additional 2D chart will appear below showing the `blade tip deflection analysis`. This chart demonstrates the deflection of blade tips across frames for both flap and edge directions.
88+
89+
![](blade-tip.png)
90+
91+
The blade tip deflection chart provides the following information:
92+
93+
- **X-axis**: Animation frame number (1 through total number of frames)
94+
- **Y-axis**: Tip deflection magnitude in the local coordinate system
95+
- **Flap deflection**: Solid lines showing out-of-plane blade bending
96+
- **Edge deflection**: Dashed lines showing in-plane blade bending
97+
- **Multiple blades**: Each blade component is displayed with different colors for easy comparison
98+
99+
Note that the blade tip deflection analysis requires specific OpenFAST configuration settings to generate the necessary VTK files with orientation data:
100+
101+
**Required settings in the main `.fst` file:**
102+
```
103+
Linearize True Linearization analysis {True/False}
104+
WrVTK 2 VTK visualization data output: (0: none; 1: init; 2: animation)
105+
VTK_fields True Write mesh fields to VTK data files? {True/False}
106+
```
107+
108+
With these settings, verify that generated `.vtp` files contain orientation field data.

frontend/src/components/ModeViz.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,4 @@ onMounted(() => {
241241
<canvas id="modeVizCanvas" class="h-100 w-100"></canvas>
242242
</template>
243243

244-
<style scoped></style>
244+
<style scoped></style>

frontend/src/components/Results.vue

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,133 @@ const charts = computed(() => {
241241
return objs
242242
})
243243
244+
// Add Blade Tip Deflection Chart with Local Line Data
245+
const bladeTipChart = computed(() => {
246+
const currentModeData = project.modeViz[project.currentVizID]
247+
248+
// Get Component names
249+
const componentNames = new Set<string>()
250+
for (const frame of currentModeData.Frames) {
251+
for (const componentName in frame.Components) {
252+
componentNames.add(componentName)
253+
}
254+
}
255+
console.log("Component Names:", Array.from(componentNames))
256+
257+
const datasets: any[] = []
258+
const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b'] // Expecting 3 blades with 2 series each (Flap and Edge)
259+
let colorIndex = 0
260+
261+
// Loop over each component (blade) - similar to PlotTipDeflection
262+
for (const componentName of componentNames) {
263+
console.log("Adding series for", componentName)
264+
265+
// For the blade tip, we only need the last point of each frame
266+
const tipFlapData: {x: number, y: number}[] = []
267+
const tipEdgeData: {x: number, y: number}[] = []
268+
269+
for (let frameIndex = 0; frameIndex < currentModeData.Frames.length; frameIndex++) {
270+
const frame = currentModeData.Frames[frameIndex]
271+
272+
if (frame.Components[componentName]) {
273+
const component = frame.Components[componentName]
274+
275+
// Check if LocalLine exists and has data
276+
if (component.LocalLine && component.LocalLine.length > 0) {
277+
// Get the last point (tip)
278+
const tipPoint = component.LocalLine[component.LocalLine.length - 1]
279+
280+
tipFlapData.push({
281+
x: frameIndex + 1, // Frame numbers start from 1
282+
y: tipPoint.XYZ[0] // X coordinate (Flap direction)
283+
})
284+
285+
tipEdgeData.push({
286+
x: frameIndex + 1, // Frame numbers start from 1
287+
y: tipPoint.XYZ[1] // Y coordinate (Edge direction)
288+
})
289+
}
290+
}
291+
}
292+
293+
console.log("Tip Flap data:", tipFlapData)
294+
console.log("Tip Edge data:", tipEdgeData)
295+
296+
// Create Flap series
297+
if (tipFlapData.length > 0) {
298+
datasets.push({
299+
label: `Flap_${componentName}`,
300+
data: tipFlapData,
301+
borderColor: colors[colorIndex % colors.length],
302+
backgroundColor: colors[colorIndex % colors.length],
303+
showLine: true,
304+
pointRadius: 2,
305+
pointHoverRadius: 4,
306+
borderWidth: 2,
307+
})
308+
}
309+
310+
// Create Edge series
311+
if (tipEdgeData.length > 0) {
312+
datasets.push({
313+
label: `Edge_${componentName}`,
314+
data: tipEdgeData,
315+
borderColor: colors[(colorIndex + 1) % colors.length],
316+
backgroundColor: colors[(colorIndex + 1) % colors.length],
317+
showLine: true,
318+
pointRadius: 2,
319+
pointHoverRadius: 4,
320+
borderWidth: 2,
321+
borderDash: [5, 5], // Dashed line to distinguish from Flap
322+
})
323+
}
324+
325+
colorIndex += 2 // Increment by 2 since we use 2 colors per component
326+
}
327+
328+
const options: ChartOptions<'scatter'> = {
329+
responsive: true,
330+
maintainAspectRatio: false,
331+
plugins: {
332+
legend: {
333+
display: true,
334+
position: 'right',
335+
labels: {
336+
font: { size: 10 }
337+
}
338+
},
339+
title: {
340+
display: true,
341+
text: 'Blade Tip Deflection',
342+
font: { size: 16, weight: 'bold' }
343+
}
344+
},
345+
scales: {
346+
x: {
347+
title: { display: true, text: 'Frames', font: { size: 14 } },
348+
ticks: { font: { size: 12 } },
349+
grid: {
350+
color: '#e0e0e0',
351+
}
352+
},
353+
y: {
354+
title: { display: true, text: 'Tip Deflection', font: { size: 14 } },
355+
ticks: { font: { size: 12 } },
356+
grid: {
357+
color: '#e0e0e0',
358+
}
359+
},
360+
},
361+
interaction: {
362+
mode: 'nearest',
363+
intersect: false
364+
},
365+
animation: { duration: 0 }
366+
}
367+
368+
return { data: { datasets }, options }
369+
})
370+
244371
245372
</script>
246373

@@ -529,10 +656,15 @@ const charts = computed(() => {
529656
<div class="card-body">
530657
<div class="row">
531658
<div class="col-10">
659+
<!-- 3D Mode Visualization -->
532660
<div style="width:100%; height: 80vh">
533661
<ModeViz :ModeData="project.modeViz[project.currentVizID]" :showNodePaths="showNodePaths">
534662
</ModeViz>
535663
</div>
664+
<!-- 2D Blade Tip Deflection Chart -->
665+
<div style="width:100%; height: 30vh" class="mt-3">
666+
<Scatter ref="bladeTipChart" :options="bladeTipChart.options" :data="bladeTipChart.data" />
667+
</div>
536668
</div>
537669
<div class="col-2">
538670
<div class="d-grid gap-2 mb-3">

frontend/wailsjs/go/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1546,6 +1546,7 @@ export namespace viz {
15461546
}
15471547
export class Component {
15481548
Line: Point[];
1549+
LocalLine: Point[];
15491550

15501551
static createFrom(source: any = {}) {
15511552
return new Component(source);
@@ -1554,6 +1555,7 @@ export namespace viz {
15541555
constructor(source: any = {}) {
15551556
if ('string' === typeof source) source = JSON.parse(source);
15561557
this.Line = this.convertValues(source["Line"], Point);
1558+
this.LocalLine = this.convertValues(source["LocalLine"], Point);
15571559
}
15581560

15591561
convertValues(a: any, classs: any, asMap: boolean = false): any {

viz/viz.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ type Point struct {
2727
}
2828

2929
type Component struct {
30-
Line []Point `json:"Line"`
30+
Line []Point `json:"Line"`
31+
LocalLine []Point `json:"LocalLine"`
3132
}
3233

3334
type Frame struct {
@@ -203,6 +204,7 @@ func (opts *Options) GenerateModeData(execPath string, op *lin.LinOP, modeIDs []
203204

204205
// Parse mode data from files
205206
md, err := ParseModeData(vtpFilePaths)
207+
206208
if err != nil {
207209
return nil, err
208210
}
@@ -219,14 +221,15 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) {
219221

220222
// Loop through files
221223
for _, vtpFile := range vtpFilePaths {
224+
fmt.Println("\nProcessing file:", vtpFile)
222225

223226
// Skip BD blade rotating states files
224227
if strings.Contains(filepath.Base(vtpFile), "BD_BldMotionRot") {
225228
continue
226229
}
227230

228231
// Load vtk file
229-
vtk, err := LoadVTK(vtpFile)
232+
vtk, local_vtk, err := LoadVTK(vtpFile)
230233
if err != nil {
231234
return nil, err
232235
}
@@ -296,6 +299,14 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) {
296299
for j, c := range conn {
297300
copy(component.Line[j].XYZ[:], vtk.PolyData.Piece.Points.DataArray.MatrixF32[c])
298301
}
302+
303+
// Copy local line data into component
304+
if local_vtk != nil {
305+
component.LocalLine = make([]Point, len(conn))
306+
for j, c := range conn {
307+
copy(component.LocalLine[j].XYZ[:], local_vtk.PolyData.Piece.Points.DataArray.MatrixF32[c])
308+
}
309+
}
299310
}
300311

301312
return &mv, nil

viz/viz_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ package viz_test
22

33
import (
44
"acdc/viz"
5+
"fmt"
6+
"os"
57
"testing"
8+
9+
"github.com/wcharczuk/go-chart/v2" // This can be deleted later after rendering the graph in the frontend. Need v2 to show axes labels.
610
)
711

812
func TestBuildModeViz(t *testing.T) {
@@ -14,8 +18,86 @@ func TestBuildModeViz(t *testing.T) {
1418
"testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.004.vtp",
1519
"testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.005.vtp",
1620
})
21+
1722
if err != nil {
1823
t.Fatal(err)
1924
}
2025
t.Logf("%+v", data)
26+
27+
PlotTipDeflection(data)
28+
}
29+
30+
func PlotTipDeflection(data *viz.ModeData) {
31+
32+
fmt.Println(data.Frames)
33+
34+
// Get Component names - BD_BldMotion1, BD_BldMotion2, BD_BldMotion3.
35+
componentNames := make(map[string]struct{})
36+
for _, frame := range data.Frames {
37+
for k := range frame.Components {
38+
componentNames[k] = struct{}{}
39+
}
40+
}
41+
fmt.Println("Component Names:", componentNames)
42+
seriesList := make([]chart.Series, 0, len(componentNames)*2)
43+
44+
// Loop over each blade
45+
for componentName := range componentNames {
46+
fmt.Println("Adding series of ", componentName)
47+
// For the Blade Tip, we only need the last point of each frame
48+
tipFlap := make([]float64, len(data.Frames))
49+
tipEdge := make([]float64, len(data.Frames))
50+
frames := make([]float64, len(data.Frames))
51+
for i, frame := range data.Frames {
52+
frames[i] = float64(i + 1) // Frame numbers start from 1
53+
if component, ok := frame.Components[componentName]; ok {
54+
if len(component.LocalLine) > 0 {
55+
tipFlap[i] = float64(component.LocalLine[len(component.LocalLine)-1].XYZ[0]) // X coordinate of the last point
56+
tipEdge[i] = float64(component.LocalLine[len(component.LocalLine)-1].XYZ[1]) // Y coordinate of the last point
57+
}
58+
}
59+
}
60+
61+
fmt.Println("Frames:", frames)
62+
fmt.Println("Tip Flap:", tipFlap)
63+
fmt.Println("Tip Edge:", tipEdge)
64+
65+
// Create a new series
66+
flapSeries := chart.ContinuousSeries{
67+
Name: "Flap_" + componentName,
68+
XValues: frames,
69+
YValues: tipFlap,
70+
}
71+
72+
edgeSeries := chart.ContinuousSeries{
73+
Name: "Edge_" + componentName,
74+
XValues: frames,
75+
YValues: tipEdge,
76+
}
77+
78+
seriesList = append(seriesList, flapSeries, edgeSeries)
79+
}
80+
81+
// Create a new chart
82+
graph := chart.Chart{
83+
Title: "",
84+
Series: seriesList,
85+
XAxis: chart.XAxis{
86+
Name: "Frames",
87+
},
88+
YAxis: chart.YAxis{
89+
Name: "Tip Deflection",
90+
},
91+
}
92+
93+
// Add the legend to the chart
94+
graph.Elements = []chart.Renderable{
95+
chart.Legend(&graph),
96+
}
97+
98+
// Save the plot as a file
99+
f, _ := os.Create("output.png")
100+
defer f.Close()
101+
graph.Render(chart.PNG, f)
102+
21103
}

0 commit comments

Comments
 (0)