@@ -196,8 +196,9 @@ def __init__(self, fileName, complex=False, child=False, faceFreeze=None, name=N
196
196
tmp [ind ] = True
197
197
self .masks = tmp
198
198
199
- def addRefAxis (self , name , curve = None , xFraction = None , volumes = None ,
199
+ def addRefAxis (self , name , curve = None , xFraction = None , yFraction = None , zFraction = None , volumes = None ,
200
200
rotType = 5 , axis = 'x' , alignIndex = None , rotAxisVar = None ,
201
+ rot0ang = None , rot0axis = [1 , 0 , 0 ],
201
202
xFractionOrder = 2 , includeVols = [], ignoreInd = [],
202
203
raySize = 1.5 ):
203
204
"""
@@ -268,7 +269,20 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
268
269
variable which should be used to compute the orientation of the theta
269
270
rotation.
270
271
271
- xFractionOrder : int
272
+ rot0ang: float
273
+ If rotType == 0, defines the offset angle of the (child) FFD with respect
274
+ to the main system of reference. This is necessary to use the scaling functions
275
+ `scale_x`, `scale_y`, and `scale_z` with rotType == 0. The axis of rotation is
276
+ defined by `rot0axis`.
277
+
278
+ rot0axis: list
279
+ If rotType == 0, defines the rotation axis for the rotation offset of the
280
+ FFD grid given by `rot0ang`. The variable has to be a list of 3 floats
281
+ defining the [x,y,z] components of the axis direction.
282
+ This is necessary to use the scaling functions `scale_x`, `scale_y`,
283
+ and `scale_z` with rotType == 0.
284
+
285
+ xFractionOrder : int (NOT USED?)
272
286
Order of spline used for refaxis curve.
273
287
274
288
includeVols : list
@@ -325,7 +339,7 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
325
339
if volumes is None :
326
340
volumes = numpy .arange (self .FFD .nVol )
327
341
self .axis [name ] = {'curve' :curve , 'volumes' :volumes ,
328
- 'rotType' :rotType , 'axis' :axis }
342
+ 'rotType' :rotType , 'axis' :axis , 'rot0ang' : rot0ang , 'rot0axis' : rot0axis }
329
343
330
344
else :
331
345
# get the direction of the symmetry plane
@@ -349,11 +363,11 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
349
363
for coef in curveSymm .coef :
350
364
curveSymm .coef [:,index ]= - curveSymm .coef [:,index ]
351
365
self .axis [name ] = {'curve' :curve , 'volumes' :volumes ,
352
- 'rotType' :rotType , 'axis' :axis }
366
+ 'rotType' :rotType , 'axis' :axis , 'rot0ang' : rot0ang , 'rot0axis' : rot0axis }
353
367
self .axis [name + 'Symm' ] = {'curve' :curveSymm , 'volumes' :volumesSymm ,
354
- 'rotType' :rotType , 'axis' :axis }
368
+ 'rotType' :rotType , 'axis' :axis , 'rot0ang' : rot0ang , 'rot0axis' : rot0axis }
355
369
nAxis = len (curve .coef )
356
- elif xFraction is not None :
370
+ elif xFraction or yFraction or zFraction :
357
371
# Some assumptions
358
372
# - FFD should be a close approximation of geometry surface so that
359
373
# xFraction roughly corresponds to airfoil LE, TE, or 1/4 chord
@@ -363,6 +377,8 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
363
377
# included
364
378
# - 'x' is streamwise direction
365
379
380
+ # Default to "mean" ref axis location along non-user specified direction
381
+
366
382
# This is the block direction along which the reference axis will lie
367
383
# alignIndex = 'k'
368
384
if alignIndex is None :
@@ -416,16 +432,62 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
416
432
# Loop through sections and compute node location
417
433
place = 0
418
434
for j , vol in enumerate (volOrd ):
435
+ # sectionArr: indices of FFD points grouped by section
419
436
sectionArr = numpy .rollaxis (lIndex [vol ], alignIndex , 0 )
420
437
skip = 0
421
438
if j > 0 :
422
439
skip = 1
423
440
for i in range (nSections [j ]):
424
- LE = numpy .min (self .FFD .coef [sectionArr [i + skip ,:,:],0 ])
425
- TE = numpy .max (self .FFD .coef [sectionArr [i + skip ,:,:],0 ])
426
- refaxisNodes [place + i ,0 ] = xFraction * (TE - LE ) + LE
427
- refaxisNodes [place + i ,1 ] = numpy .mean (self .FFD .coef [sectionArr [i + skip ,:,:],1 ])
428
- refaxisNodes [place + i ,2 ] = numpy .mean (self .FFD .coef [sectionArr [i + skip ,:,:],2 ])
441
+ # getting all the section control points coordinates
442
+ pts_tens = self .FFD .coef [sectionArr [i + skip , :, :], :] # shape=(xAxisNodes,yAxisnodes,3)
443
+
444
+ # reshaping into vector to allow rotation (if needed) - leveraging on pts_tens.shape[2]=3 (FFD cp coordinates)
445
+ pts_vec = numpy .copy (pts_tens .reshape (- 1 , 3 )) # new shape=(xAxisNodes*yAxisnodes,3)
446
+
447
+ if rot0ang :
448
+ # rotating the FFD to be aligned with main axes
449
+ for ct_ in range (numpy .shape (pts_vec )[0 ]):
450
+ # here we loop over the pts_vec, rotate them and insert them inplace in pts_vec again
451
+ p_ = numpy .copy (pts_vec [ct_ , :])
452
+ p_rot = geo_utils .rotVbyW (p_ , rot0axis , numpy .pi / 180 * (rot0ang ))
453
+ pts_vec [ct_ , :] = p_rot
454
+
455
+ # Temporary ref axis node coordinates - aligned with main system of reference
456
+ if xFraction :
457
+ # getting the bounds of the FFD section
458
+ x_min = numpy .min (pts_vec [:, 0 ])
459
+ x_max = numpy .max (pts_vec [:, 0 ])
460
+ x_node = xFraction * (x_max - x_min ) + x_min # chordwise
461
+ else :
462
+ x_node = numpy .mean (pts_vec [:, 0 ])
463
+
464
+ if yFraction :
465
+ y_min = numpy .min (pts_vec [:, 1 ])
466
+ y_max = numpy .max (pts_vec [:, 1 ])
467
+ y_node = y_max - yFraction * (y_max - y_min ) # top-bottom
468
+ else :
469
+ y_node = numpy .mean (pts_vec [:, 1 ])
470
+
471
+ if zFraction :
472
+ z_min = numpy .min (pts_vec [:, 2 ])
473
+ z_max = numpy .max (pts_vec [:, 2 ])
474
+ z_node = z_max - zFraction * (z_max - z_min ) # top-bottom
475
+ else :
476
+ z_node = numpy .mean (pts_vec [:, 2 ])
477
+
478
+ # This is the FFD ref axis node - if the block has not been rotated
479
+ nd = [x_node , y_node , z_node ]
480
+ nd_final = numpy .copy (nd )
481
+
482
+ if rot0ang :
483
+ # rotating the non-aligned FFDs back in position
484
+ nd_final [:] = geo_utils .rotVbyW (nd , rot0axis , numpy .pi / 180 * (- rot0ang ))
485
+
486
+ # insert the final coordinates in the var to be passed to pySpline:
487
+ refaxisNodes [place + i , 0 ] = nd_final [0 ]
488
+ refaxisNodes [place + i , 1 ] = nd_final [1 ]
489
+ refaxisNodes [place + i , 2 ] = nd_final [2 ]
490
+
429
491
place += i + 1
430
492
431
493
# Add additional volumes
@@ -437,7 +499,7 @@ def addRefAxis(self, name, curve=None, xFraction=None, volumes=None,
437
499
curve = pySpline .Curve (X = refaxisNodes , k = 2 )
438
500
nAxis = len (curve .coef )
439
501
self .axis [name ] = {'curve' :curve , 'volumes' :volumes ,
440
- 'rotType' :rotType , 'axis' :axis ,
502
+ 'rotType' :rotType , 'axis' :axis , 'rot0ang' : rot0ang , 'rot0axis' : rot0axis ,
441
503
'rotAxisVar' :rotAxisVar }
442
504
else :
443
505
raise Error ("One of 'curve' or 'xFraction' must be "
@@ -1188,23 +1250,53 @@ def updateCalculations(self, new_pts, isComplex, config):
1188
1250
1189
1251
for ipt in range (self .nPtAttach ):
1190
1252
base_pt = self .refAxis .curves [self .curveIDs [ipt ]](self .links_s [ipt ])
1253
+ # Variables for rotType = 0 rotation + scaling
1254
+ ang = self .axis [self .curveIDNames [ipt ]]['rot0ang' ]
1255
+ ax_dir = self .axis [self .curveIDNames [ipt ]]['rot0axis' ]
1256
+
1191
1257
scale = self .scale [self .curveIDNames [ipt ]](self .links_s [ipt ])
1192
1258
scale_x = self .scale_x [self .curveIDNames [ipt ]](self .links_s [ipt ])
1193
1259
scale_y = self .scale_y [self .curveIDNames [ipt ]](self .links_s [ipt ])
1194
1260
scale_z = self .scale_z [self .curveIDNames [ipt ]](self .links_s [ipt ])
1195
1261
1196
1262
rotType = self .axis [self .curveIDNames [ipt ]]['rotType' ]
1197
1263
if rotType == 0 :
1264
+ bp_ = numpy .copy (base_pt ) # copy of original pointset - will not be rotated
1265
+ if isinstance (ang ,(float , int )): # rotation active only if a non-default value is provided
1266
+ ang *= numpy .pi / 180 # conv to [rad]
1267
+ # Rotating the FFD according to inputs
1268
+ # The FFD points should now be aligned with the main system of reference
1269
+ base_pt = geo_utils .rotVbyW (bp_ , ax_dir , ang )
1198
1270
deriv = self .refAxis .curves [
1199
1271
self .curveIDs [ipt ]].getDerivative (self .links_s [ipt ])
1200
1272
deriv /= geo_utils .euclideanNorm (deriv ) # Normalize
1201
1273
new_vec = - numpy .cross (deriv , self .links_n [ipt ])
1202
- new_vec = geo_utils .rotVbyW (new_vec , deriv , self .rot_theta [
1203
- self .curveIDNames [ipt ]](self .links_s [ipt ])* numpy .pi / 180 )
1204
1274
if isComplex :
1205
- new_pts [ipt ] = base_pt + new_vec * scale
1275
+ new_pts [ipt ] = bp_ + new_vec * scale # using "unrotated" bp_ vector
1276
+ else :
1277
+ new_pts [ipt ] = numpy .real (bp_ + new_vec * scale )
1278
+
1279
+ if isinstance (ang ,(float , int )):
1280
+ # Rotating to be aligned with main sys ref
1281
+ nv_ = numpy .copy (new_vec )
1282
+ new_vec = geo_utils .rotVbyW (nv_ , ax_dir , ang )
1283
+
1284
+ # Apply scaling
1285
+ new_vec [0 ] *= scale_x
1286
+ new_vec [1 ] *= scale_y
1287
+ new_vec [2 ] *= scale_z
1288
+
1289
+ if isinstance (ang ,(float , int )):
1290
+ # Rotating back the scaled pointset to its original position
1291
+ nv_rot = numpy .copy (new_vec ) # nv_rot is scaled and rotated
1292
+ new_vec = geo_utils .rotVbyW (nv_rot , ax_dir , - ang )
1293
+
1294
+ new_vec = geo_utils .rotVbyW (new_vec , deriv , self .rot_theta [self .curveIDNames [ipt ]](self .links_s [ipt ])* numpy .pi / 180 )
1295
+
1296
+ if isComplex :
1297
+ new_pts [ipt ] = bp_ + new_vec
1206
1298
else :
1207
- new_pts [ipt ] = numpy .real (base_pt + new_vec * scale )
1299
+ new_pts [ipt ] = numpy .real (bp_ + new_vec )
1208
1300
1209
1301
else :
1210
1302
rotX = geo_utils .rotxM (self .rot_x [
@@ -1246,6 +1338,7 @@ def updateCalculations(self, new_pts, isComplex, config):
1246
1338
D [0 ] *= scale_x
1247
1339
D [1 ] *= scale_y
1248
1340
D [2 ] *= scale_z
1341
+
1249
1342
if isComplex :
1250
1343
new_pts [ipt ] = base_pt + D * scale
1251
1344
else :
0 commit comments