Skip to content

Commit 3c73097

Browse files
committed
Add ability to visualize node orientation
1 parent de1dab3 commit 3c73097

File tree

5 files changed

+150
-65
lines changed

5 files changed

+150
-65
lines changed

frontend/src/components/ModeViz.vue

Lines changed: 74 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ const project = useProjectStore()
1010
const props = defineProps<{
1111
ModeData: viz.ModeData
1212
showNodePaths: boolean
13+
showNodeOrientation: boolean
1314
}>()
1415
1516
watch(
@@ -19,13 +20,16 @@ watch(
1920
)
2021
2122
watch(() => props.showNodePaths, (snp) => (nodePaths.visible = snp))
23+
watch(() => props.showNodeOrientation, (sno) => {
24+
for (const ofr of orientationFrames) {
25+
ofr.visible = sno && lineFrames.indexOf(ofr) === frameNum
26+
}
27+
})
2228
23-
const showNodePaths = ref(true)
24-
25-
let frames = new Array<THREE.Group>;
29+
let lineFrames = new Array<THREE.Group>;
30+
let orientationFrames = new Array<THREE.Group>;
2631
let nodePaths = new THREE.Group;
2732
let frameNum = 0;
28-
let capturing = false;
2933
let frameCenter = new THREE.Vector3;
3034
let frameSize = new THREE.Vector3;
3135
let clock = new THREE.Clock();
@@ -35,30 +39,75 @@ const FOV = 10
3539
function createFrames(modeData: viz.ModeData) {
3640
if (modeData.Frames == null) return
3741
scene.clear()
38-
frames = [] as THREE.Group[];
42+
3943
const geometry = new THREE.BufferGeometry();
4044
geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0], 3));
4145
const material = new THREE.PointsMaterial({ color: 0x888888 });
4246
const origin = new THREE.Points(geometry, material);
4347
origin.visible = false
48+
49+
// Clear existing frames
50+
lineFrames = [] as THREE.Group[];
51+
orientationFrames = [] as THREE.Group[];
52+
4453
const allFramesGroup = new THREE.Group()
4554
allFramesGroup.add(origin)
55+
56+
const xMaterial = new THREE.LineBasicMaterial({ color: 0xff0000, linewidth: 2 });
57+
const yMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00, linewidth: 2 });
58+
const zMaterial = new THREE.LineBasicMaterial({ color: 0x0000ff, linewidth: 2 });
59+
60+
// Loop through frames
4661
for (const f of modeData.Frames) {
47-
const frameGroup = new THREE.Group()
62+
63+
// Lines
64+
const lineFrameGroup = new THREE.Group()
4865
for (const c of Object.values(f.Components)) {
4966
const curve = new THREE.CatmullRomCurve3(
5067
c.Line.map((p) => new THREE.Vector3(p.XYZ[0], p.XYZ[1], p.XYZ[2])))
5168
const points = curve.getPoints(50);
5269
const geometry = new THREE.BufferGeometry().setFromPoints(points);
5370
const material = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 1 });
5471
const curveObject = new THREE.Line(geometry, material);
55-
frameGroup.add(curveObject)
72+
lineFrameGroup.add(curveObject)
5673
allFramesGroup.add(curveObject.clone()) // Add clone of object to be used for view sizing
5774
}
58-
frameGroup.visible = false // Initialize each group to not visible for animation
59-
frames.push(frameGroup)
60-
scene.add(frameGroup)
75+
lineFrameGroup.visible = false // Initialize each group to not visible for animation
76+
lineFrames.push(lineFrameGroup)
77+
78+
// Orientations
79+
const orientationFrameGroup = new THREE.Group()
80+
for (const c of Object.values(f.Components)) {
81+
const indices = new Uint16Array(c.Line.map((_, i) => i * 2).flatMap(i => [i, i + 1]));
82+
83+
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]));
84+
const geometryX = new THREE.BufferGeometry();
85+
geometryX.setAttribute('position', new THREE.BufferAttribute(pointsX, 3));
86+
geometryX.setIndex(new THREE.BufferAttribute(indices, 1));
87+
const lineX = new THREE.LineSegments(geometryX, xMaterial);
88+
orientationFrameGroup.add(lineX);
89+
90+
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]));
91+
const geometryY = new THREE.BufferGeometry();
92+
geometryY.setAttribute('position', new THREE.BufferAttribute(pointsY, 3));
93+
geometryY.setIndex(new THREE.BufferAttribute(indices, 1));
94+
const lineY = new THREE.LineSegments(geometryY, yMaterial);
95+
orientationFrameGroup.add(lineY);
96+
97+
// 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]));
98+
// const geometryZ = new THREE.BufferGeometry();
99+
// geometryZ.setAttribute('position', new THREE.BufferAttribute(pointsZ, 3));
100+
// geometryZ.setIndex(new THREE.BufferAttribute(indices, 1));
101+
// const lineZ = new THREE.LineSegments(geometryZ, zMaterial);
102+
// orientationFrameGroup.add(lineZ);
103+
}
104+
orientationFrameGroup.visible = false // Initialize each group to not visible for animation
105+
orientationFrames.push(orientationFrameGroup)
106+
107+
scene.add(lineFrameGroup)
108+
scene.add(orientationFrameGroup)
61109
}
110+
62111
// Node paths
63112
const componentNames = Object.keys(modeData.Frames[0].Components)
64113
const curves = new Array<THREE.CatmullRomCurve3>
@@ -172,12 +221,23 @@ const views = [
172221
function animate() {
173222
requestAnimationFrame(animate);
174223
delta += clock.getDelta()
175-
if (delta > 1.5 / frames.length) {
224+
if (delta > 1.5 / lineFrames.length) {
176225
delta = 0
177-
frames[frameNum].visible = false;
226+
227+
// Hide current frame
228+
lineFrames[frameNum].visible = false;
229+
orientationFrames[frameNum].visible = false;
230+
231+
// Increment frame number
178232
frameNum++
179-
if (frameNum >= frames.length) frameNum = 0
180-
frames[frameNum].visible = true;
233+
if (frameNum >= lineFrames.length) frameNum = 0
234+
235+
// Show next frame
236+
lineFrames[frameNum].visible = true;
237+
if (props.showNodeOrientation) {
238+
orientationFrames[frameNum].visible = true;
239+
}
240+
181241
render();
182242
}
183243
}

frontend/src/components/Results.vue

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ const selectedPoint = ref<diagram.Point | null>(null)
2222
const freqChart = ref<ChartComponentRef<'scatter'> | null>(null)
2323
const dampChart = ref<ChartComponentRef<'scatter'> | null>(null)
2424
const showNodePaths = ref(true)
25+
const showNodeOrientation = ref(false)
2526
const xAxisWS = ref(true)
2627
const rotorSpeedMods = [1, 3, 6, 9, 12, 15]
2728
const vizScale = ref(20)
@@ -261,38 +262,38 @@ const bladeTipChart = computed(() => {
261262
// Loop over each component (blade) - similar to PlotTipDeflection
262263
for (const componentName of componentNames) {
263264
console.log("Adding series for", componentName)
264-
265+
265266
// 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-
267+
const tipFlapData: { x: number, y: number }[] = []
268+
const tipEdgeData: { x: number, y: number }[] = []
269+
269270
for (let frameIndex = 0; frameIndex < currentModeData.Frames.length; frameIndex++) {
270271
const frame = currentModeData.Frames[frameIndex]
271-
272+
272273
if (frame.Components[componentName]) {
273274
const component = frame.Components[componentName]
274-
275+
275276
// Check if LocalLine exists and has data
276277
if (component.LocalLine && component.LocalLine.length > 0) {
277278
// Get the last point (tip)
278279
const tipPoint = component.LocalLine[component.LocalLine.length - 1]
279-
280+
280281
tipFlapData.push({
281282
x: frameIndex + 1, // Frame numbers start from 1
282283
y: tipPoint.XYZ[0] // X coordinate (Flap direction)
283284
})
284-
285+
285286
tipEdgeData.push({
286287
x: frameIndex + 1, // Frame numbers start from 1
287288
y: tipPoint.XYZ[1] // Y coordinate (Edge direction)
288289
})
289290
}
290291
}
291292
}
292-
293+
293294
console.log("Tip Flap data:", tipFlapData)
294295
console.log("Tip Edge data:", tipEdgeData)
295-
296+
296297
// Create Flap series
297298
if (tipFlapData.length > 0) {
298299
datasets.push({
@@ -306,7 +307,7 @@ const bladeTipChart = computed(() => {
306307
borderWidth: 2,
307308
})
308309
}
309-
310+
310311
// Create Edge series
311312
if (tipEdgeData.length > 0) {
312313
datasets.push({
@@ -321,15 +322,15 @@ const bladeTipChart = computed(() => {
321322
borderDash: [5, 5], // Dashed line to distinguish from Flap
322323
})
323324
}
324-
325+
325326
colorIndex += 2 // Increment by 2 since we use 2 colors per component
326327
}
327328
328329
const options: ChartOptions<'scatter'> = {
329330
responsive: true,
330331
maintainAspectRatio: false,
331332
plugins: {
332-
legend: {
333+
legend: {
333334
display: true,
334335
position: 'right',
335336
labels: {
@@ -383,7 +384,7 @@ const bladeTipChart = computed(() => {
383384
<div class="row row-cols-auto g-3">
384385
<div class="col" v-for="c in project.analysis?.Cases">
385386
<a class="btn btn-outline-primary w-100" @click="project.selectCaseLinDir(c.ID)">Case {{ c.ID
386-
}}: {{ c.Name }} </a>
387+
}}: {{ c.Name }} </a>
387388
</div>
388389
<div class="col">
389390
<a class="btn btn-outline-primary w-100" @click="project.selectCustomLinDir()">Browse</a>
@@ -658,7 +659,8 @@ const bladeTipChart = computed(() => {
658659
<div class="col-10">
659660
<!-- 3D Mode Visualization -->
660661
<div style="width:100%; height: 80vh">
661-
<ModeViz :ModeData="project.modeViz[project.currentVizID]" :showNodePaths="showNodePaths">
662+
<ModeViz :ModeData="project.modeViz[project.currentVizID]" :showNodePaths="showNodePaths"
663+
:showNodeOrientation="showNodeOrientation">
662664
</ModeViz>
663665
</div>
664666
<!-- 2D Blade Tip Deflection Chart -->
@@ -671,6 +673,8 @@ const bladeTipChart = computed(() => {
671673
<a class="btn btn-primary" @click="project.clearModeViz">Clear</a>
672674
<a class="btn btn-primary" @click="showNodePaths = !showNodePaths">{{
673675
showNodePaths ? 'Hide' : 'Show' }} Node Paths</a>
676+
<a class="btn btn-primary" @click="showNodeOrientation = !showNodeOrientation">{{
677+
showNodeOrientation ? 'Hide' : 'Show' }} Node Orientation</a>
674678
</div>
675679
<div class="list-group">
676680
<a class="list-group-item list-group-item-action" v-for="(mv, i) in project.modeViz"

frontend/wailsjs/go/models.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,6 +1534,9 @@ export namespace viz {
15341534

15351535
export class Point {
15361536
XYZ: number[];
1537+
OrientationX: number[];
1538+
OrientationY: number[];
1539+
OrientationZ: number[];
15371540

15381541
static createFrom(source: any = {}) {
15391542
return new Point(source);
@@ -1542,6 +1545,9 @@ export namespace viz {
15421545
constructor(source: any = {}) {
15431546
if ('string' === typeof source) source = JSON.parse(source);
15441547
this.XYZ = source["XYZ"];
1548+
this.OrientationX = source["OrientationX"];
1549+
this.OrientationY = source["OrientationY"];
1550+
this.OrientationZ = source["OrientationZ"];
15451551
}
15461552
}
15471553
export class Component {

viz/viz.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,10 @@ type Options struct {
2323
}
2424

2525
type Point struct {
26-
XYZ [3]float32 `json:"XYZ"`
26+
XYZ [3]float32 `json:"XYZ"`
27+
OrientationX [3]float32 `json:"OrientationX"`
28+
OrientationY [3]float32 `json:"OrientationY"`
29+
OrientationZ [3]float32 `json:"OrientationZ"`
2730
}
2831

2932
type Component struct {
@@ -294,10 +297,32 @@ func ParseModeData(vtpFilePaths []string) (*ModeData, error) {
294297
return nil, err
295298
}
296299

300+
// Determine indices of data arrays containing orientation data
301+
orientationIndices := []int{-1, -1, -1}
302+
for i, da := range vtk.PolyData.Piece.PointData.DataArray {
303+
switch strings.ToLower(da.Name) {
304+
case "orientationx":
305+
orientationIndices[0] = i
306+
case "orientationy":
307+
orientationIndices[1] = i
308+
case "orientationz":
309+
orientationIndices[2] = i
310+
}
311+
}
312+
297313
// Copy line data into component
298314
component.Line = make([]Point, len(conn))
299315
for j, c := range conn {
300316
copy(component.Line[j].XYZ[:], vtk.PolyData.Piece.Points.DataArray.MatrixF32[c])
317+
if orientationIndices[0] > -1 {
318+
copy(component.Line[j].OrientationX[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[0]].MatrixF32[c])
319+
}
320+
if orientationIndices[1] > -1 {
321+
copy(component.Line[j].OrientationY[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[1]].MatrixF32[c])
322+
}
323+
if orientationIndices[2] > -1 {
324+
copy(component.Line[j].OrientationZ[:], vtk.PolyData.Piece.PointData.DataArray[orientationIndices[2]].MatrixF32[c])
325+
}
301326
}
302327

303328
// Copy local line data into component

0 commit comments

Comments
 (0)