surfaceInertia.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2011-2025 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 Application
25  surfaceInertia
26 
27 Description
28  Calculates the inertia tensor, principal axes and moments of a
29  command line specified triSurface. Inertia can either be of the
30  solid body or of a thin shell.
31 
32 \*---------------------------------------------------------------------------*/
33 
34 #include "argList.H"
35 #include "ListOps.H"
36 #include "triSurface.H"
37 #include "OFstream.H"
38 #include "meshTools.H"
39 #include "randomGenerator.H"
40 #include "transform.H"
41 #include "IOmanip.H"
42 #include "Pair.H"
43 #include "momentOfInertia.H"
44 
45 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
46 
47 using namespace Foam;
48 
49 int main(int argc, char *argv[])
50 {
51  #include "removeCaseOptions.H"
52 
54  (
55  "Calculates the inertia tensor and principal axes and moments "
56  "of the specified surface.\n"
57  "Inertia can either be of the solid body or of a thin shell."
58  );
59 
60  argList::validArgs.append("surface file");
62  (
63  "shellProperties",
64  "inertia of a thin shell"
65  );
66 
68  (
69  "density",
70  "scalar",
71  "Specify density, "
72  "kg/m3 for solid properties, kg/m2 for shell properties"
73  );
74 
76  (
77  "referencePoint",
78  "vector",
79  "Inertia relative to this point, not the centre of mass"
80  );
81 
82  argList args(argc, argv);
83 
84  const fileName surfFileName = args[1];
85  const scalar density = args.optionLookupOrDefault("density", 1.0);
86 
87  vector refPt = Zero;
88  bool calcAroundRefPt = args.optionReadIfPresent("referencePoint", refPt);
89 
90  triSurface surf(surfFileName);
91 
92  scalar m = 0.0;
93  vector cM = Zero;
94  symmTensor J = Zero;
95 
96  if (args.optionFound("shellProperties"))
97  {
98  momentOfInertia::massPropertiesShell(surf, density, m, cM, J);
99  }
100  else
101  {
102  momentOfInertia::massPropertiesSolid(surf, density, m, cM, J);
103  }
104 
105  if (m < 0)
106  {
108  << "Negative mass detected, the surface may be inside-out." << endl;
109  }
110 
111  vector eVal = eigenValues(J);
112  tensor eVec = eigenVectors(J, eVal);
113 
114  bool showTransform = true;
115 
116  if
117  (
118  (mag(eVec.x() ^ eVec.y()) > (1.0 - small))
119  && (mag(eVec.y() ^ eVec.z()) > (1.0 - small))
120  && (mag(eVec.z() ^ eVec.x()) > (1.0 - small))
121  )
122  {
123  // Make the eigenvectors a right handed orthogonal triplet
124  eVec = tensor
125  (
126  eVec.x(),
127  eVec.y(),
128  eVec.z() * sign((eVec.x() ^ eVec.y()) & eVec.z())
129  );
130 
131  // Finding the most natural transformation. Using Lists
132  // rather than tensors to allow indexed permutation.
133 
134  // Cartesian basis vectors - right handed orthogonal triplet
135  List<vector> cartesian(3);
136 
137  cartesian[0] = vector(1, 0, 0);
138  cartesian[1] = vector(0, 1, 0);
139  cartesian[2] = vector(0, 0, 1);
140 
141  // Principal axis basis vectors - right handed orthogonal
142  // triplet
143  List<vector> principal(3);
144 
145  principal[0] = eVec.x();
146  principal[1] = eVec.y();
147  principal[2] = eVec.z();
148 
149  scalar maxMagDotProduct = -great;
150 
151  // Matching axis indices, first: cartesian, second:principal
152 
153  Pair<label> match(-1, -1);
154 
155  forAll(cartesian, cI)
156  {
157  forAll(principal, pI)
158  {
159  scalar magDotProduct = mag(cartesian[cI] & principal[pI]);
160 
161  if (magDotProduct > maxMagDotProduct)
162  {
163  maxMagDotProduct = magDotProduct;
164  match.first() = cI;
165  match.second() = pI;
166  }
167  }
168  }
169 
170  scalar sense = sign
171  (
172  cartesian[match.first()] & principal[match.second()]
173  );
174 
175  if (sense < 0)
176  {
177  // Invert the best match direction and swap the order of
178  // the other two vectors
179 
180  List<vector> tPrincipal = principal;
181 
182  tPrincipal[match.second()] *= -1;
183 
184  tPrincipal[(match.second() + 1) % 3] =
185  principal[(match.second() + 2) % 3];
186 
187  tPrincipal[(match.second() + 2) % 3] =
188  principal[(match.second() + 1) % 3];
189 
190  principal = tPrincipal;
191 
192  vector tEVal = eVal;
193  tEVal[(match.second() + 1) % 3] = eVal[(match.second() + 2) % 3];
194  tEVal[(match.second() + 2) % 3] = eVal[(match.second() + 1) % 3];
195 
196  eVal = tEVal;
197  }
198 
199  label permutationDelta = match.second() - match.first();
200 
201  if (permutationDelta != 0)
202  {
203  // Add 3 to the permutationDelta to avoid negative indices
204 
205  permutationDelta += 3;
206 
207  List<vector> tPrincipal = principal;
208  vector tEVal = eVal;
209 
210  for (label i = 0; i < 3; i++)
211  {
212  tPrincipal[i] = principal[(i + permutationDelta) % 3];
213  tEVal[i] = eVal[(i + permutationDelta) % 3];
214  }
215 
216  principal = tPrincipal;
217  eVal = tEVal;
218  }
219 
220  label matchedAlready = match.first();
221 
222  match =Pair<label>(-1, -1);
223 
224  maxMagDotProduct = -great;
225 
226  forAll(cartesian, cI)
227  {
228  if (cI == matchedAlready)
229  {
230  continue;
231  }
232 
233  forAll(principal, pI)
234  {
235  if (pI == matchedAlready)
236  {
237  continue;
238  }
239 
240  scalar magDotProduct = mag(cartesian[cI] & principal[pI]);
241 
242  if (magDotProduct > maxMagDotProduct)
243  {
244  maxMagDotProduct = magDotProduct;
245 
246  match.first() = cI;
247  match.second() = pI;
248  }
249  }
250  }
251 
252  sense = sign
253  (
254  cartesian[match.first()] & principal[match.second()]
255  );
256 
257  if (sense < 0 || (match.second() - match.first()) != 0)
258  {
259  principal[match.second()] *= -1;
260 
261  List<vector> tPrincipal = principal;
262 
263  tPrincipal[(matchedAlready + 1) % 3] =
264  principal[(matchedAlready + 2) % 3]*-sense;
265 
266  tPrincipal[(matchedAlready + 2) % 3] =
267  principal[(matchedAlready + 1) % 3]*-sense;
268 
269  principal = tPrincipal;
270 
271  vector tEVal = eVal;
272  tEVal[(matchedAlready + 1) % 3] = eVal[(matchedAlready + 2) % 3];
273  tEVal[(matchedAlready + 2) % 3] = eVal[(matchedAlready + 1) % 3];
274 
275  eVal = tEVal;
276  }
277 
278  eVec = tensor(principal[0], principal[1], principal[2]);
279  }
280  else
281  {
283  << "Non-unique eigenvectors, cannot compute transformation "
284  << "from Cartesian axes" << endl;
285 
286  showTransform = false;
287  }
288 
289  // calculate the total surface area
290 
291  scalar surfaceArea = 0;
292 
293  forAll(surf, facei)
294  {
295  const labelledTri& f = surf[facei];
296 
297  if (f[0] == f[1] || f[0] == f[2] || f[1] == f[2])
298  {
300  << "Illegal triangle " << facei << " vertices " << f
301  << " coords " << f.points(surf.points()) << endl;
302  }
303  else
304  {
305  surfaceArea += triPointRef
306  (
307  surf.points()[f[0]],
308  surf.points()[f[1]],
309  surf.points()[f[2]]
310  ).mag();
311  }
312  }
313 
314  Info<< nl << setprecision(12)
315  << "Density: " << density << nl
316  << "Mass: " << m << nl
317  << "Centre of mass: " << cM << nl
318  << "Surface area: " << surfaceArea << nl
319  << "Inertia tensor around centre of mass: " << nl << J << nl
320  << "eigenValues (principal moments): " << eVal << nl
321  << "eigenVectors (principal axes): " << nl
322  << eVec.x() << nl << eVec.y() << nl << eVec.z() << endl;
323 
324  if (showTransform)
325  {
326  Info<< "Transform tensor from reference state (orientation):" << nl
327  << eVec.T() << nl
328  << "Rotation tensor required to transform "
329  "from the body reference frame to the global "
330  "reference frame, i.e.:" << nl
331  << "globalVector = orientation & bodyLocalVector"
332  << endl;
333 
334  Info<< nl
335  << "Entries for sixDoFRigidBodyDisplacement boundary condition:"
336  << nl
337  << " mass " << m << token::END_STATEMENT << nl
338  << " centreOfMass " << cM << token::END_STATEMENT << nl
339  << " momentOfInertia " << eVal << token::END_STATEMENT << nl
340  << " orientation " << eVec.T() << token::END_STATEMENT
341  << endl;
342  }
343 
344  if (calcAroundRefPt)
345  {
346  Info<< nl << "Inertia tensor relative to " << refPt << ": " << nl
348  << endl;
349  }
350 
351  OFstream str("axes.obj");
352 
353  Info<< nl << "Writing scaled principal axes at centre of mass of "
354  << surfFileName << " to " << str.name() << endl;
355 
356  scalar scale = mag(cM - surf.points()[0])/eVal.component(findMin(eVal));
357 
358  meshTools::writeOBJ(str, cM);
359  meshTools::writeOBJ(str, cM + scale*eVal.x()*eVec.x());
360  meshTools::writeOBJ(str, cM + scale*eVal.y()*eVec.y());
361  meshTools::writeOBJ(str, cM + scale*eVal.z()*eVec.z());
362 
363  for (label i = 1; i < 4; i++)
364  {
365  str << "l " << 1 << ' ' << i + 1 << endl;
366  }
367 
368  Info<< nl << "End" << nl << endl;
369 
370  return 0;
371 }
372 
373 
374 // ************************************************************************* //
Istream and Ostream manipulators taking arguments.
Various functions to operate on Lists.
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
A 1D array of objects of type <T>, where the size of the vector is known and used for subscript bound...
Definition: List.H:91
Output to file stream.
Definition: OFstream.H:87
Tensor< Cmpt > T() const
Return transpose.
Definition: TensorI.H:331
Vector< Cmpt > z() const
Definition: TensorI.H:303
Vector< Cmpt > y() const
Definition: TensorI.H:296
Vector< Cmpt > x() const
Definition: TensorI.H:289
const Cmpt & component(const direction) const
Definition: VectorSpaceI.H:91
const Cmpt & z() const
Definition: VectorI.H:87
const Cmpt & y() const
Definition: VectorI.H:81
const Cmpt & x() const
Definition: VectorI.H:75
Extract command arguments and options from the supplied argc and argv parameters.
Definition: argList.H:103
static void addOption(const word &opt, const string &param="", const string &usage="")
Add to an option to validOptions with usage information.
Definition: argList.C:128
static void addNote(const string &)
Add extra notes for the usage information.
Definition: argList.C:159
static void addBoolOption(const word &opt, const string &usage="")
Add to a bool option to validOptions with usage information.
Definition: argList.C:118
bool optionFound(const word &opt) const
Return true if the named option is found.
Definition: argListI.H:114
bool optionReadIfPresent(const word &opt, T &) const
Read a value from the named option if present.
Definition: argListI.H:255
static SLList< string > validArgs
A list of valid (mandatory) arguments.
Definition: argList.H:153
T optionLookupOrDefault(const word &opt, const T &deflt) const
Read a value from the named option if present.
Definition: argListI.H:294
A class for handling file names.
Definition: fileName.H:82
word name() const
Return file name (part beyond last /)
Definition: fileName.C:195
Triangle with additional region number.
Definition: labelledTri.H:60
static void massPropertiesSolid(const pointField &pts, const triFaceList &triFaces, scalar density, scalar &mass, vector &cM, symmTensor &J)
static tensor applyParallelAxisTheorem(scalar mass, const vector &cM, const symmTensor &J, const vector &refPt)
static void massPropertiesShell(const pointField &pts, const triFaceList &triFaces, scalar density, scalar &mass, vector &cM, symmTensor &J)
@ END_STATEMENT
Definition: token.H:108
Triangulated surface description with patch information.
Definition: triSurface.H:69
int main(int argc, char *argv[])
Definition: financialFoam.C:44
#define WarningInFunction
Report a warning using Foam::Warning.
void writeOBJ(Ostream &os, const point &pt)
Write obj representation of point.
Definition: meshTools.C:203
Namespace for OpenFOAM.
static const zero Zero
Definition: zero.H:97
Omanip< int > setprecision(const int i)
Definition: IOmanip.H:205
dimensionedScalar sign(const dimensionedScalar &ds)
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
Tensor< scalar > tensor
Tensor of scalars.
Definition: tensor.H:51
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:258
void eigenValues(LagrangianPatchField< vector > &f, const LagrangianPatchField< tensor > &f1)
messageStream Info
void mag(LagrangianPatchField< scalar > &f, const LagrangianPatchField< Type > &f1)
Vector< scalar > vector
A scalar version of the templated Vector.
Definition: vector.H:49
void eigenVectors(LagrangianPatchField< tensor > &f, const LagrangianPatchField< tensor > &f1)
label findMin(const ListType &, const label start=0)
Find index of min element (and less than given element).
triangle< point, const point & > triPointRef
Definition: triPointRef.H:44
static const char nl
Definition: Ostream.H:267
labelList f(nPoints)
Foam::argList args(argc, argv)
3D tensor transformation operations.