PyNiteFEA 2.0.0__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PKG-INFO +10 -5
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PyNiteFEA.egg-info/PKG-INFO +10 -5
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Rendering.py +65 -9
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Visualization.py +250 -86
- {pynitefea-2.0.0 → pynitefea-2.0.2}/README.md +9 -4
- {pynitefea-2.0.0 → pynitefea-2.0.2}/setup.py +1 -1
- {pynitefea-2.0.0 → pynitefea-2.0.2}/LICENSE +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PyNiteFEA.egg-info/SOURCES.txt +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PyNiteFEA.egg-info/dependency_links.txt +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PyNiteFEA.egg-info/requires.txt +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/PyNiteFEA.egg-info/top_level.txt +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Analysis.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/BeamSegY.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/BeamSegZ.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/FEModel3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/FixedEndReactions.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/LoadCombo.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/MainStyleSheet.css +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/MatFoundation.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Material.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Member3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Mesh.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Node3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/PhysMember.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Plate3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Quad3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Report_Template.html +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Reporting.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Section.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/ShearWall.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Spring3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/Tri3D.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/VTKWriter.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/Pynite/__init__.py +0 -0
- {pynitefea-2.0.0 → pynitefea-2.0.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyNiteFEA
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A simple elastic 3D structural finite element library for Python.
|
|
5
5
|
Home-page: https://github.com/JWock82/Pynite.git
|
|
6
6
|
Author: D. Craig Brinck, PE, SE
|
|
@@ -117,6 +117,15 @@ Here's a list of projects that use Pynite:
|
|
|
117
117
|
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
|
|
118
118
|
|
|
119
119
|
# What's New?
|
|
120
|
+
v2.0.2
|
|
121
|
+
* Added docstrings to the VTK `Renderer` class to help the user.
|
|
122
|
+
* Enforced use of properties instead of attributes in the VTK `Renderer` and added docstrings to properties help the user make decisions.
|
|
123
|
+
* Removed the VTK `Renderer`'s properties that began with "set_". These were redundant and caused confusion. The prefix "set_" is no longer used to access or set any of the `Renderer`'s properties.
|
|
124
|
+
|
|
125
|
+
v2.0.1
|
|
126
|
+
* Pynite no longer struggles rendering load cases and combinations when no loads are present. This is especially helpful for rendering modal load combinations, which don't have loads (only masses).
|
|
127
|
+
* The pyvista plotter's "X" button now works just like pressing "q". This was an annoying pyvista-ism that led to error messages when closing the window with the "X" button.
|
|
128
|
+
|
|
120
129
|
v2.0.0
|
|
121
130
|
* Added modal analysis. You can now find the natural frequencies and mode shapes of frame-type structures (plates not yet supported). A special thanks to @boustrephon for heading up this feature.
|
|
122
131
|
* Dropping support for python < 3.10.
|
|
@@ -195,7 +204,3 @@ v1.0.0
|
|
|
195
204
|
* Added a new `ShearWall` class that assists you in constructing and analyzing shear walls. This tool automatically detects piers and coupling beams, and finds the forces inside them and calculates their ascpect ratios, which can be handy for seismic design. It reports stiffness of multi-story shear walls at each story to help with rigid diaphragm analysis. It allows for modeling walls with openings, steps, and partial depth diaphragm loading.
|
|
196
205
|
* `vtk` and `pyvista` are now optional dependencies. This change streamlines installation for users who don't rely on `Pynite's` built-in visualization tools. From now on, `Pynite` should be installed using `$ pip install PyNiteFEA[all]` for most users.
|
|
197
206
|
* `Pynite` no longer uses auxiliary nodes to define member cross-section rotation. You can now directly specify the rotation (in degrees) when you define a member using the `rotation` argument.
|
|
198
|
-
|
|
199
|
-
v0.0.98-100
|
|
200
|
-
* Bug fix for `FEModel3D.add_section`. It was throwing exceptions and had not been updated to match the examples.
|
|
201
|
-
* Improvements to spring rendering in `pyvista`. Up until this point spring elements were being rendered as lines. They now render as zigzag lines in `pyvista`. There is still more work for improvement on spring rendering, but this is a good start.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: PyNiteFEA
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: A simple elastic 3D structural finite element library for Python.
|
|
5
5
|
Home-page: https://github.com/JWock82/Pynite.git
|
|
6
6
|
Author: D. Craig Brinck, PE, SE
|
|
@@ -117,6 +117,15 @@ Here's a list of projects that use Pynite:
|
|
|
117
117
|
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
|
|
118
118
|
|
|
119
119
|
# What's New?
|
|
120
|
+
v2.0.2
|
|
121
|
+
* Added docstrings to the VTK `Renderer` class to help the user.
|
|
122
|
+
* Enforced use of properties instead of attributes in the VTK `Renderer` and added docstrings to properties help the user make decisions.
|
|
123
|
+
* Removed the VTK `Renderer`'s properties that began with "set_". These were redundant and caused confusion. The prefix "set_" is no longer used to access or set any of the `Renderer`'s properties.
|
|
124
|
+
|
|
125
|
+
v2.0.1
|
|
126
|
+
* Pynite no longer struggles rendering load cases and combinations when no loads are present. This is especially helpful for rendering modal load combinations, which don't have loads (only masses).
|
|
127
|
+
* The pyvista plotter's "X" button now works just like pressing "q". This was an annoying pyvista-ism that led to error messages when closing the window with the "X" button.
|
|
128
|
+
|
|
120
129
|
v2.0.0
|
|
121
130
|
* Added modal analysis. You can now find the natural frequencies and mode shapes of frame-type structures (plates not yet supported). A special thanks to @boustrephon for heading up this feature.
|
|
122
131
|
* Dropping support for python < 3.10.
|
|
@@ -195,7 +204,3 @@ v1.0.0
|
|
|
195
204
|
* Added a new `ShearWall` class that assists you in constructing and analyzing shear walls. This tool automatically detects piers and coupling beams, and finds the forces inside them and calculates their ascpect ratios, which can be handy for seismic design. It reports stiffness of multi-story shear walls at each story to help with rigid diaphragm analysis. It allows for modeling walls with openings, steps, and partial depth diaphragm loading.
|
|
196
205
|
* `vtk` and `pyvista` are now optional dependencies. This change streamlines installation for users who don't rely on `Pynite's` built-in visualization tools. From now on, `Pynite` should be installed using `$ pip install PyNiteFEA[all]` for most users.
|
|
197
206
|
* `Pynite` no longer uses auxiliary nodes to define member cross-section rotation. You can now directly specify the rotation (in degrees) when you define a member using the `rotation` argument.
|
|
198
|
-
|
|
199
|
-
v0.0.98-100
|
|
200
|
-
* Bug fix for `FEModel3D.add_section`. It was throwing exceptions and had not been updated to match the examples.
|
|
201
|
-
* Improvements to spring rendering in `pyvista`. Up until this point spring elements were being rendered as lines. They now render as zigzag lines in `pyvista`. There is still more work for improvement on spring rendering, but this is a good start.
|
|
@@ -8,6 +8,10 @@ import numpy as np
|
|
|
8
8
|
import pyvista as pv
|
|
9
9
|
import math
|
|
10
10
|
|
|
11
|
+
# Suppress PyVista warnings for clean console output
|
|
12
|
+
warnings.filterwarnings('ignore', category=UserWarning, module='pyvista')
|
|
13
|
+
warnings.filterwarnings('ignore', message='.*Points is not a float type.*')
|
|
14
|
+
|
|
11
15
|
# For type checking only - these imports are only used during type checking
|
|
12
16
|
if TYPE_CHECKING:
|
|
13
17
|
from typing import List, Union, Tuple, Optional
|
|
@@ -54,13 +58,27 @@ class Renderer:
|
|
|
54
58
|
# (e.g., grid, axes) before render. Each func in this list must accept a `pyvista.Plotter` argument.
|
|
55
59
|
self.post_update_callbacks: List[Callable[[pv.Plotter], None]] = []
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
# Create plotter with off_screen mode if pyvista is set globally to OFF_SCREEN
|
|
62
|
+
# This is important for headless CI/testing environments
|
|
63
|
+
self.plotter: pv.Plotter = pv.Plotter(off_screen=pv.OFF_SCREEN)
|
|
58
64
|
self.plotter.set_background('white') # Setting background color
|
|
59
65
|
# self.plotter.add_logo_widget('./Resources/Full Logo No Buffer - Transparent.png')
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
|
|
67
|
+
# Only set view and axes in interactive mode (renderer must exist for these calls)
|
|
68
|
+
# In off-screen/headless mode, these will be set when update() is called
|
|
69
|
+
if not pv.OFF_SCREEN:
|
|
70
|
+
# self.plotter.view_isometric()
|
|
71
|
+
self.plotter.view_xy()
|
|
72
|
+
self.plotter.show_axes()
|
|
73
|
+
self.plotter.set_viewup((0, 1, 0)) # Set the Y axis to vertical for 3D plots
|
|
74
|
+
|
|
75
|
+
# Make X button behave like 'q' key - properly exit without destroying plotter
|
|
76
|
+
# Why: By default, PyVista's X button forcefully destroys the render window,
|
|
77
|
+
# causing warnings and preventing clean shutdown. This observer intercepts
|
|
78
|
+
# the window close event.
|
|
79
|
+
# How: When ExitEvent fires (X button clicked), we call TerminateApp() on the
|
|
80
|
+
# interactor, which cleanly exits the event loop just like pressing 'q'.
|
|
81
|
+
self.plotter.iren.add_observer('ExitEvent', lambda obj, event: obj.TerminateApp())
|
|
64
82
|
|
|
65
83
|
# Initialize load labels
|
|
66
84
|
self._load_label_points: List[List[float]] = []
|
|
@@ -178,7 +196,7 @@ class Renderer:
|
|
|
178
196
|
def scalar_bar_text_size(self, text_size: int) -> None:
|
|
179
197
|
self._scalar_bar_text_size = text_size
|
|
180
198
|
|
|
181
|
-
def render_model(self, reset_camera: bool = True) -> None:
|
|
199
|
+
def render_model(self, reset_camera: bool = True, off_screen: bool = False) -> None:
|
|
182
200
|
"""
|
|
183
201
|
Renders the model in a window
|
|
184
202
|
|
|
@@ -186,19 +204,37 @@ class Renderer:
|
|
|
186
204
|
----------
|
|
187
205
|
reset_camera : bool
|
|
188
206
|
Resets the camera if set to `True`. Default is `True`.
|
|
207
|
+
off_screen : bool
|
|
208
|
+
Renders off-screen without displaying a window. Useful for testing or
|
|
209
|
+
generating images in headless environments. Default is `False`.
|
|
189
210
|
"""
|
|
190
211
|
|
|
212
|
+
# Set off-screen mode if requested (for testing/headless environments)
|
|
213
|
+
# This must be done BEFORE calling update() so the plotter is in off-screen mode
|
|
214
|
+
# when adding meshes, preventing PyVista camera initialization issues
|
|
215
|
+
if off_screen:
|
|
216
|
+
self.plotter.off_screen = True
|
|
217
|
+
|
|
191
218
|
# Update the plotter with the latest geometry
|
|
192
219
|
self.update(reset_camera)
|
|
193
220
|
|
|
194
221
|
# Render the model (code execution will pause here until the user closes the window)
|
|
195
|
-
|
|
222
|
+
try:
|
|
223
|
+
self.plotter.show(title='Pynite - Simple Finite Element Analysis for Python', auto_close=False)
|
|
224
|
+
finally:
|
|
225
|
+
# Explicitly deep clean the plotter to prevent garbage collection issues
|
|
226
|
+
# Why: PyVista/VTK objects can have circular references that cause AttributeErrors
|
|
227
|
+
# during Python's garbage collection phase when the program exits.
|
|
228
|
+
# How: deep_clean() forces immediate cleanup of all VTK objects, meshes, and actors
|
|
229
|
+
# before Python's garbage collector runs, preventing the "'NoneType' object has
|
|
230
|
+
# no attribute 'check_attribute'" exception that would otherwise appear.
|
|
231
|
+
self.plotter.deep_clean()
|
|
196
232
|
|
|
197
233
|
def screenshot(self, filepath: str = './Pynite_Image.png', interact: bool = True, reset_camera: bool = False) -> None:
|
|
198
234
|
"""
|
|
199
235
|
Saves a screenshot of the rendered model.
|
|
200
236
|
|
|
201
|
-
In non-Jupyter notebook environments, if `interact` is set to `True`, the plotter will show a window that allows you to set the scene prior to the screenshot. Press `q` when you are ready to capture the screenshot and close the window.
|
|
237
|
+
In non-Jupyter notebook environments, if `interact` is set to `True`, the plotter will show a window that allows you to set the scene prior to the screenshot. Press `q` or click the `X` button when you are ready to capture the screenshot and close the window.
|
|
202
238
|
|
|
203
239
|
For Jupyter notebooks, the scene must be set using `render_model` prior to using `screenshot`.
|
|
204
240
|
|
|
@@ -246,6 +282,15 @@ class Renderer:
|
|
|
246
282
|
|
|
247
283
|
# Clear out the old plot (if any)
|
|
248
284
|
self.plotter.clear()
|
|
285
|
+
|
|
286
|
+
# Set up view and axes (works for both interactive and off-screen modes)
|
|
287
|
+
try:
|
|
288
|
+
self.plotter.view_xy()
|
|
289
|
+
self.plotter.show_axes()
|
|
290
|
+
self.plotter.set_viewup((0, 1, 0))
|
|
291
|
+
except:
|
|
292
|
+
# Silently fail if not supported in this context
|
|
293
|
+
pass
|
|
249
294
|
|
|
250
295
|
# Clear out internally stored labels (if any)
|
|
251
296
|
self._load_label_points = []
|
|
@@ -1063,9 +1108,20 @@ class Renderer:
|
|
|
1063
1108
|
if abs(load[0]) > max_area_load:
|
|
1064
1109
|
max_area_load = abs(load[0])
|
|
1065
1110
|
|
|
1111
|
+
# Prevent division by zero errors by ensuring max values are never zero
|
|
1112
|
+
# If a load type has no loads, set it to 1 to avoid crashes during normalization
|
|
1113
|
+
if max_pt_load == 0:
|
|
1114
|
+
max_pt_load = 1
|
|
1115
|
+
if max_moment == 0:
|
|
1116
|
+
max_moment = 1
|
|
1117
|
+
if max_dist_load == 0:
|
|
1118
|
+
max_dist_load = 1
|
|
1119
|
+
if max_area_load == 0:
|
|
1120
|
+
max_area_load = 1
|
|
1121
|
+
|
|
1066
1122
|
# Return the maximum loads for the load combo or load case
|
|
1067
1123
|
return max_pt_load, max_moment, max_dist_load, max_area_load
|
|
1068
|
-
|
|
1124
|
+
|
|
1069
1125
|
def plot_loads(self):
|
|
1070
1126
|
|
|
1071
1127
|
# Get the maximum load magnitudes that will be used to normalize the display scale
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
from __future__ import annotations # Allows more recent type hints features
|
|
2
|
-
from json import load
|
|
3
2
|
import warnings
|
|
4
3
|
|
|
5
4
|
from IPython.display import Image
|
|
@@ -8,28 +7,83 @@ from numpy.linalg import norm
|
|
|
8
7
|
import vtk
|
|
9
8
|
|
|
10
9
|
class Renderer():
|
|
11
|
-
"""
|
|
10
|
+
"""Renderer for visualizing finite element models using VTK.
|
|
11
|
+
|
|
12
|
+
This class provides a flexible interface for rendering 3D finite element models,
|
|
13
|
+
including nodes, members, springs, plates, and quads. It supports rendering of
|
|
14
|
+
undeformed and deformed shapes, load visualizations, and stress/force contours.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
model : FEModel3D
|
|
19
|
+
The finite element model to be rendered.
|
|
20
|
+
|
|
21
|
+
Attributes
|
|
22
|
+
----------
|
|
23
|
+
annotation_size : float
|
|
24
|
+
Controls the size of text labels and visual elements (default: 5).
|
|
25
|
+
deformed_shape : bool
|
|
26
|
+
Whether to render the deformed shape of the model (default: False).
|
|
27
|
+
deformed_scale : float
|
|
28
|
+
Scale factor for deformation visualization (default: 30).
|
|
29
|
+
render_nodes : bool
|
|
30
|
+
Whether to render nodes in the visualization (default: True).
|
|
31
|
+
render_loads : bool
|
|
32
|
+
Whether to render applied loads (default: True).
|
|
33
|
+
color_map : str or None
|
|
34
|
+
Type of stress/force contour to display on plates/quads. Options:
|
|
35
|
+
'Qx', 'Qy', 'Mx', 'My', 'Mxy', 'Sx', 'Sy', 'Txy' (default: None).
|
|
36
|
+
combo_name : str or None
|
|
37
|
+
Name of load combination to visualize. Mutually exclusive with case.
|
|
38
|
+
case : str or None
|
|
39
|
+
Name of load case to visualize. Mutually exclusive with combo_name.
|
|
40
|
+
labels : bool
|
|
41
|
+
Whether to display text labels for elements (default: True).
|
|
42
|
+
scalar_bar : bool
|
|
43
|
+
Whether to display a scalar bar for contour plots (default: False).
|
|
44
|
+
scalar_bar_text_size : int
|
|
45
|
+
Font size for scalar bar text (default: 24).
|
|
46
|
+
theme : str
|
|
47
|
+
Visual theme: 'default' or 'print' (default: 'default').
|
|
48
|
+
window_size : tuple
|
|
49
|
+
Window dimensions as (width, height).
|
|
50
|
+
|
|
51
|
+
Examples
|
|
52
|
+
--------
|
|
53
|
+
>>> from PyNite import FEModel3D
|
|
54
|
+
>>> model = FEModel3D()
|
|
55
|
+
>>> # ... build model ...
|
|
56
|
+
>>> renderer = Renderer(model)
|
|
57
|
+
>>> renderer.annotation_size = 6
|
|
58
|
+
>>> renderer.combo_name = 'Load Combo 1'
|
|
59
|
+
>>> renderer.render_model()
|
|
12
60
|
"""
|
|
13
61
|
|
|
14
62
|
scalar = None
|
|
15
63
|
|
|
16
64
|
def __init__(self, model):
|
|
17
|
-
|
|
65
|
+
"""Initialize the renderer with a finite element model.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
model : FEModel3D
|
|
70
|
+
The finite element model to render.
|
|
71
|
+
"""
|
|
18
72
|
self.model = model
|
|
19
73
|
|
|
20
74
|
# Default settings for rendering
|
|
21
|
-
self.
|
|
22
|
-
self.
|
|
23
|
-
self.
|
|
24
|
-
self.
|
|
25
|
-
self.
|
|
26
|
-
self.
|
|
27
|
-
self.
|
|
28
|
-
self.
|
|
29
|
-
self.
|
|
30
|
-
self.
|
|
31
|
-
self.
|
|
32
|
-
self.
|
|
75
|
+
self._annotation_size = 5
|
|
76
|
+
self._deformed_shape = False
|
|
77
|
+
self._deformed_scale = 30
|
|
78
|
+
self._render_nodes = True
|
|
79
|
+
self._render_loads = True
|
|
80
|
+
self._color_map = None
|
|
81
|
+
self._combo_name = 'Combo 1'
|
|
82
|
+
self._case = None
|
|
83
|
+
self._labels = True
|
|
84
|
+
self._scalar_bar = False
|
|
85
|
+
self._scalar_bar_text_size = 24
|
|
86
|
+
self._theme = 'default'
|
|
33
87
|
|
|
34
88
|
# Initialize VTK objects
|
|
35
89
|
self.renderer = vtk.vtkRenderer()
|
|
@@ -38,62 +92,134 @@ class Renderer():
|
|
|
38
92
|
self.window.AddRenderer(self.renderer)
|
|
39
93
|
|
|
40
94
|
@property
|
|
41
|
-
def
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
95
|
+
def window_size(self):
|
|
96
|
+
"""Window size as (width, height) tuple."""
|
|
97
|
+
return self.window.GetSize()
|
|
98
|
+
|
|
99
|
+
@window_size.setter
|
|
100
|
+
def window_size(self, size):
|
|
101
|
+
"""Set window size from tuple or list (width, height)."""
|
|
102
|
+
width, height = size
|
|
47
103
|
self.window.SetSize(width, height)
|
|
48
104
|
|
|
49
105
|
@property
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
self.
|
|
106
|
+
def combo_name(self):
|
|
107
|
+
"""Load combination name. When set, case is automatically set to None."""
|
|
108
|
+
return self._combo_name
|
|
109
|
+
|
|
110
|
+
@combo_name.setter
|
|
111
|
+
def combo_name(self, value):
|
|
112
|
+
self._combo_name = value
|
|
113
|
+
if value is not None:
|
|
114
|
+
self._case = None
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def case(self):
|
|
118
|
+
"""Load case name. When set, combo_name is automatically set to None."""
|
|
119
|
+
return self._case
|
|
120
|
+
|
|
121
|
+
@case.setter
|
|
122
|
+
def case(self, value):
|
|
123
|
+
self._case = value
|
|
124
|
+
if value is not None:
|
|
125
|
+
self._combo_name = None
|
|
57
126
|
|
|
58
|
-
|
|
59
|
-
|
|
127
|
+
@property
|
|
128
|
+
def annotation_size(self):
|
|
129
|
+
"""Size of text annotations and visual elements in model units."""
|
|
130
|
+
return self._annotation_size
|
|
131
|
+
|
|
132
|
+
@annotation_size.setter
|
|
133
|
+
def annotation_size(self, value):
|
|
134
|
+
self._annotation_size = value
|
|
60
135
|
|
|
61
|
-
|
|
62
|
-
|
|
136
|
+
@property
|
|
137
|
+
def deformed_shape(self):
|
|
138
|
+
"""Whether to render the deformed shape of the model."""
|
|
139
|
+
return self._deformed_shape
|
|
140
|
+
|
|
141
|
+
@deformed_shape.setter
|
|
142
|
+
def deformed_shape(self, value):
|
|
143
|
+
self._deformed_shape = value
|
|
63
144
|
|
|
64
|
-
|
|
65
|
-
|
|
145
|
+
@property
|
|
146
|
+
def deformed_scale(self):
|
|
147
|
+
"""Scale factor for deformation visualization."""
|
|
148
|
+
return self._deformed_scale
|
|
149
|
+
|
|
150
|
+
@deformed_scale.setter
|
|
151
|
+
def deformed_scale(self, value):
|
|
152
|
+
self._deformed_scale = value
|
|
66
153
|
|
|
67
|
-
|
|
68
|
-
|
|
154
|
+
@property
|
|
155
|
+
def render_nodes(self):
|
|
156
|
+
"""Whether to render nodes in the visualization."""
|
|
157
|
+
return self._render_nodes
|
|
158
|
+
|
|
159
|
+
@render_nodes.setter
|
|
160
|
+
def render_nodes(self, value):
|
|
161
|
+
self._render_nodes = value
|
|
69
162
|
|
|
70
|
-
|
|
71
|
-
|
|
163
|
+
@property
|
|
164
|
+
def render_loads(self):
|
|
165
|
+
"""Whether to render applied loads."""
|
|
166
|
+
return self._render_loads
|
|
72
167
|
|
|
73
|
-
|
|
74
|
-
|
|
168
|
+
@render_loads.setter
|
|
169
|
+
def render_loads(self, value):
|
|
170
|
+
self._render_loads = value
|
|
75
171
|
|
|
76
|
-
|
|
77
|
-
|
|
172
|
+
@property
|
|
173
|
+
def color_map(self):
|
|
174
|
+
"""Type of stress/force contour to display on plates/quads.
|
|
175
|
+
|
|
176
|
+
Valid options: 'Qx', 'Qy', 'Mx', 'My', 'Mxy', 'Sx', 'Sy', 'Txy'
|
|
177
|
+
- Qx, Qy: Out-of-plane shear forces
|
|
178
|
+
- Mx, My, Mxy: Local out-of-plane bending moments
|
|
179
|
+
- Sx, Sy: Membrane forces
|
|
180
|
+
- Txy: In-plane shear force
|
|
78
181
|
"""
|
|
79
|
-
self.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
self.
|
|
182
|
+
return self._color_map
|
|
183
|
+
|
|
184
|
+
@color_map.setter
|
|
185
|
+
def color_map(self, value):
|
|
186
|
+
self._color_map = value
|
|
84
187
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
188
|
+
@property
|
|
189
|
+
def labels(self):
|
|
190
|
+
"""Whether to display text labels for elements."""
|
|
191
|
+
return self._labels
|
|
192
|
+
|
|
193
|
+
@labels.setter
|
|
194
|
+
def labels(self, value):
|
|
195
|
+
self._labels = value
|
|
88
196
|
|
|
89
|
-
|
|
90
|
-
|
|
197
|
+
@property
|
|
198
|
+
def scalar_bar(self):
|
|
199
|
+
"""Whether to display a scalar bar legend for contour plots."""
|
|
200
|
+
return self._scalar_bar
|
|
201
|
+
|
|
202
|
+
@scalar_bar.setter
|
|
203
|
+
def scalar_bar(self, value):
|
|
204
|
+
self._scalar_bar = value
|
|
91
205
|
|
|
92
|
-
|
|
93
|
-
|
|
206
|
+
@property
|
|
207
|
+
def scalar_bar_text_size(self):
|
|
208
|
+
"""Font size for scalar bar text."""
|
|
209
|
+
return self._scalar_bar_text_size
|
|
210
|
+
|
|
211
|
+
@scalar_bar_text_size.setter
|
|
212
|
+
def scalar_bar_text_size(self, value):
|
|
213
|
+
self._scalar_bar_text_size = value
|
|
94
214
|
|
|
95
|
-
|
|
96
|
-
|
|
215
|
+
@property
|
|
216
|
+
def theme(self):
|
|
217
|
+
"""Visual theme: 'default' (dark background) or 'print' (white background)."""
|
|
218
|
+
return self._theme
|
|
219
|
+
|
|
220
|
+
@theme.setter
|
|
221
|
+
def theme(self, value):
|
|
222
|
+
self._theme = value
|
|
97
223
|
|
|
98
224
|
def render_model(self, interact=True, reset_camera=True):
|
|
99
225
|
"""
|
|
@@ -198,11 +324,26 @@ class Renderer():
|
|
|
198
324
|
return
|
|
199
325
|
|
|
200
326
|
def update(self, reset_camera=True):
|
|
201
|
-
"""
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
327
|
+
"""Build or rebuild the VTK renderer with current settings.
|
|
328
|
+
|
|
329
|
+
This method updates the visualization based on the current renderer settings,
|
|
330
|
+
including deformed shape, loads, contours, and other visual elements. It is
|
|
331
|
+
automatically called by render_model() and screenshot().
|
|
332
|
+
|
|
333
|
+
Parameters
|
|
334
|
+
----------
|
|
335
|
+
reset_camera : bool, optional
|
|
336
|
+
If True, resets the camera to view the entire model (default: True).
|
|
337
|
+
|
|
338
|
+
Raises
|
|
339
|
+
------
|
|
340
|
+
Exception
|
|
341
|
+
If the model configuration is invalid for the requested visualization.
|
|
342
|
+
|
|
343
|
+
Notes
|
|
344
|
+
-----
|
|
345
|
+
This method validates that deformed_shape is not used with a load case,
|
|
346
|
+
and that render_loads requires either a load combination or load case.
|
|
206
347
|
"""
|
|
207
348
|
|
|
208
349
|
# Input validation
|
|
@@ -604,8 +745,9 @@ class VisNode():
|
|
|
604
745
|
# Set the mapper for the node's actor
|
|
605
746
|
self.actor.SetMapper(mapper)
|
|
606
747
|
|
|
748
|
+
|
|
607
749
|
class VisSpring():
|
|
608
|
-
|
|
750
|
+
|
|
609
751
|
def __init__(self, spring, nodes, annotation_size=5, color=None):
|
|
610
752
|
|
|
611
753
|
# Generate a line source for the spring
|
|
@@ -1320,6 +1462,9 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1320
1462
|
polygon_points = vtk.vtkPoints()
|
|
1321
1463
|
polygon_polydata = vtk.vtkPolyData()
|
|
1322
1464
|
|
|
1465
|
+
# Track whether any loads have been added to avoid VTK errors with empty append filters
|
|
1466
|
+
has_loads = False
|
|
1467
|
+
|
|
1323
1468
|
# Get the maximum load magnitudes that will be used to normalize the display scale
|
|
1324
1469
|
max_pt_load, max_moment, max_dist_load, max_area_load = _MaxLoads(model, combo_name, case)
|
|
1325
1470
|
|
|
@@ -1365,6 +1510,7 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1365
1510
|
polydata.AddInputData(ptLoad.polydata.GetOutput())
|
|
1366
1511
|
renderer.AddActor(ptLoad.lblActor)
|
|
1367
1512
|
ptLoad.lblActor.SetCamera(renderer.GetActiveCamera())
|
|
1513
|
+
has_loads = True
|
|
1368
1514
|
|
|
1369
1515
|
# Step through each member
|
|
1370
1516
|
for member in model.members.values():
|
|
@@ -1418,6 +1564,7 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1418
1564
|
polydata.AddInputData(ptLoad.polydata.GetOutput())
|
|
1419
1565
|
renderer.AddActor(ptLoad.lblActor)
|
|
1420
1566
|
ptLoad.lblActor.SetCamera(renderer.GetActiveCamera())
|
|
1567
|
+
has_loads = True
|
|
1421
1568
|
|
|
1422
1569
|
# Step through each member distributed load
|
|
1423
1570
|
for load in member.DistLoads:
|
|
@@ -1454,6 +1601,7 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1454
1601
|
renderer.AddActor(distLoad.lblActors[1])
|
|
1455
1602
|
distLoad.lblActors[0].SetCamera(renderer.GetActiveCamera())
|
|
1456
1603
|
distLoad.lblActors[1].SetCamera(renderer.GetActiveCamera())
|
|
1604
|
+
has_loads = True
|
|
1457
1605
|
|
|
1458
1606
|
# Step through each plate
|
|
1459
1607
|
i = 0
|
|
@@ -1489,6 +1637,7 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1489
1637
|
|
|
1490
1638
|
# Add the area load's arrows to the overall load polydata
|
|
1491
1639
|
polydata.AddInputData(area_load.polydata.GetOutput())
|
|
1640
|
+
has_loads = True
|
|
1492
1641
|
|
|
1493
1642
|
# Add the 4 points at the corners of this area load to the list of points
|
|
1494
1643
|
polygon_points.InsertNextPoint(area_load.p0[0], area_load.p0[1], area_load.p0[2])
|
|
@@ -1523,35 +1672,39 @@ def _RenderLoads(model, renderer, annotation_size, combo_name, case, theme='defa
|
|
|
1523
1672
|
polygon_polydata.SetPoints(polygon_points)
|
|
1524
1673
|
polygon_polydata.SetPolys(polygons)
|
|
1525
1674
|
|
|
1526
|
-
#
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1675
|
+
# Only create and add actors if there are loads to render (avoids VTK errors with empty append filters)
|
|
1676
|
+
if has_loads:
|
|
1677
|
+
# Set up an actor and mapper for the loads
|
|
1678
|
+
load_mapper = vtk.vtkPolyDataMapper()
|
|
1679
|
+
load_mapper.SetInputConnection(polydata.GetOutputPort())
|
|
1680
|
+
load_actor = vtk.vtkActor()
|
|
1681
|
+
load_actor.SetMapper(load_mapper)
|
|
1531
1682
|
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1683
|
+
# Colorize the loads
|
|
1684
|
+
if theme == 'default':
|
|
1685
|
+
load_actor.GetProperty().SetColor(0, 1, 0) # Green
|
|
1686
|
+
elif theme == 'print':
|
|
1687
|
+
load_actor.GetProperty().SetColor(0, 0.75, 0) # Dark Green
|
|
1537
1688
|
|
|
1538
|
-
|
|
1539
|
-
|
|
1689
|
+
# Add the load actor to the renderer
|
|
1690
|
+
renderer.AddActor(load_actor)
|
|
1540
1691
|
|
|
1541
|
-
#
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1692
|
+
# Only create polygon actors if there are area loads (i > 0 means we added polygons)
|
|
1693
|
+
if i > 0:
|
|
1694
|
+
# Set up an actor and a mapper for the area load polygons
|
|
1695
|
+
polygon_mapper = vtk.vtkPolyDataMapper()
|
|
1696
|
+
polygon_mapper.SetInputData(polygon_polydata)
|
|
1697
|
+
polygon_actor = vtk.vtkActor()
|
|
1545
1698
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1699
|
+
# polygon_actor.GetProperty().SetOpacity(0.5) # 50% opacity
|
|
1700
|
+
polygon_actor.SetMapper(polygon_mapper)
|
|
1701
|
+
renderer.AddActor(polygon_actor)
|
|
1549
1702
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1703
|
+
# Set the color of the area load polygons
|
|
1704
|
+
if theme == 'default':
|
|
1705
|
+
polygon_actor.GetProperty().SetColor(0, 1, 0) # Green
|
|
1706
|
+
elif theme == 'print':
|
|
1707
|
+
polygon_actor.GetProperty().SetColor(0, 0.75, 0) # Dark Green
|
|
1555
1708
|
|
|
1556
1709
|
def _RenderContours(model, renderer, deformed_shape, deformed_scale, color_map, scalar_bar, scalar_bar_text_size, combo_name, theme='default'):
|
|
1557
1710
|
|
|
@@ -1847,5 +2000,16 @@ def _MaxLoads(model, combo_name=None, case=None):
|
|
|
1847
2000
|
if abs(load[0]) > max_area_load:
|
|
1848
2001
|
max_area_load = abs(load[0])
|
|
1849
2002
|
|
|
2003
|
+
# Prevent division by zero errors by ensuring max values are never zero
|
|
2004
|
+
# If a load type has no loads, set it to 1 to avoid crashes during normalization
|
|
2005
|
+
if max_pt_load == 0:
|
|
2006
|
+
max_pt_load = 1
|
|
2007
|
+
if max_moment == 0:
|
|
2008
|
+
max_moment = 1
|
|
2009
|
+
if max_dist_load == 0:
|
|
2010
|
+
max_dist_load = 1
|
|
2011
|
+
if max_area_load == 0:
|
|
2012
|
+
max_area_load = 1
|
|
2013
|
+
|
|
1850
2014
|
# Return the maximum loads in the load combination or load case
|
|
1851
2015
|
return max_pt_load, max_moment, max_dist_load, max_area_load
|
|
@@ -67,6 +67,15 @@ Here's a list of projects that use Pynite:
|
|
|
67
67
|
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)
|
|
68
68
|
|
|
69
69
|
# What's New?
|
|
70
|
+
v2.0.2
|
|
71
|
+
* Added docstrings to the VTK `Renderer` class to help the user.
|
|
72
|
+
* Enforced use of properties instead of attributes in the VTK `Renderer` and added docstrings to properties help the user make decisions.
|
|
73
|
+
* Removed the VTK `Renderer`'s properties that began with "set_". These were redundant and caused confusion. The prefix "set_" is no longer used to access or set any of the `Renderer`'s properties.
|
|
74
|
+
|
|
75
|
+
v2.0.1
|
|
76
|
+
* Pynite no longer struggles rendering load cases and combinations when no loads are present. This is especially helpful for rendering modal load combinations, which don't have loads (only masses).
|
|
77
|
+
* The pyvista plotter's "X" button now works just like pressing "q". This was an annoying pyvista-ism that led to error messages when closing the window with the "X" button.
|
|
78
|
+
|
|
70
79
|
v2.0.0
|
|
71
80
|
* Added modal analysis. You can now find the natural frequencies and mode shapes of frame-type structures (plates not yet supported). A special thanks to @boustrephon for heading up this feature.
|
|
72
81
|
* Dropping support for python < 3.10.
|
|
@@ -145,7 +154,3 @@ v1.0.0
|
|
|
145
154
|
* Added a new `ShearWall` class that assists you in constructing and analyzing shear walls. This tool automatically detects piers and coupling beams, and finds the forces inside them and calculates their ascpect ratios, which can be handy for seismic design. It reports stiffness of multi-story shear walls at each story to help with rigid diaphragm analysis. It allows for modeling walls with openings, steps, and partial depth diaphragm loading.
|
|
146
155
|
* `vtk` and `pyvista` are now optional dependencies. This change streamlines installation for users who don't rely on `Pynite's` built-in visualization tools. From now on, `Pynite` should be installed using `$ pip install PyNiteFEA[all]` for most users.
|
|
147
156
|
* `Pynite` no longer uses auxiliary nodes to define member cross-section rotation. You can now directly specify the rotation (in degrees) when you define a member using the `rotation` argument.
|
|
148
|
-
|
|
149
|
-
v0.0.98-100
|
|
150
|
-
* Bug fix for `FEModel3D.add_section`. It was throwing exceptions and had not been updated to match the examples.
|
|
151
|
-
* Improvements to spring rendering in `pyvista`. Up until this point spring elements were being rendered as lines. They now render as zigzag lines in `pyvista`. There is still more work for improvement on spring rendering, but this is a good start.
|
|
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
|
|
|
5
5
|
|
|
6
6
|
setuptools.setup(
|
|
7
7
|
name="PyNiteFEA",
|
|
8
|
-
version="2.0.
|
|
8
|
+
version="2.0.2",
|
|
9
9
|
author="D. Craig Brinck, PE, SE",
|
|
10
10
|
author_email="Building.Code@outlook.com",
|
|
11
11
|
description="A simple elastic 3D structural finite element library for Python.",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|