Coverage for src/meshpy/mesh_creation_functions/beam_basic_geometry.py: 91%

122 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-28 04:21 +0000

1# The MIT License (MIT) 

2# 

3# Copyright (c) 2018-2025 MeshPy Authors 

4# 

5# Permission is hereby granted, free of charge, to any person obtaining a copy 

6# of this software and associated documentation files (the "Software"), to deal 

7# in the Software without restriction, including without limitation the rights 

8# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 

9# copies of the Software, and to permit persons to whom the Software is 

10# furnished to do so, subject to the following conditions: 

11# 

12# The above copyright notice and this permission notice shall be included in 

13# all copies or substantial portions of the Software. 

14# 

15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 

16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 

17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 

18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 

19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 

20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 

21# THE SOFTWARE. 

22"""This file has functions to create basic geometry items with meshpy.""" 

23 

24import warnings as _warnings 

25 

26import numpy as _np 

27 

28from meshpy.core.conf import mpy as _mpy 

29from meshpy.core.mesh import Mesh as _Mesh 

30from meshpy.core.rotation import Rotation as _Rotation 

31from meshpy.mesh_creation_functions.beam_generic import ( 

32 create_beam_mesh_function as _create_beam_mesh_function, 

33) 

34from meshpy.utils.nodes import get_single_node as _get_single_node 

35 

36 

37def create_beam_mesh_line(mesh, beam_class, material, start_point, end_point, **kwargs): 

38 """Generate a straight line of beam elements. 

39 

40 Args 

41 ---- 

42 mesh: Mesh 

43 Mesh that the line will be added to. 

44 beam_class: Beam 

45 Class of beam that will be used for this line. 

46 material: Material 

47 Material for this line. 

48 start_point, end_point: _np.array, list 

49 3D-coordinates for the start and end point of the line. 

50 

51 **kwargs (for all of them look into create_beam_mesh_function) 

52 ---- 

53 n_el: int 

54 Number of equally spaced beam elements along the line. Defaults to 1. 

55 Mutually exclusive with l_el. 

56 l_el: float 

57 Desired length of beam elements. Mutually exclusive with n_el. 

58 Be aware, that this length might not be achieved, if the elements are 

59 warped after they are created. 

60 start_node: Node, GeometrySet 

61 Node to use as the first node for this line. Use this if the line 

62 is connected to other lines (angles have to be the same, otherwise 

63 connections should be used). If a geometry set is given, it can 

64 contain one, and one node only. 

65 add_sets: bool 

66 If this is true the sets are added to the mesh and then displayed 

67 in eventual VTK output, even if they are not used for a boundary 

68 condition or coupling. 

69 

70 Return 

71 ---- 

72 return_set: GeometryName 

73 Set with the 'start' and 'end' node of the line. Also a 'line' set 

74 with all nodes of the line. 

75 """ 

76 

77 # Get geometrical values for this line. 

78 start_point = _np.asarray(start_point) 

79 end_point = _np.asarray(end_point) 

80 direction = end_point - start_point 

81 line_length = _np.linalg.norm(direction) 

82 t1 = direction / line_length 

83 

84 # Check if the z or y axis are larger projected onto the direction. 

85 # The tolerance is used here to ensure that round-off changes in the last digits of 

86 # the floating point values don't switch the case. This increases the robustness in 

87 # testing. 

88 if abs(_np.dot(t1, [0, 0, 1])) < abs(_np.dot(t1, [0, 1, 0])) - _mpy.eps_quaternion: 

89 t2 = [0, 0, 1] 

90 else: 

91 t2 = [0, 1, 0] 

92 rotation = _Rotation.from_basis(t1, t2) 

93 

94 def get_beam_geometry(parameter_a, parameter_b): 

95 """Return a function for the position along the beams axis.""" 

96 

97 def beam_function(xi): 

98 """Return a point on the beams axis for a given parameter 

99 coordinate xi.""" 

100 point_a = start_point + parameter_a * direction 

101 point_b = start_point + parameter_b * direction 

102 pos = 0.5 * (1 - xi) * point_a + 0.5 * (1 + xi) * point_b 

103 arc_length = ( 

104 0.5 * (1 - xi) * parameter_a + 0.5 * (1 + xi) * parameter_b 

105 ) * line_length 

106 return (pos, rotation, arc_length) 

107 

108 return beam_function 

109 

110 # Create the beam in the mesh 

111 return _create_beam_mesh_function( 

112 mesh, 

113 beam_class=beam_class, 

114 material=material, 

115 function_generator=get_beam_geometry, 

116 interval=[0.0, 1.0], 

117 interval_length=line_length, 

118 **kwargs, 

119 ) 

120 

121 

122def create_beam_mesh_arc_segment_via_rotation( 

123 mesh, beam_class, material, center, axis_rotation, radius, angle, **kwargs 

124): 

125 """Generate a circular segment of beam elements. 

126 

127 The circular segment is defined via a rotation, specifying the "initial" 

128 triad of the beam at the beginning of the arc. 

129 

130 This function exists for compatibility reasons with older MeshPy implementations. 

131 The user is encouraged to use the newer implementation create_beam_mesh_arc_segment_via_axis 

132 

133 Args 

134 ---- 

135 mesh: Mesh 

136 Mesh that the arc segment will be added to. 

137 beam_class: Beam 

138 Class of beam that will be used for this line. 

139 material: Material 

140 Material for this segment. 

141 center: _np.array, list 

142 Center of the arc. 

143 axis_rotation: Rotation 

144 This rotation defines the spatial orientation of the arc. 

145 The 3rd base vector of this rotation is the rotation axis of the arc 

146 segment. The segment starts in the direction of the 1st basis vector 

147 and the starting point is along the 2nd basis vector. 

148 radius: float 

149 The radius of the segment. 

150 angle: float 

151 The central angle of this segment in radians. 

152 

153 **kwargs (for all of them look into create_beam_mesh_function) 

154 ---- 

155 n_el: int 

156 Number of equally spaced beam elements along the line. Defaults to 1. 

157 Mutually exclusive with l_el. 

158 l_el: float 

159 Desired length of beam elements. Mutually exclusive with n_el. 

160 Be aware, that this length might not be achieved, if the elements are 

161 warped after they are created. 

162 

163 Return 

164 ---- 

165 return_set: GeometryName 

166 Set with the 'start' and 'end' node of the line. Also a 'line' set 

167 with all nodes of the line. 

168 """ 

169 

170 # Convert the input to the one for create_beam_mesh_arc_segment_via_axis 

171 axis = axis_rotation * [0, 0, 1] 

172 start_point = center + radius * (axis_rotation * [0, -1, 0]) 

173 return create_beam_mesh_arc_segment_via_axis( 

174 mesh, beam_class, material, axis, center, start_point, angle, **kwargs 

175 ) 

176 

177 

178def create_beam_mesh_arc_segment_via_axis( 

179 mesh, 

180 beam_class, 

181 material, 

182 axis, 

183 axis_point, 

184 start_point, 

185 angle, 

186 **kwargs, 

187): 

188 """Generate a circular segment of beam elements. 

189 

190 The arc is defined via a rotation axis, a point on the rotation axis a starting 

191 point, as well as the angle of the arc segment. 

192 

193 Args 

194 ---- 

195 mesh: Mesh 

196 Mesh that the arc segment will be added to. 

197 beam_class: Beam 

198 Class of beam that will be used for this line. 

199 material: Material 

200 Material for this segment. 

201 axis: _np.array, list 

202 Rotation axis of the arc. 

203 axis_point: _np.array, list 

204 Point lying on the rotation axis. Does not have to be the center of the arc. 

205 start_point: _np.array, list 

206 Start point of the arc. 

207 angle: float 

208 The central angle of this segment in radians. 

209 

210 **kwargs (for all of them look into create_beam_mesh_function) 

211 ---- 

212 n_el: int 

213 Number of equally spaced beam elements along the line. Defaults to 1. 

214 Mutually exclusive with l_el. 

215 l_el: float 

216 Desired length of beam elements. Mutually exclusive with n_el. 

217 Be aware, that this length might not be achieved, if the elements are 

218 warped after they are created. 

219 

220 Return 

221 ---- 

222 return_set: GeometryName 

223 Set with the 'start' and 'end' node of the line. Also a 'line' set 

224 with all nodes of the line. 

225 """ 

226 

227 # The angle can not be negative with the current implementation. 

228 if angle <= 0.0: 

229 raise ValueError( 

230 "The angle for a beam arc segment has to be a positive number!" 

231 ) 

232 

233 # Shortest distance from the given point to the axis of rotation gives 

234 # the "center" of the arc 

235 axis = _np.asarray(axis) 

236 axis_point = _np.asarray(axis_point) 

237 start_point = _np.asarray(start_point) 

238 

239 axis = axis / _np.linalg.norm(axis) 

240 diff = start_point - axis_point 

241 distance = diff - _np.dot(_np.dot(diff, axis), axis) 

242 radius = _np.linalg.norm(distance) 

243 center = start_point - distance 

244 

245 # Get the rotation at the start 

246 # No need to check the start node here, as eventual rotation offsets in 

247 # tangential direction will be covered by the create beam functionality. 

248 tangent = _np.cross(axis, distance) 

249 tangent /= _np.linalg.norm(tangent) 

250 start_rotation = _Rotation.from_rotation_matrix( 

251 _np.transpose(_np.array([tangent, -distance / radius, axis])) 

252 ) 

253 

254 def get_beam_geometry(alpha, beta): 

255 """Return a function for the position and rotation along the beam 

256 axis.""" 

257 

258 def beam_function(xi): 

259 """Return a point and the triad on the beams axis for a given 

260 parameter coordinate xi.""" 

261 phi = 0.5 * (xi + 1) * (beta - alpha) + alpha 

262 arc_rotation = _Rotation(axis, phi) 

263 rot = arc_rotation * start_rotation 

264 pos = center + arc_rotation * distance 

265 return (pos, rot, phi * radius) 

266 

267 return beam_function 

268 

269 # Create the beam in the mesh 

270 return _create_beam_mesh_function( 

271 mesh, 

272 beam_class=beam_class, 

273 material=material, 

274 function_generator=get_beam_geometry, 

275 interval=[0.0, angle], 

276 interval_length=angle * radius, 

277 **kwargs, 

278 ) 

279 

280 

281def create_beam_mesh_arc_segment_2d( 

282 mesh, beam_class, material, center, radius, phi_start, phi_end, **kwargs 

283): 

284 """Generate a circular segment of beam elements in the x-y plane. 

285 

286 Args 

287 ---- 

288 mesh: Mesh 

289 Mesh that the arc segment will be added to. 

290 beam_class: Beam 

291 Class of beam that will be used for this line. 

292 material: Material 

293 Material for this segment. 

294 center: _np.array, list 

295 Center of the arc. If the z component is not 0, an error will be 

296 thrown. 

297 radius: float 

298 The radius of the segment. 

299 phi_start, phi_end: float 

300 The start and end angles of the arc w.r.t the x-axis. If the start 

301 angle is larger than the end angle the beam faces in counter-clockwise 

302 direction, and if the start angle is smaller than the end angle, the 

303 beam faces in clockwise direction. 

304 

305 **kwargs (for all of them look into create_beam_mesh_function) 

306 ---- 

307 n_el: int 

308 Number of equally spaced beam elements along the line. Defaults to 1. 

309 Mutually exclusive with l_el. 

310 l_el: float 

311 Desired length of beam elements. Mutually exclusive with n_el. 

312 Be aware, that this length might not be achieved, if the elements are 

313 warped after they are created. 

314 

315 Return 

316 ---- 

317 return_set: GeometryName 

318 Set with the 'start' and 'end' node of the line. Also a 'line' set 

319 with all nodes of the line. 

320 """ 

321 

322 # The center point has to be on the x-y plane. 

323 if _np.abs(center[2]) > _mpy.eps_pos: 

324 raise ValueError("The z-value of center has to be 0!") 

325 

326 # Check if the beam is in clockwise or counter clockwise direction. 

327 angle = phi_end - phi_start 

328 axis = _np.array([0, 0, 1]) 

329 start_point = center + radius * (_Rotation(axis, phi_start) * [1, 0, 0]) 

330 

331 counter_clockwise = _np.sign(angle) == 1 

332 if not counter_clockwise: 

333 # If the beam is not in counter clockwise direction, we have to flip 

334 # the rotation axis. 

335 axis = -1.0 * axis 

336 

337 return create_beam_mesh_arc_segment_via_axis( 

338 mesh, 

339 beam_class, 

340 material, 

341 axis, 

342 center, 

343 start_point, 

344 _np.abs(angle), 

345 **kwargs, 

346 ) 

347 

348 

349def create_beam_mesh_line_at_node( 

350 mesh, beam_class, material, start_node, length, **kwargs 

351): 

352 """Generate a straight line at a given node. The tangent will be the same 

353 as at that node. 

354 

355 Args 

356 ---- 

357 mesh: Mesh 

358 Mesh that the arc segment will be added to. 

359 beam_class: Beam 

360 Class of beam that will be used for this line. 

361 material: Material 

362 Material for this segment. 

363 start_node: _np.array, list 

364 Point where the arc will continue. 

365 length: float 

366 Length of the line. 

367 

368 **kwargs (for all of them look into create_beam_mesh_function) 

369 ---- 

370 n_el: int 

371 Number of equally spaced beam elements along the line. Defaults to 1. 

372 Mutually exclusive with l_el. 

373 l_el: float 

374 Desired length of beam elements. Mutually exclusive with n_el. 

375 Be aware, that this length might not be achieved, if the elements are 

376 warped after they are created. 

377 

378 Return 

379 ---- 

380 return_set: GeometryName 

381 Set with the 'start' and 'end' node of the line. Also a 'line' set 

382 with all nodes of the line. 

383 """ 

384 

385 if length < 0: 

386 raise ValueError("Length has to be positive!") 

387 

388 # Create the line starting from the given node 

389 start_node = _get_single_node(start_node) 

390 tangent = start_node.rotation * [1, 0, 0] 

391 start_position = start_node.coordinates 

392 end_position = start_position + tangent * length 

393 

394 return create_beam_mesh_line( 

395 mesh, 

396 beam_class, 

397 material, 

398 start_position, 

399 end_position, 

400 start_node=start_node, 

401 **kwargs, 

402 ) 

403 

404 

405def create_beam_mesh_arc_at_node( 

406 mesh, beam_class, material, start_node, arc_axis_normal, radius, angle, **kwargs 

407): 

408 """Generate a circular segment starting at a given node. The arc will be 

409 tangent to the given node. 

410 

411 Args 

412 ---- 

413 mesh: Mesh 

414 Mesh that the arc segment will be added to. 

415 beam_class: Beam 

416 Class of beam that will be used for this line. 

417 material: Material 

418 Material for this segment. 

419 start_node: _np.array, list 

420 Point where the arc will continue. 

421 arc_axis_normal: 3d-vector 

422 Rotation axis for the created arc. 

423 radius: float 

424 The radius of the arc segment. 

425 angle: float 

426 Angle of the arc. If the angle is negative, the arc will point in the 

427 opposite direction, i.e., as if the arc_axis_normal would change sign. 

428 

429 **kwargs (for all of them look into create_beam_mesh_function) 

430 ---- 

431 n_el: int 

432 Number of equally spaced beam elements along the line. Defaults to 1. 

433 Mutually exclusive with l_el. 

434 l_el: float 

435 Desired length of beam elements. Mutually exclusive with n_el. 

436 Be aware, that this length might not be achieved, if the elements are 

437 warped after they are created. 

438 

439 Return 

440 ---- 

441 return_set: GeometryName 

442 Set with the 'start' and 'end' node of the line. Also a 'line' set 

443 with all nodes of the line. 

444 """ 

445 

446 # If the angle is negative, the normal is switched 

447 arc_axis_normal = _np.asarray(arc_axis_normal) 

448 if angle < 0: 

449 arc_axis_normal = -1.0 * arc_axis_normal 

450 

451 # The normal has to be perpendicular to the start point tangent 

452 start_node = _get_single_node(start_node) 

453 tangent = start_node.rotation * [1, 0, 0] 

454 if _np.abs(_np.dot(tangent, arc_axis_normal)) > _mpy.eps_pos: 

455 raise ValueError( 

456 "The normal has to be perpendicular to the tangent in the start node!" 

457 ) 

458 

459 # Get the center of the arc 

460 center_direction = _np.cross(tangent, arc_axis_normal) 

461 center_direction *= 1.0 / _np.linalg.norm(center_direction) 

462 center = start_node.coordinates - center_direction * radius 

463 

464 return create_beam_mesh_arc_segment_via_axis( 

465 mesh, 

466 beam_class, 

467 material, 

468 arc_axis_normal, 

469 center, 

470 start_node.coordinates, 

471 _np.abs(angle), 

472 start_node=start_node, 

473 **kwargs, 

474 ) 

475 

476 

477def create_beam_mesh_helix( 

478 mesh, 

479 beam_class, 

480 material, 

481 axis_vector, 

482 axis_point, 

483 start_point, 

484 *, 

485 helix_angle=None, 

486 height_helix=None, 

487 turns=None, 

488 warning_straight_line=True, 

489 **kwargs, 

490): 

491 """Generate a helical segment starting at a given start point around a 

492 predefined axis (defined by axis_vector and axis_point). The helical 

493 segment is defined by a start_point and exactly two of the basic helical 

494 quantities [helix_angle, height_helix, turns]. 

495 

496 Args 

497 ---- 

498 mesh: Mesh 

499 Mesh that the helical segment will be added to. 

500 beam_class: Beam 

501 Class of beam that will be used for this line. 

502 material: Material 

503 Material for this segment. 

504 axis_vector: _np.array, list 

505 Vector for the orientation of the helical center axis. 

506 axis_point: _np.array, list 

507 Point lying on the helical center axis. Does not need to align with 

508 bottom plane of helix. 

509 start_point: _np.array, list 

510 Start point of the helix. Defines the radius. 

511 helix_angle: float 

512 Angle of the helix (synonyms in literature: twist angle or pitch 

513 angle). 

514 height_helix: float 

515 Height of helix. 

516 turns: float 

517 Number of turns. 

518 warning_straight_line: bool 

519 Warn if radius of helix is zero or helix angle is 90 degrees and 

520 simple line is returned. 

521 

522 **kwargs (for all of them look into create_beam_mesh_function) 

523 ---- 

524 n_el: int 

525 Number of equally spaced beam elements along the line. Defaults to 1. 

526 Mutually exclusive with l_el. 

527 l_el: float 

528 Desired length of beam elements. Mutually exclusive with n_el. 

529 Be aware, that this length might not be achieved, if the elements are 

530 warped after they are created. 

531 

532 Return 

533 ---- 

534 return_set: GeometryName 

535 Set with the 'start' and 'end' node of the line. Also a 'line' set 

536 with all nodes of the line. 

537 """ 

538 

539 if [helix_angle, height_helix, turns].count(None) != 1: 

540 raise ValueError( 

541 "Exactly two arguments of [helix_angle, height_helix, turns]" 

542 " must be provided!" 

543 ) 

544 

545 if helix_angle is not None and _np.isclose(_np.sin(helix_angle), 0.0): 

546 raise ValueError( 

547 "Helix angle of helix is 0 degrees! " 

548 + "Change angle for feasible helix geometry!" 

549 ) 

550 

551 if height_helix is not None and _np.isclose(height_helix, 0.0): 

552 raise ValueError( 

553 "Height of helix is 0! Change height for feasible helix geometry!" 

554 ) 

555 

556 # determine radius of helix 

557 axis_vector = _np.asarray(axis_vector) 

558 axis_point = _np.asarray(axis_point) 

559 start_point = _np.asarray(start_point) 

560 

561 axis_vector = axis_vector / _np.linalg.norm(axis_vector) 

562 origin = axis_point + _np.dot( 

563 _np.dot(start_point - axis_point, axis_vector), axis_vector 

564 ) 

565 start_point_origin_vec = start_point - origin 

566 radius = _np.linalg.norm(start_point_origin_vec) 

567 

568 # create temporary mesh to not alter original mesh 

569 mesh_temp = _Mesh() 

570 

571 # return line if radius of helix is 0, helix angle is pi/2 or turns is 0 

572 if ( 

573 _np.isclose(radius, 0) 

574 or (helix_angle is not None and _np.isclose(_np.cos(helix_angle), 0.0)) 

575 or (turns is not None and _np.isclose(turns, 0.0)) 

576 ): 

577 if height_helix is None: 

578 raise ValueError( 

579 "Radius of helix is 0, helix angle is 90 degrees or turns is 0! " 

580 + "Fallback to simple line geometry but height cannot be " 

581 + "determined based on helix angle and turns! Either switch one " 

582 + "helix parameter to height of helix or change radius!" 

583 ) 

584 

585 if warning_straight_line: 

586 _warnings.warn( 

587 "Radius of helix is 0, helix angle is 90 degrees or turns is 0! " 

588 + "Simple line geometry is returned!" 

589 ) 

590 

591 if helix_angle is not None and height_helix is not None: 

592 end_point = start_point + height_helix * axis_vector * _np.sign( 

593 _np.sin(helix_angle) 

594 ) 

595 elif height_helix is not None and turns is not None: 

596 end_point = start_point + height_helix * axis_vector 

597 

598 line_sets = create_beam_mesh_line( 

599 mesh_temp, 

600 beam_class, 

601 material, 

602 start_point=start_point, 

603 end_point=end_point, 

604 **kwargs, 

605 ) 

606 

607 # add line to mesh 

608 mesh.add_mesh(mesh_temp) 

609 

610 return line_sets 

611 

612 # generate simple helix 

613 if helix_angle and height_helix: 

614 end_point = _np.array( 

615 [ 

616 radius, 

617 _np.sign(_np.sin(helix_angle)) * height_helix / _np.tan(helix_angle), 

618 _np.sign(_np.sin(helix_angle)) * height_helix, 

619 ] 

620 ) 

621 elif helix_angle and turns: 

622 end_point = _np.array( 

623 [ 

624 radius, 

625 _np.sign(_np.cos(helix_angle)) * 2 * _np.pi * radius * turns, 

626 _np.sign(_np.cos(helix_angle)) 

627 * 2 

628 * _np.pi 

629 * radius 

630 * _np.abs(turns) 

631 * _np.tan(helix_angle), 

632 ] 

633 ) 

634 elif height_helix and turns: 

635 end_point = _np.array( 

636 [ 

637 radius, 

638 2 * _np.pi * radius * turns, 

639 height_helix, 

640 ] 

641 ) 

642 

643 helix_sets = create_beam_mesh_line( 

644 mesh_temp, 

645 beam_class, 

646 material, 

647 start_point=[radius, 0, 0], 

648 end_point=end_point, 

649 **kwargs, 

650 ) 

651 

652 mesh_temp.wrap_around_cylinder() 

653 

654 # rotate and translate simple helix to align with necessary axis and starting point 

655 mesh_temp.rotate( 

656 _Rotation.from_basis(start_point_origin_vec, axis_vector) 

657 * _Rotation([1, 0, 0], -_np.pi * 0.5) 

658 ) 

659 mesh_temp.translate(-mesh_temp.nodes[0].coordinates + start_point) 

660 

661 # add helix to mesh 

662 mesh.add_mesh(mesh_temp) 

663 

664 return helix_sets