From 89113d231a1aec18159683fbe87a4aa117ee5a96 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Wed, 2 Jul 2025 16:12:56 -0600 Subject: [PATCH 01/15] change struct Polys to PointData for proper parsing --- viz/vtk.go | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/viz/vtk.go b/viz/vtk.go index ea8fbba..23ac03f 100644 --- a/viz/vtk.go +++ b/viz/vtk.go @@ -2,6 +2,7 @@ package viz import ( "encoding/xml" + "fmt" "os" "strconv" "strings" @@ -26,7 +27,7 @@ type Piece struct { NumberOfPolys int `xml:",attr"` Points Points Lines Lines - Polys Polys + PointData PointData } type Points struct { @@ -37,7 +38,7 @@ type Lines struct { DataArray []DataArray `xml:"DataArray"` } -type Polys struct { +type PointData struct { DataArray []DataArray `xml:"DataArray"` } @@ -60,6 +61,8 @@ func (da *DataArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error return err } + fmt.Println("decode element: ", d) + // Split the raw values into a slice of strings (space-separated values) valueStrings := strings.Fields(da.RawValues) numValues := len(valueStrings) @@ -117,5 +120,17 @@ func LoadVTK(path string) (*VTKFile, error) { return nil, err } + fmt.Println("vf:", vf.PolyData) + fmt.Println(vf.PolyData.Piece.NumberOfLines, "lines") + fmt.Println(vf.PolyData.Piece.Points.DataArray) + fmt.Println(vf.PolyData.Piece.PointData.DataArray) + + // Find DataArray that has the name of "OrientationX" + for _, da := range vf.PolyData.Piece.PointData.DataArray { + if da.Name == "OrientationX" { + fmt.Println("Found OrientationX:", da) + } + } + return vf, nil } From dbe267681a4c76a291896adb3a4841c961707ce7 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Sun, 13 Jul 2025 23:38:13 -0600 Subject: [PATCH 02/15] extract orientation vectors from vtp file --- viz/viz.go | 3 ++- viz/vtk.go | 43 ++++++++++++++++++++++++++++++++++--------- 2 files changed, 36 insertions(+), 10 deletions(-) diff --git a/viz/viz.go b/viz/viz.go index 237fcce..4f8182f 100644 --- a/viz/viz.go +++ b/viz/viz.go @@ -226,10 +226,11 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { } // Load vtk file - vtk, err := LoadVTK(vtpFile) + vtk, ov, err := LoadVTK(vtpFile) if err != nil { return nil, err } + fmt.Println("Loaded Orientation Vectors:", ov) // Skip files without lines // TODO: add handling files only containing points diff --git a/viz/vtk.go b/viz/vtk.go index 23ac03f..ec427aa 100644 --- a/viz/vtk.go +++ b/viz/vtk.go @@ -15,6 +15,12 @@ type VTKFile struct { PolyData PolyData `xml:"PolyData"` } +type OrientationVectors struct { + X [][]float32 `json:"x"` // nx3 matrix for X orientation vectors + Y [][]float32 `json:"y"` // nx3 matrix for Y orientation vectors + Z [][]float32 `json:"z"` // nx3 matrix for Z orientation vectors +} + type PolyData struct { Piece Piece } @@ -107,30 +113,49 @@ func (da *DataArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error return nil } -func LoadVTK(path string) (*VTKFile, error) { +func LoadVTK(path string) (*VTKFile, *OrientationVectors, error) { bs, err := os.ReadFile(path) if err != nil { - return nil, err + return nil, nil, err } vf := &VTKFile{} if err = xml.Unmarshal(bs, vf); err != nil { - return nil, err + return nil, nil, err } - fmt.Println("vf:", vf.PolyData) - fmt.Println(vf.PolyData.Piece.NumberOfLines, "lines") - fmt.Println(vf.PolyData.Piece.Points.DataArray) - fmt.Println(vf.PolyData.Piece.PointData.DataArray) + // Debug: Print the structure + fmt.Printf("NumberOfPoints: %d\n", vf.PolyData.Piece.NumberOfPoints) + fmt.Printf("Points DataArray Type: %s, Components: %d\n", vf.PolyData.Piece.Points.DataArray.Type, vf.PolyData.Piece.Points.DataArray.NumberOfComponents) + fmt.Printf("Points DataArray Values: %v\n", vf.PolyData.Piece.Points.DataArray.MatrixF32) + + // Create orientation vectors + orientationVectors := &OrientationVectors{} + + // Debug: Print all available DataArrays + fmt.Printf("Number of PointData arrays: %d\n", len(vf.PolyData.Piece.PointData.DataArray)) + for i, da := range vf.PolyData.Piece.PointData.DataArray { + fmt.Printf("DataArray[%d]: Name='%s', Type='%s', Components=%d\n", i, da.Name, da.Type, da.NumberOfComponents) + } // Find DataArray that has the name of "OrientationX" for _, da := range vf.PolyData.Piece.PointData.DataArray { if da.Name == "OrientationX" { - fmt.Println("Found OrientationX:", da) + fmt.Println("Found OrientationX:", da.MatrixF32) + orientationVectors.X = da.MatrixF32 + } else if da.Name == "OrientationY" { + fmt.Println("Found OrientationY:", da.MatrixF32) + orientationVectors.Y = da.MatrixF32 + } else if da.Name == "OrientationZ" { + fmt.Println("Found OrientationZ:", da.MatrixF32) + orientationVectors.Z = da.MatrixF32 + } else if da.Name == "OrientationZ" { + fmt.Println("Found OrientationZ:", da.MatrixF32) + orientationVectors.Z = da.MatrixF32 } } - return vf, nil + return vf, orientationVectors, nil } From 0c3257ffd58c6704187faabfbb291595758fec2f Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Mon, 14 Jul 2025 23:11:05 -0600 Subject: [PATCH 03/15] convert global to local coords --- viz/viz.go | 5 +- viz/vtk.go | 131 +++++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/viz/viz.go b/viz/viz.go index 4f8182f..82b4836 100644 --- a/viz/viz.go +++ b/viz/viz.go @@ -226,11 +226,12 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { } // Load vtk file - vtk, ov, err := LoadVTK(vtpFile) + vtk, _, err := LoadVTK(vtpFile) if err != nil { return nil, err } - fmt.Println("Loaded Orientation Vectors:", ov) + + // fmt.Println("Local VTK:", local_vtk) // Skip files without lines // TODO: add handling files only containing points diff --git a/viz/vtk.go b/viz/vtk.go index ec427aa..d50d33a 100644 --- a/viz/vtk.go +++ b/viz/vtk.go @@ -21,6 +21,12 @@ type OrientationVectors struct { Z [][]float32 `json:"z"` // nx3 matrix for Z orientation vectors } +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 +} + type PolyData struct { Piece Piece } @@ -113,7 +119,7 @@ func (da *DataArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error return nil } -func LoadVTK(path string) (*VTKFile, *OrientationVectors, error) { +func LoadVTK(path string) (*VTKFile, *VTKFile, error) { bs, err := os.ReadFile(path) if err != nil { @@ -126,36 +132,115 @@ func LoadVTK(path string) (*VTKFile, *OrientationVectors, error) { return nil, nil, err } - // Debug: Print the structure - fmt.Printf("NumberOfPoints: %d\n", vf.PolyData.Piece.NumberOfPoints) - fmt.Printf("Points DataArray Type: %s, Components: %d\n", vf.PolyData.Piece.Points.DataArray.Type, vf.PolyData.Piece.Points.DataArray.NumberOfComponents) - fmt.Printf("Points DataArray Values: %v\n", vf.PolyData.Piece.Points.DataArray.MatrixF32) + local_vf, err := Global2Local(vf) + if err != nil { + return nil, nil, fmt.Errorf("Failed to convert global coordinates to local: %w", err) + } - // Create orientation vectors - orientationVectors := &OrientationVectors{} + return vf, local_vf, nil +} - // Debug: Print all available DataArrays - fmt.Printf("Number of PointData arrays: %d\n", len(vf.PolyData.Piece.PointData.DataArray)) - for i, da := range vf.PolyData.Piece.PointData.DataArray { - fmt.Printf("DataArray[%d]: Name='%s', Type='%s', Components=%d\n", i, da.Name, da.Type, da.NumberOfComponents) - } +func GetOrientations(vf *VTKFile) (*OrientationVectors, *OrientationMatrix, error) { + // Create orientation vectors + ov := &OrientationVectors{} - // Find DataArray that has the name of "OrientationX" + // Find Orientation vectors for _, da := range vf.PolyData.Piece.PointData.DataArray { if da.Name == "OrientationX" { - fmt.Println("Found OrientationX:", da.MatrixF32) - orientationVectors.X = da.MatrixF32 + ov.X = da.MatrixF32 } else if da.Name == "OrientationY" { - fmt.Println("Found OrientationY:", da.MatrixF32) - orientationVectors.Y = da.MatrixF32 + ov.Y = da.MatrixF32 } else if da.Name == "OrientationZ" { - fmt.Println("Found OrientationZ:", da.MatrixF32) - orientationVectors.Z = da.MatrixF32 - } else if da.Name == "OrientationZ" { - fmt.Println("Found OrientationZ:", da.MatrixF32) - orientationVectors.Z = da.MatrixF32 + ov.Z = da.MatrixF32 } } - return vf, orientationVectors, nil + // 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 +} + +func Global2Local(vf *VTKFile) (*VTKFile, error) { + + // Copy vf + local_vf := vf // Shallow copy + 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) + if err != nil { + 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 + fmt.Println("\nLocal coordinates:", 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 +} + +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++ { + resPoint[j] = point[j] + (-translation[j]) + } + result[i] = resPoint + } + return result +} + +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++ { + resVec[j] += vec[k] * matrix[k][j] + } + } + result[i] = resVec + } + return result +} + +func TransposeMatrix(om *OrientationMatrix) [][]float32 { + // Transpose the orientation matrix + if len(om.X) == 0 { + return nil + } + transposed := make([][]float32, 3) + transposed[0] = []float32{om.X[0], om.Y[0], om.Z[0]} + transposed[1] = []float32{om.X[1], om.Y[1], om.Z[1]} + transposed[2] = []float32{om.X[2], om.Y[2], om.Z[2]} + + return transposed } From 3b5c9e374c4d45ef9116324e09d69613546d29d7 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Tue, 15 Jul 2025 12:38:19 -0600 Subject: [PATCH 04/15] add local coords to mode data --- viz/viz.go | 20 ++++++++++++++++---- viz/vtk.go | 35 ++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/viz/viz.go b/viz/viz.go index 82b4836..526e547 100644 --- a/viz/viz.go +++ b/viz/viz.go @@ -27,7 +27,8 @@ type Point struct { } type Component struct { - Line []Point `json:"Line"` + Line []Point `json:"Line"` + LocalLine []Point `json:"Line"` } type Frame struct { @@ -203,6 +204,8 @@ func (opts *Options) GenerateModeData(execPath string, op *lin.LinOP, modeIDs [] // Parse mode data from files md, err := ParseModeData(vtpFilePaths) + fmt.Println("Mode Data:", md) + if err != nil { return nil, err } @@ -219,6 +222,7 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { // Loop through files for _, vtpFile := range vtpFilePaths { + fmt.Println("\nProcessing file:", vtpFile) // Skip BD blade rotating states files if strings.Contains(filepath.Base(vtpFile), "BD_BldMotionRot") { @@ -226,13 +230,11 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { } // Load vtk file - vtk, _, err := LoadVTK(vtpFile) + vtk, local_vtk, err := LoadVTK(vtpFile) if err != nil { return nil, err } - // fmt.Println("Local VTK:", local_vtk) - // Skip files without lines // TODO: add handling files only containing points if vtk.PolyData.Piece.NumberOfLines == 0 { @@ -298,6 +300,16 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { for j, c := range conn { copy(component.Line[j].XYZ[:], vtk.PolyData.Piece.Points.DataArray.MatrixF32[c]) } + + fmt.Println(vtk.PolyData.Piece.Points.DataArray.MatrixF32) + + // Add local coordinates + component.LocalLine = make([]Point, len(conn)) + for j, c := range conn { + copy(component.LocalLine[j].XYZ[:], local_vtk.PolyData.Piece.Points.DataArray.MatrixF32[c]) + } + + fmt.Println(local_vtk.PolyData.Piece.Points.DataArray.MatrixF32) } return &mv, nil diff --git a/viz/vtk.go b/viz/vtk.go index d50d33a..b69beb8 100644 --- a/viz/vtk.go +++ b/viz/vtk.go @@ -1,6 +1,7 @@ package viz import ( + "encoding/json" "encoding/xml" "fmt" "os" @@ -73,7 +74,7 @@ func (da *DataArray) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error return err } - fmt.Println("decode element: ", d) + // fmt.Println("decode element: ", d) // Split the raw values into a slice of strings (space-separated values) valueStrings := strings.Fields(da.RawValues) @@ -136,6 +137,8 @@ func LoadVTK(path string) (*VTKFile, *VTKFile, error) { if err != nil { return nil, nil, fmt.Errorf("Failed to convert global coordinates to local: %w", err) } + fmt.Println(vf.PolyData.Piece.Points.DataArray.MatrixF32) + fmt.Println(local_vf.PolyData.Piece.Points.DataArray.MatrixF32) return vf, local_vf, nil } @@ -172,36 +175,38 @@ func GetOrientations(vf *VTKFile) (*OrientationVectors, *OrientationMatrix, erro func Global2Local(vf *VTKFile) (*VTKFile, error) { - // Copy vf - local_vf := vf // Shallow copy + // Copy vf (Deep Copy -- so that it works independently) + var local_vf *VTKFile + err := DeepCopy(&vf, &local_vf) local_coords := local_vf.PolyData.Piece.Points.DataArray.MatrixF32 - fmt.Println("\nLocal coordinates:", local_coords) + // fmt.Println("\nLocal coordinates:", local_coords) // Get Orientation vectors and matrices ov, om, err := GetOrientations(local_vf) if err != nil { return nil, fmt.Errorf("Failed to extract orientation vectors and matrices: %w", err) } - fmt.Println("\nOrientation Matrix:", om) + // 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) + // fmt.Println("\nLocal coordinates:", local_coords) transposed_om := TransposeMatrix(om) - fmt.Println("\nTransposed Orientation Matrix:", transposed_om) + // fmt.Println("\nTransposed Orientation Matrix:", transposed_om) local_coords = DotProduct(local_coords, transposed_om) // Rotate by the first orientation vector - fmt.Println("\nLocal coordinates:", local_coords) + 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) + // fmt.Println("\nOrientationX:", ov.X) + // fmt.Println("\nOrientationY:", ov.Y) + // fmt.Println("\nOrientationZ:", ov.Z) return local_vf, nil } @@ -244,3 +249,11 @@ func TransposeMatrix(om *OrientationMatrix) [][]float32 { return transposed } + +func DeepCopy(src, dst interface{}) error { + bytes, err := json.Marshal(src) + if err != nil { + return err + } + return json.Unmarshal(bytes, dst) +} From 5019d077f6c293cb3aefcc48690fd103c48c35ec Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Tue, 15 Jul 2025 14:29:20 -0600 Subject: [PATCH 05/15] bug fix: Duplicate identifier 'Line' --- frontend/wailsjs/go/models.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 0691bb8..4d972b0 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1502,6 +1502,7 @@ export namespace viz { } export class Component { Line: Point[]; + LocalLine: Point[]; static createFrom(source: any = {}) { return new Component(source); @@ -1510,6 +1511,7 @@ export namespace viz { constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.Line = this.convertValues(source["Line"], Point); + this.LocalLine = this.convertValues(source["LocalLine"], Point); } convertValues(a: any, classs: any, asMap: boolean = false): any { From 329702cdec520a4dae622e9de09d3f5e2cb14de3 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Tue, 15 Jul 2025 16:12:04 -0600 Subject: [PATCH 06/15] fix bug on Line duplicate identifier --- viz/viz.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viz/viz.go b/viz/viz.go index 526e547..8801b3d 100644 --- a/viz/viz.go +++ b/viz/viz.go @@ -28,7 +28,7 @@ type Point struct { type Component struct { Line []Point `json:"Line"` - LocalLine []Point `json:"Line"` + LocalLine []Point `json:"LocalLine"` } type Frame struct { From 1333827f13566d3037543274d03dd301ee6076d7 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Tue, 15 Jul 2025 16:31:01 -0600 Subject: [PATCH 07/15] add blade tip deflection plotting function --- viz/viz_test.go | 161 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 156 insertions(+), 5 deletions(-) diff --git a/viz/viz_test.go b/viz/viz_test.go index 84bdf24..804bbe5 100644 --- a/viz/viz_test.go +++ b/viz/viz_test.go @@ -2,20 +2,171 @@ package viz_test import ( "acdc/viz" + "fmt" + "os" "testing" + + "github.com/wcharczuk/go-chart/v2" // This can be deleted later after rendering the graph in the frontend. Need v2 to show axes labels. ) func TestBuildModeViz(t *testing.T) { data, err := viz.ParseModeData([]string{ - "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.001.vtp", - "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.002.vtp", - "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.003.vtp", - "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.004.vtp", - "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.005.vtp", + // "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.001.vtp", + // "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.002.vtp", + // "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.003.vtp", + // "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.004.vtp", + // "testdata/03_NREL_5MW-ED.Mode1.LinTime1.ED_TowerLn2Mesh_motion.005.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.001.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.002.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.003.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.004.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.005.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.006.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.007.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.008.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.009.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.010.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.011.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.012.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.013.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.014.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.015.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.016.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.017.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.018.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.019.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.020.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion1.021.vtp", + + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.001.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.002.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.003.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.004.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.005.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.006.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.007.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.008.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.009.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.010.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.011.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.012.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.013.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.014.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.015.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.016.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.017.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.018.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.019.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.020.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion2.021.vtp", + + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.001.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.002.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.003.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.004.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.005.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.006.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.007.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.008.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.009.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.010.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.011.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.012.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.013.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.014.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.015.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.016.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.017.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.018.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.019.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.020.vtp", + "../../ATLAS/atlas_example/Case01/vtk/04_IEA-15-240-RWT-Monopile.Mode1.LinTime1.BD_BldMotion3.021.vtp", }) + + // fmt.Println("\nData:", data) + // fmt.Println("\nGlobal line: ", data.Frames[0].Components["BD_BldMotion2"].Line) + // fmt.Println("\nLocal line: ", data.Frames[0].Components["BD_BldMotion2"].LocalLine) + if err != nil { t.Fatal(err) } t.Logf("%+v", data) + + PlotTipDeflection(data) +} + +func PlotTipDeflection(data *viz.ModeData) { + + fmt.Println(data.Frames) + + // Get Component names - BD_BldMotion1, BD_BldMotion2, BD_BldMotion3. + componentNames := make(map[string]struct{}) + for _, frame := range data.Frames { + for k := range frame.Components { + componentNames[k] = struct{}{} + } + } + fmt.Println("Component Names:", componentNames) + seriesList := make([]chart.Series, 0, len(componentNames)*2) + + // Loop over each blade + for componentName := range componentNames { + fmt.Println("Adding series of ", componentName) + // For the Blade Tip, we only need the last point of each frame + tipFlap := make([]float64, len(data.Frames)) + tipEdge := make([]float64, len(data.Frames)) + frames := make([]float64, len(data.Frames)) + for i, frame := range data.Frames { + frames[i] = float64(i + 1) // Frame numbers start from 1 + if component, ok := frame.Components[componentName]; ok { + if len(component.LocalLine) > 0 { + tipFlap[i] = float64(component.LocalLine[len(component.LocalLine)-1].XYZ[0]) // X coordinate of the last point + tipEdge[i] = float64(component.LocalLine[len(component.LocalLine)-1].XYZ[1]) // Y coordinate of the last point + } + } + } + + fmt.Println("Frames:", frames) + fmt.Println("Tip Flap:", tipFlap) + fmt.Println("Tip Edge:", tipEdge) + + // Create a new series + flapSeries := chart.ContinuousSeries{ + Name: "Flap_" + componentName, + XValues: frames, + YValues: tipFlap, + } + + edgeSeries := chart.ContinuousSeries{ + Name: "Edge_" + componentName, + XValues: frames, + YValues: tipEdge, + } + + seriesList = append(seriesList, flapSeries, edgeSeries) + } + + // Create a new chart + graph := chart.Chart{ + Title: "", + Series: seriesList, + XAxis: chart.XAxis{ + Name: "Frames", + }, + YAxis: chart.YAxis{ + Name: "Tip Deflection", + }, + } + + // Add the legend to the chart + graph.Elements = []chart.Renderable{ + chart.Legend(&graph), + } + + // Save the plot as a file + f, _ := os.Create("output.png") + defer f.Close() + graph.Render(chart.PNG, f) + } From 9e912ecd91af172b26482f7c6d252af23cd10418 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Mon, 15 Sep 2025 15:20:47 -0600 Subject: [PATCH 08/15] add 2d dummy line plot --- frontend/src/components/ModeViz.vue | 163 +++++++++++++++++++++++++--- frontend/src/components/Results.vue | 2 +- 2 files changed, 147 insertions(+), 18 deletions(-) diff --git a/frontend/src/components/ModeViz.vue b/frontend/src/components/ModeViz.vue index b00a431..ac1aff0 100644 --- a/frontend/src/components/ModeViz.vue +++ b/frontend/src/components/ModeViz.vue @@ -32,6 +32,110 @@ let clock = new THREE.Clock(); let delta = 0 const FOV = 10 +// 2D Plot scene and group +let plot2DScene: THREE.Scene; +let plotGroup: THREE.Group; + +function create2DPlot() { + plot2DScene = new THREE.Scene(); + plotGroup = new THREE.Group(); + + // Create sine wave data + const points: THREE.Vector3[] = []; + const numPoints = 100; + const amplitude = 80; + const frequency = 0.1; // Increased frequency for better visualization + + for (let i = 0; i < numPoints; i++) { + const x = (i - numPoints/2) * 2; // X position from -100 to +100 + const xNormalized = x / 100 * Math.PI * 2; // Convert to radians for sine function + const y = amplitude * Math.sin(xNormalized); // Now sine starts at 0 when x=0 + points.push(new THREE.Vector3(x, y, 0)); + } + + // Create the sine wave line + const lineGeometry = new THREE.BufferGeometry().setFromPoints(points); + const lineMaterial = new THREE.LineBasicMaterial({ + color: 0x00ff00, + linewidth: 3 + }); + const line = new THREE.Line(lineGeometry, lineMaterial); + plotGroup.add(line); + + // Create axes + const axesMaterial = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 }); + + // X-axis + const xAxisGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(-100, 0, 0), + new THREE.Vector3(100, 0, 0) + ]); + const xAxis = new THREE.Line(xAxisGeometry, axesMaterial); + plotGroup.add(xAxis); + + // Y-axis + const yAxisGeometry = new THREE.BufferGeometry().setFromPoints([ + new THREE.Vector3(0, -100, 0), + new THREE.Vector3(0, 100, 0) + ]); + const yAxis = new THREE.Line(yAxisGeometry, axesMaterial); + plotGroup.add(yAxis); + + // Add X and Y labels using sprites + // X-label + const xLabelCanvas = document.createElement('canvas'); + const xLabelContext = xLabelCanvas.getContext('2d')!; + xLabelCanvas.width = 128; + xLabelCanvas.height = 64; + xLabelContext.fillStyle = '#ffffff'; + xLabelContext.font = '24px Arial'; + xLabelContext.textAlign = 'center'; + xLabelContext.fillText('Time (s)', 64, 40); + + const xLabelTexture = new THREE.CanvasTexture(xLabelCanvas); + const xLabelMaterial = new THREE.SpriteMaterial({ map: xLabelTexture }); + const xLabel = new THREE.Sprite(xLabelMaterial); + xLabel.position.set(0, -120, 0); + xLabel.scale.set(60, 30, 1); + plotGroup.add(xLabel); + + // Y-label + const yLabelCanvas = document.createElement('canvas'); + const yLabelContext = yLabelCanvas.getContext('2d')!; + yLabelCanvas.width = 128; + yLabelCanvas.height = 64; + yLabelContext.fillStyle = '#ffffff'; + yLabelContext.font = '24px Arial'; + yLabelContext.textAlign = 'center'; + yLabelContext.fillText('Amplitude', 64, 40); + + const yLabelTexture = new THREE.CanvasTexture(yLabelCanvas); + const yLabelMaterial = new THREE.SpriteMaterial({ map: yLabelTexture }); + const yLabel = new THREE.Sprite(yLabelMaterial); + yLabel.position.set(-120, 0, 0); + yLabel.scale.set(60, 30, 1); + plotGroup.add(yLabel); + + // Title + const titleCanvas = document.createElement('canvas'); + const titleContext = titleCanvas.getContext('2d')!; + titleCanvas.width = 256; + titleCanvas.height = 64; + titleContext.fillStyle = '#ffffff'; + titleContext.font = '28px Arial'; + titleContext.textAlign = 'center'; + titleContext.fillText('Sine Wave', 128, 40); + + const titleTexture = new THREE.CanvasTexture(titleCanvas); + const titleMaterial = new THREE.SpriteMaterial({ map: titleTexture }); + const title = new THREE.Sprite(titleMaterial); + title.position.set(0, 120, 0); + title.scale.set(80, 40, 1); + plotGroup.add(title); + + plot2DScene.add(plotGroup); +} + function createFrames(modeData: viz.ModeData) { if (modeData.Frames == null) return scene.clear() @@ -96,6 +200,9 @@ function createFrames(modeData: viz.ModeData) { frameNum = 0 const axesHelper = new THREE.AxesHelper(frameSize.x / 2); scene.add(axesHelper) + + // Create 2D plot + create2DPlot(); } let scene: THREE.Scene; @@ -105,9 +212,9 @@ const views = [ { // Top View left: 0, - bottom: 0.705, + bottom: 0.805, width: 0.4, - height: 0.30, + height: 0.20, up: [1, 0, 0], updateCamera: function (camera: THREE.PerspectiveCamera) { // Calculate distance along Z axis to fit model in frame horizontally @@ -122,14 +229,14 @@ const views = [ { // Front View left: 0, - bottom: 0, + bottom: 0.204, width: 0.4, - height: 0.70, + height: 0.60, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { // Calculate distance along -X axis to fit model in frame vertically // See https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/ for equation - let distance = 1.05 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) + let distance = 1.15 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) camera.position.fromArray([-distance, 0, frameCenter.z]); // Looking along X (downwind) camera.lookAt(frameCenter); }, @@ -138,13 +245,13 @@ const views = [ { // Side View left: 0.402, - bottom: 0, - width: 0.3, - height: 0.70, + bottom: 0.204, + width: 0.25, + height: 0.60, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { // Calculate distance along -Y axis to fit model in frame vertically - let distance = 1.05 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) + let distance = 1.15 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) camera.position.fromArray([0, -distance, frameCenter.z]); // Looking along -Y (side) camera.lookAt(frameCenter); }, @@ -153,19 +260,38 @@ const views = [ { // Isometric View - left: 0.704, - bottom: 0, - width: 0.3, - height: 1.0, + left: 0.654, + bottom: 0.204, + width: 0.35, + height: 0.8, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { // Calculate distance along Z axis to fit model in frame horizontally - let distanceFront = 0.8 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) - let distanceSide = 0.8 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) + let distanceFront = 1.0 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) + let distanceSide = 1.0 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) camera.position.fromArray([-distanceFront, -distanceSide, frameCenter.z + 3 * frameSize.z]); // Looking along -Z (downward) camera.lookAt(frameCenter); }, camera: new THREE.PerspectiveCamera, + }, + + { + // 2D Sine Wave Plot + left: 0, + bottom: 0, + width: 1.0, + height: 0.2, + up: [0, 1, 0], + scene: null, // Will be set to plot2DScene + updateCamera: function (camera: THREE.PerspectiveCamera) { + // Calculate distance along Z axis to fit model in frame horizontally + const fov = camera.fov * (Math.PI / 180); + const fovh = 2 * Math.atan(Math.tan(fov / 2) * camera.aspect); + let distance = 1.05 * (frameSize.y / 2 / Math.tan(fovh / 2) + frameSize.z) + camera.position.fromArray([0, 0, distance*20]); // Looking along -Z (downward) + camera.lookAt(0, 0, 0); + }, + camera: new THREE.PerspectiveCamera, } ]; @@ -196,6 +322,9 @@ function render() { const view = views[ii]; const camera = view.camera; + // Use 2D plot scene for the last view + const sceneToRender = (ii === 4) ? plot2DScene : scene; + view.updateCamera(camera); const left = Math.floor(canvasWidth * view.left); @@ -210,7 +339,7 @@ function render() { camera.aspect = width / height; camera.updateProjectionMatrix(); - renderer.render(scene, camera); + renderer.render(sceneToRender, camera); } } @@ -241,4 +370,4 @@ onMounted(() => { - + \ No newline at end of file diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index 71e5d91..024664d 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -529,7 +529,7 @@ const charts = computed(() => {
-
+
From 1e21dfdab756b76506fae106c48220dc98c3c647 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Mon, 15 Sep 2025 16:29:49 -0600 Subject: [PATCH 09/15] make it into chart --- frontend/src/components/ModeViz.vue | 370 +++++++++++++++++----------- 1 file changed, 232 insertions(+), 138 deletions(-) diff --git a/frontend/src/components/ModeViz.vue b/frontend/src/components/ModeViz.vue index ac1aff0..4551673 100644 --- a/frontend/src/components/ModeViz.vue +++ b/frontend/src/components/ModeViz.vue @@ -1,5 +1,5 @@ - - \ No newline at end of file +
+ +
+ +
+ + +
+ +
+
+ \ No newline at end of file From 4e39eb2e29545492379b833743b2666511310214 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Mon, 15 Sep 2025 16:47:17 -0600 Subject: [PATCH 10/15] move blade tip chart to Results.vue to match Chart.js plotting style --- frontend/src/components/ModeViz.vue | 258 ++-------------------------- frontend/src/components/Results.vue | 82 ++++++++- 2 files changed, 95 insertions(+), 245 deletions(-) diff --git a/frontend/src/components/ModeViz.vue b/frontend/src/components/ModeViz.vue index 4551673..9091573 100644 --- a/frontend/src/components/ModeViz.vue +++ b/frontend/src/components/ModeViz.vue @@ -1,5 +1,5 @@ \ No newline at end of file + + + + \ No newline at end of file diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index 024664d..b490723 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -241,6 +241,81 @@ const charts = computed(() => { return objs }) +const bladeTipChart = computed(() => { + const configs = [ + { frequency: 0.05, amplitude: 70, phase: 0, label: 'sin(x)', color: '#1f77b4' }, + { frequency: 0.1, amplitude: 50, phase: 0, label: 'sin(2x)', color: '#ff7f0e' }, + { frequency: 0.15, amplitude: 40, phase: 0, label: 'sin(3x)', color: '#2ca02c' }, + { frequency: 0.08, amplitude: 30, phase: Math.PI/4, label: 'sin(1.6x)', color: '#d62728' } + ] + + let data = { datasets: [] } as ChartData<'scatter'> + + // Generate data for each sine wave + configs.forEach(config => { + const points: {x: number, y: number}[] = [] + for (let i = 0; i < 200; i++) { + const x = (i - 100) * 0.1 + const y = config.amplitude * Math.sin(x * (config.frequency / 0.05) + config.phase) + points.push({ x, y }) + } + + data.datasets.push({ + label: config.label, + data: points, + borderColor: config.color, + backgroundColor: config.color, + showLine: true, + pointRadius: 0, + pointHoverRadius: 4, + borderWidth: 2, + }) + }) + + const options: ChartOptions<'scatter'> = { + responsive: true, + maintainAspectRatio: false, + plugins: { + legend: { + display: true, + position: 'right', + labels: { + font: { size: 12 } + } + }, + title: { + display: true, + text: 'Blade Tip Deflection across Frames', + font: { size: 16, weight: 'bold' } + } + }, + scales: { + x: { + title: { display: true, text: 'Frames', font: { size: 14 } }, + ticks: { font: { size: 12 } }, + grid: { + color: '#e0e0e0', + } + }, + y: { + title: { display: true, text: 'Tip Deflection', font: { size: 14 } }, + ticks: { font: { size: 12 } }, + grid: { + color: '#e0e0e0', + } + }, + }, + interaction: { + mode: 'nearest', + intersect: false + }, + animation: { duration: 0 } + } + + return { data, options } +}) + + @@ -529,10 +604,15 @@ const charts = computed(() => {
-
+ +
+ +
+ +
From a07f64eccd4b865d27046e13d024074b88513a2d Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Mon, 15 Sep 2025 17:01:35 -0600 Subject: [PATCH 11/15] remove unnecessary changes + minor edit on layout styling --- frontend/src/components/ModeViz.vue | 55 ++++++++++++++++------------- frontend/src/components/Results.vue | 4 +-- 2 files changed, 33 insertions(+), 26 deletions(-) diff --git a/frontend/src/components/ModeViz.vue b/frontend/src/components/ModeViz.vue index 9091573..9cee9fc 100644 --- a/frontend/src/components/ModeViz.vue +++ b/frontend/src/components/ModeViz.vue @@ -53,9 +53,9 @@ function createFrames(modeData: viz.ModeData) { const material = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 1 }); const curveObject = new THREE.Line(geometry, material); frameGroup.add(curveObject) - allFramesGroup.add(curveObject.clone()) + allFramesGroup.add(curveObject.clone()) // Add clone of object to be used for view sizing } - frameGroup.visible = false + frameGroup.visible = false // Initialize each group to not visible for animation frames.push(frameGroup) scene.add(frameGroup) } @@ -105,15 +105,16 @@ const views = [ { // Top View left: 0, - bottom: 0.75, + bottom: 0.705, width: 0.4, - height: 0.25, + height: 0.30, up: [1, 0, 0], updateCamera: function (camera: THREE.PerspectiveCamera) { + // Calculate distance along Z axis to fit model in frame horizontally const fov = camera.fov * (Math.PI / 180); const fovh = 2 * Math.atan(Math.tan(fov / 2) * camera.aspect); let distance = 1.05 * (frameSize.y / 2 / Math.tan(fovh / 2) + frameSize.z) - camera.position.fromArray([0, 0, distance]); + camera.position.fromArray([0, 0, distance]); // Looking along -Z (downward) camera.lookAt(frameCenter); }, camera: new THREE.PerspectiveCamera, @@ -123,11 +124,13 @@ const views = [ left: 0, bottom: 0, width: 0.4, - height: 0.75, + height: 0.70, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { - let distance = 1.15 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) - camera.position.fromArray([-distance, 0, frameCenter.z]); + // Calculate distance along -X axis to fit model in frame vertically + // See https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/ for equation + let distance = 1.05 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) + camera.position.fromArray([-distance, 0, frameCenter.z]); // Looking along X (downwind) camera.lookAt(frameCenter); }, camera: new THREE.PerspectiveCamera, @@ -136,27 +139,30 @@ const views = [ // Side View left: 0.402, bottom: 0, - width: 0.25, - height: 0.75, + width: 0.3, + height: 0.70, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { - let distance = 1.15 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) - camera.position.fromArray([0, -distance, frameCenter.z]); + // Calculate distance along -Y axis to fit model in frame vertically + let distance = 1.05 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) + camera.position.fromArray([0, -distance, frameCenter.z]); // Looking along -Y (side) camera.lookAt(frameCenter); }, camera: new THREE.PerspectiveCamera, }, + { // Isometric View - left: 0.654, + left: 0.704, bottom: 0, - width: 0.35, + width: 0.3, height: 1.0, up: [0, 0, 1], updateCamera: function (camera: THREE.PerspectiveCamera) { - let distanceFront = 1.0 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) - let distanceSide = 1.0 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) - camera.position.fromArray([-distanceFront, -distanceSide, frameCenter.z + 3 * frameSize.z]); + // Calculate distance along Z axis to fit model in frame horizontally + let distanceFront = 0.8 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.x / 2) + let distanceSide = 0.8 * (frameSize.z / 2 / Math.tan(camera.fov * Math.PI / 180 / 2) + frameSize.y / 2) + camera.position.fromArray([-distanceFront, -distanceSide, frameCenter.z + 3 * frameSize.z]); // Looking along -Z (downward) camera.lookAt(frameCenter); }, camera: new THREE.PerspectiveCamera, @@ -168,17 +174,16 @@ function animate() { delta += clock.getDelta() if (delta > 1.5 / frames.length) { delta = 0 - if (frames.length > 0) { - frames[frameNum].visible = false; - frameNum++ - if (frameNum >= frames.length) frameNum = 0 - frames[frameNum].visible = true; - } + frames[frameNum].visible = false; + frameNum++ + if (frameNum >= frames.length) frameNum = 0 + frames[frameNum].visible = true; render(); } } function render() { + const canvas = renderer.domElement; const canvasWidth = canvas.clientWidth; const canvasHeight = canvas.clientHeight; @@ -187,6 +192,7 @@ function render() { } for (let ii = 0; ii < views.length; ++ii) { + const view = views[ii]; const camera = view.camera; @@ -195,7 +201,7 @@ function render() { const left = Math.floor(canvasWidth * view.left); const bottom = Math.floor(canvasHeight * view.bottom); const width = Math.floor(canvasWidth * view.width); - const height = Math.floor(canvasWidth * view.height); + const height = Math.floor(canvasHeight * view.height); renderer.setViewport(left, bottom, width, height); renderer.setScissor(left, bottom, width, height); @@ -209,6 +215,7 @@ function render() { } onMounted(() => { + const canvas = document.getElementById('modeVizCanvas')!; for (let ii = 0; ii < views.length; ++ii) { diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index b490723..0411db3 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -605,12 +605,12 @@ const bladeTipChart = computed(() => {
-
+
-
+
From a3e51f963fe73a2a0f248b5dc125a36762a5fb22 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Thu, 18 Sep 2025 20:31:47 -0600 Subject: [PATCH 12/15] skip non-blade components for local coords calculation + verify that it's running --- frontend/src/components/Results.vue | 2 +- frontend/wailsjs/go/models.ts | 4 ++-- viz/viz.go | 15 ++++++--------- viz/vtk.go | 8 ++++++-- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index 0411db3..8fc65a2 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -609,7 +609,7 @@ const bladeTipChart = computed(() => {
- +
diff --git a/frontend/wailsjs/go/models.ts b/frontend/wailsjs/go/models.ts index 4d972b0..51d38c2 100755 --- a/frontend/wailsjs/go/models.ts +++ b/frontend/wailsjs/go/models.ts @@ -1502,7 +1502,7 @@ export namespace viz { } export class Component { Line: Point[]; - LocalLine: Point[]; + LocalLine: Point[]; static createFrom(source: any = {}) { return new Component(source); @@ -1511,7 +1511,7 @@ export namespace viz { constructor(source: any = {}) { if ('string' === typeof source) source = JSON.parse(source); this.Line = this.convertValues(source["Line"], Point); - this.LocalLine = this.convertValues(source["LocalLine"], Point); + this.LocalLine = this.convertValues(source["LocalLine"], Point); } convertValues(a: any, classs: any, asMap: boolean = false): any { diff --git a/viz/viz.go b/viz/viz.go index 8801b3d..260e9ec 100644 --- a/viz/viz.go +++ b/viz/viz.go @@ -204,7 +204,6 @@ func (opts *Options) GenerateModeData(execPath string, op *lin.LinOP, modeIDs [] // Parse mode data from files md, err := ParseModeData(vtpFilePaths) - fmt.Println("Mode Data:", md) if err != nil { return nil, err @@ -301,15 +300,13 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) { copy(component.Line[j].XYZ[:], vtk.PolyData.Piece.Points.DataArray.MatrixF32[c]) } - fmt.Println(vtk.PolyData.Piece.Points.DataArray.MatrixF32) - - // Add local coordinates - component.LocalLine = make([]Point, len(conn)) - for j, c := range conn { - copy(component.LocalLine[j].XYZ[:], local_vtk.PolyData.Piece.Points.DataArray.MatrixF32[c]) + // Copy local line data into component + if local_vtk != nil { + component.LocalLine = make([]Point, len(conn)) + for j, c := range conn { + copy(component.LocalLine[j].XYZ[:], local_vtk.PolyData.Piece.Points.DataArray.MatrixF32[c]) + } } - - fmt.Println(local_vtk.PolyData.Piece.Points.DataArray.MatrixF32) } return &mv, nil diff --git a/viz/vtk.go b/viz/vtk.go index b69beb8..da05d61 100644 --- a/viz/vtk.go +++ b/viz/vtk.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "fmt" "os" + "path/filepath" "strconv" "strings" ) @@ -133,12 +134,15 @@ func LoadVTK(path string) (*VTKFile, *VTKFile, error) { return nil, nil, err } + // Skip for non-blade files for local coords conversion + if !strings.Contains(filepath.Base(path), "Blade") { + return vf, nil, nil + } + local_vf, err := Global2Local(vf) if err != nil { return nil, nil, fmt.Errorf("Failed to convert global coordinates to local: %w", err) } - fmt.Println(vf.PolyData.Piece.Points.DataArray.MatrixF32) - fmt.Println(local_vf.PolyData.Piece.Points.DataArray.MatrixF32) return vf, local_vf, nil } From 0db064c25bc2facd214ad5986648a0e2b6de9a90 Mon Sep 17 00:00:00 2001 From: sora-ryu Date: Thu, 18 Sep 2025 22:03:41 -0600 Subject: [PATCH 13/15] plot blade tip deflections with local line data --- frontend/src/components/Results.vue | 118 ++++++++++++++++++++-------- 1 file changed, 85 insertions(+), 33 deletions(-) diff --git a/frontend/src/components/Results.vue b/frontend/src/components/Results.vue index 8fc65a2..6bd3d93 100644 --- a/frontend/src/components/Results.vue +++ b/frontend/src/components/Results.vue @@ -241,36 +241,89 @@ const charts = computed(() => { return objs }) +// Add Blade Tip Deflection Chart with Local Line Data const bladeTipChart = computed(() => { - const configs = [ - { frequency: 0.05, amplitude: 70, phase: 0, label: 'sin(x)', color: '#1f77b4' }, - { frequency: 0.1, amplitude: 50, phase: 0, label: 'sin(2x)', color: '#ff7f0e' }, - { frequency: 0.15, amplitude: 40, phase: 0, label: 'sin(3x)', color: '#2ca02c' }, - { frequency: 0.08, amplitude: 30, phase: Math.PI/4, label: 'sin(1.6x)', color: '#d62728' } - ] - - let data = { datasets: [] } as ChartData<'scatter'> + const currentModeData = project.modeViz[project.currentVizID] - // Generate data for each sine wave - configs.forEach(config => { - const points: {x: number, y: number}[] = [] - for (let i = 0; i < 200; i++) { - const x = (i - 100) * 0.1 - const y = config.amplitude * Math.sin(x * (config.frequency / 0.05) + config.phase) - points.push({ x, y }) + // Get Component names + const componentNames = new Set() + for (const frame of currentModeData.Frames) { + for (const componentName in frame.Components) { + componentNames.add(componentName) } - - data.datasets.push({ - label: config.label, - data: points, - borderColor: config.color, - backgroundColor: config.color, - showLine: true, - pointRadius: 0, - pointHoverRadius: 4, - borderWidth: 2, - }) - }) + } + console.log("Component Names:", Array.from(componentNames)) + + const datasets: any[] = [] + const colors = ['#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b'] // Expecting 3 blades with 2 series each (Flap and Edge) + let colorIndex = 0 + + // 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}[] = [] + + 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) + }) + } + } + } + + console.log("Tip Flap data:", tipFlapData) + console.log("Tip Edge data:", tipEdgeData) + + // Create Flap series + if (tipFlapData.length > 0) { + datasets.push({ + label: `Flap_${componentName}`, + data: tipFlapData, + borderColor: colors[colorIndex % colors.length], + backgroundColor: colors[colorIndex % colors.length], + showLine: true, + pointRadius: 2, + pointHoverRadius: 4, + borderWidth: 2, + }) + } + + // Create Edge series + if (tipEdgeData.length > 0) { + datasets.push({ + label: `Edge_${componentName}`, + data: tipEdgeData, + borderColor: colors[(colorIndex + 1) % colors.length], + backgroundColor: colors[(colorIndex + 1) % colors.length], + showLine: true, + pointRadius: 2, + pointHoverRadius: 4, + borderWidth: 2, + borderDash: [5, 5], // Dashed line to distinguish from Flap + }) + } + + colorIndex += 2 // Increment by 2 since we use 2 colors per component + } const options: ChartOptions<'scatter'> = { responsive: true, @@ -280,12 +333,12 @@ const bladeTipChart = computed(() => { display: true, position: 'right', labels: { - font: { size: 12 } + font: { size: 10 } } }, title: { display: true, - text: 'Blade Tip Deflection across Frames', + text: 'Blade Tip Deflection', font: { size: 16, weight: 'bold' } } }, @@ -312,11 +365,10 @@ const bladeTipChart = computed(() => { animation: { duration: 0 } } - return { data, options } + return { data: { datasets }, options } }) -