Skip to content

Commit

Permalink
Improved spring rendering in pyvista
Browse files Browse the repository at this point in the history
  • Loading branch information
JWock82 authored and JWock82 committed Nov 22, 2024
1 parent 7d0b802 commit e29261c
Show file tree
Hide file tree
Showing 2 changed files with 57 additions and 35 deletions.
89 changes: 54 additions & 35 deletions PyNite/Rendering.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def screenshot(self, filepath='./Pynite_Image.png', interact=True, reset_camera=
# Don't bother showing the image before capturing the screenshot
self.plotter.off_screen = True

# Save the screenshot to the specified filepath. Note that `auto_close` shuts down the entire plotter after the screenshot is taken, rather than just closing the window. We'll set `auto_close=False` to allow multiple screenshots to be taken. Note that the window must be closed with `q`. Closing it with the 'x' button will close the whole plotter down.
# Save the screenshot to the specified filepath. Note that `auto_close` shuts down the entire plotter after the screenshot is taken, rather than just closing the window. We'll set `auto_close=False` to allow the plotter to remain active. Note that the window must be closed by pressing `q`. Closing it with the 'X' button in the window's corner will close the whole plotter down.
self.plotter.show(title='Pynite - Simple Finite Element Anlaysis for Python', screenshot=filepath, auto_close=False)

def update(self, reset_camera=True):
Expand Down Expand Up @@ -286,7 +286,7 @@ def update(self, reset_camera=True):

# Render deformed springs
for spring in self.model.springs.values():
self.plot_deformed_spring(spring, self.deformed_scale, self.combo_name)
self.plot_spring(spring, self.annotation_size, 'red', deformed=True)

# _DeformedShape(self.model, self.deformed_scale, self.annotation_size, self.combo_name, self.render_nodes, self.theme)

Expand Down Expand Up @@ -506,29 +506,69 @@ def plot_member(self, member, theme='default'):

self.plotter.add_mesh(line, color='black', line_width=2)

def plot_spring(self, spring, size, color='grey'):
def plot_spring(self, spring, size, color='grey', deformed=False):
"""
Adds a spring to the plotter. This method generates a zig-zag line representing a spring between two nodes, and adds it to the plotter with specified theme settings."""

# Find the position of the i-node and j-node
# Find the spring's i-node and j-node
i_node = spring.i_node
j_node = spring.j_node

# Find the spring's node coordinates
Xi, Yi, Zi = i_node.X, i_node.Y, i_node.Z
Xj, Yj, Zj = j_node.X, j_node.Y, j_node.Z

# Create the line
line = pv.Line((Xi, Yi, Zi), (Xj, Yj, Zj))
# Determine if the spring should be plotted in its deformed shape
if deformed:
Xi = Xi + i_node.DX[self.combo_name]*self.deformed_scale
Yi = Yi + i_node.DY[self.combo_name]*self.deformed_scale
Zi = Zi + i_node.DZ[self.combo_name]*self.deformed_scale
Xj = Xj + j_node.DX[self.combo_name]*self.deformed_scale
Yj = Yj + j_node.DY[self.combo_name]*self.deformed_scale
Zj = Zj + j_node.DZ[self.combo_name]*self.deformed_scale

# Calculate the spring direction vector and length
direction = np.array([Xj, Yj, Zj]) - np.array([Xi, Yi, Zi])
length = ((Xj-Xi)**2 + (Yj-Yi)**2 - (Zj-Zi)**2)**0.5

# Normalize the direction vector
direction = direction/length

# Calculate perpendicular vectors for zig-zag plane
arbitrary_vector = np.array([1, 0, 0])
if np.allclose(direction, arbitrary_vector) or np.allclose(direction, -arbitrary_vector):
arbitrary_vector = np.array([0, 1, 0])
perp_vector1 = np.cross(direction, arbitrary_vector)
perp_vector1 /= np.linalg.norm(perp_vector1)
perp_vector2 = np.cross(direction, perp_vector1)
perp_vector2 /= np.linalg.norm(perp_vector2)

# Generate points for the zig-zag line
num_zigs = 4
num_points = num_zigs * 2
amplitude = size
t = np.linspace(0, length, num_points)
zigzag_pattern = amplitude * np.tile([1, -1], num_zigs)
zigzag_points = np.outer(t, direction) + np.outer(zigzag_pattern, perp_vector1)

# Change the color
if color is None:
line.plot(color='magenta')
elif color == 'black':
line.plot(color='black')
# Adjust the zigzag points to start position
zigzag_points += np.array([Xi, Yi, Zi])

# Add lines connecting the points
lines = np.zeros((num_points - 1, 3), dtype=int)
lines[:, 0] = np.full((num_points - 1), 2, dtype=int)
lines[:, 1] = np.arange(num_points - 1, dtype=int)
lines[:, 2] = np.arange(1, num_points, dtype=int)

# Create a PolyData object for the zig-zag line
zigzag_line = pv.PolyData(zigzag_points, lines=lines)

# Create a plotter and add the zig-zag line
self.plotter.add_mesh(zigzag_line, color=color, line_width=2)

# Add the spring label to the list of labels
self._spring_labels.append(spring.name)
self._spring_label_points.append([(Xi+Xj)/2, (Yi+Yj)/2, (Zi+Zj)/2])

# Add the line to the plotter
self.plotter.add_mesh(line)

def plot_plates(self, deformed_shape, deformed_scale, color_map, combo_name):

Expand Down Expand Up @@ -684,27 +724,6 @@ def plot_deformed_member(self, member, scale_factor):
for i in range(len(D_plot)-1):
line = pv.Line(D_plot[i], D_plot[i+1])
self.plotter.add_mesh(line, color='red', line_width=2)

def plot_deformed_spring(self, spring, scale_factor, combo_name='Combo 1'):

# Determine if the spring is active for the load combination
if spring.active[combo_name]:

# Get the spring's i-node and j-node
i_node = spring.i_node
j_node = spring.j_node

# Calculate the deformed positions of the spring's end points
Xi = i_node.X + i_node.DX[combo_name]*scale_factor
Yi = i_node.Y + i_node.DY[combo_name]*scale_factor
Zi = i_node.Z + i_node.DZ[combo_name]*scale_factor

Xj = j_node.X + j_node.DX[combo_name]*scale_factor
Yj = j_node.Y + j_node.DY[combo_name]*scale_factor
Zj = j_node.Z + j_node.DZ[combo_name]*scale_factor

# Plot a line for the deformed spring
self.plotter.add_mesh(pv.Line((Xi, Yi, Zi), (Xj, Yj, Zj)))

def plot_pt_load(self, position, direction, length, label_text=None, color='green'):

Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ Here's a list of projects that use PyNite:
* Phaenotyp (https://github.com/bewegende-Architektur/Phaenotyp) (https://youtu.be/shloSw9HjVI)

# What's New?
v0.0.98 (in progress)
* 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.

v0.0.97
* Fixed physical member load and deflection diagrams. Physical members are a newer feature. Member internal results were being reported correctly, but the diagrams for these members had not been revised to plot correctly. The old method for plain members was still being used. Physical members were not considering that a physical member was made from multiple submembers, and results for each span needed to be combined to get the whole plot.
* Switched some commonly used python libraries to be installed by default with `Pynite`. Most `Pynite` users will want these libraries installed for full-featured use of `Pynite`. These libraries help with `Pynite` visualizations, plotting, the sparse solver, and `Jupyter Lab` functionality. This is just easier for new python users. I was getting a lot of questions about how to set up libraries, and this takes the guesswork away. This is part of `Pynite's` objective to stay easy to use.
Expand Down

0 comments on commit e29261c

Please sign in to comment.