Removed axes3ds dependency.

This commit is contained in:
spencerfolk
2023-12-15 11:00:30 -05:00
parent 2b3df3e5f9
commit 53ccb003dd
7 changed files with 72 additions and 185 deletions

View File

@@ -11,7 +11,6 @@ from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
from scipy.spatial.transform import Rotation
from rotorpy.utils.axes3ds import Axes3Ds
from rotorpy.utils.shapes import Quadrotor
import os
@@ -94,12 +93,9 @@ def animate(time, position, rotation, wind, animate_wind, world, filename=None,
else:
fig = plt.figure('Animation')
fig.clear()
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
if not show_axes:
ax.set_axis_off()
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
ax.set_zlim(-1,1)
quad = Quadrotor(ax, wind=animate_wind)
@@ -114,7 +110,7 @@ def animate(time, position, rotation, wind, animate_wind, world, filename=None,
def update(frame):
title_artist.set_text('t = {:.2f}'.format(time[frame]))
quad.transform(position=position[frame,:], rotation=rotation[frame,:,:], wind=wind[frame,:])
[a.do_3d_projection(fig.canvas.get_renderer()) for a in quad.artists]
# [a.do_3d_projection(fig.canvas.get_renderer()) for a in quad.artists] # No longer necessary in newer matplotlib?
return world_artists + list(quad.artists) + [title_artist]
ani = ClosingFuncAnimation(fig=fig,

View File

@@ -1,159 +0,0 @@
"""
This module provides Axes3Ds ("Axes3D Spatial"), a drop-in replacement for
Axes3D which incorporates the improvements proposed by eric-wieser in matplotlib
issue #8896.
The purpose is to reduce the distortion when projecting 3D scenes into the 2D
image. For example, the projection of a sphere will be (closer to) a circle.
"""
"""
License agreement for matplotlib versions 1.3.0 and later
=========================================================
1. This LICENSE AGREEMENT is between the Matplotlib Development Team
("MDT"), and the Individual or Organization ("Licensee") accessing and
otherwise using matplotlib software in source or binary form and its
associated documentation.
2. Subject to the terms and conditions of this License Agreement, MDT
hereby grants Licensee a nonexclusive, royalty-free, world-wide license
to reproduce, analyze, test, perform and/or display publicly, prepare
derivative works, distribute, and otherwise use matplotlib
alone or in any derivative version, provided, however, that MDT's
License Agreement and MDT's notice of copyright, i.e., "Copyright (c)
2012- Matplotlib Development Team; All Rights Reserved" are retained in
matplotlib alone or in any derivative version prepared by
Licensee.
3. In the event Licensee prepares a derivative work that is based on or
incorporates matplotlib or any part thereof, and wants to
make the derivative work available to others as provided herein, then
Licensee hereby agrees to include in any such work a brief summary of
the changes made to matplotlib .
4. MDT is making matplotlib available to Licensee on an "AS
IS" basis. MDT MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, MDT MAKES NO AND
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF MATPLOTLIB
WILL NOT INFRINGE ANY THIRD PARTY RIGHTS.
5. MDT SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF MATPLOTLIB
FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR
LOSS AS A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING
MATPLOTLIB , OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF
THE POSSIBILITY THEREOF.
6. This License Agreement will automatically terminate upon a material
breach of its terms and conditions.
7. Nothing in this License Agreement shall be deemed to create any
relationship of agency, partnership, or joint venture between MDT and
Licensee. This License Agreement does not grant permission to use MDT
trademarks or trade name in a trademark sense to endorse or promote
products or services of Licensee, or any third party.
8. By copying, installing or otherwise using matplotlib ,
Licensee agrees to be bound by the terms and conditions of this License
Agreement.
"""
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
# Patch note: An update of the original implementation in proj3d.py.
def world_transformation(xmin, xmax,
ymin, ymax,
zmin, zmax, pb_aspect=None):
"""
produce a matrix that scales homogenous coords in the specified ranges
to [0, 1], or [0, pb_aspect[i]] if the plotbox aspect ratio is specified
"""
dx = xmax - xmin
dy = ymax - ymin
dz = zmax - zmin
if pb_aspect is not None:
ax, ay, az = pb_aspect
dx /= ax
dy /= ay
dz /= az
return np.array([[1/dx, 0, 0, -xmin/dx],
[0, 1/dy, 0, -ymin/dy],
[0, 0, 1/dz, -zmin/dz],
[0, 0, 0, 1]])
class Axes3Ds(Axes3D):
"""
Class Axes3Ds ("Axes3D Spatial") is a drop-in replacement for Axes3D which
incorporates the improvements proposed by eric-wieser in matplotlib issue
#8896.
"""
# Patch note: A new function.
def apply_aspect(self, position=None):
if position is None:
position = self.get_position(original=True)
# in the superclass, we would go through and actually deal with axis
# scales and box/datalim. Those are all irrelevant - all we need to do
# is make sure our coordinate system is square.
figW, figH = self.get_figure().get_size_inches()
fig_aspect = figH / figW
box_aspect = 1
pb = position.frozen()
pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
self.set_position(pb1.anchored(self.get_anchor(), pb), 'active')
# Patch note: Overwritten to use the updated version of world_transformation
# and the new pb_aspect value.
def get_proj(self):
"""
Create the projection matrix from the current viewing position.
elev stores the elevation angle in the z plane
azim stores the azimuth angle in the x,y plane
dist is the distance of the eye viewing point from the object
point.
"""
# chosen for similarity with the initial view before gh-8896
pb_aspect = np.array([4, 4, 3]) / 3.5
relev, razim = np.pi * self.elev/180, np.pi * self.azim/180
xmin, xmax = self.get_xlim3d()
ymin, ymax = self.get_ylim3d()
zmin, zmax = self.get_zlim3d()
# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = world_transformation(xmin, xmax,
ymin, ymax,
zmin, zmax, pb_aspect=pb_aspect)
# look into the middle of the new coordinates
R = pb_aspect / 2
xp = R[0] + np.cos(razim) * np.cos(relev) * self.dist
yp = R[1] + np.sin(razim) * np.cos(relev) * self.dist
zp = R[2] + np.sin(relev) * self.dist
E = np.array((xp, yp, zp))
self.eye = E
self.vvec = R - E
self.vvec = self.vvec / np.linalg.norm(self.vvec)
if abs(relev) > np.pi/2:
# upside down
V = np.array((0, 0, -1))
else:
V = np.array((0, 0, 1))
zfront, zback = -self.dist, self.dist
viewM = proj3d.view_transformation(E, R, V)
projM = self._projection(zfront, zback)
M0 = np.dot(viewM, worldM)
M = np.dot(projM, M0)
return M

View File

@@ -233,7 +233,6 @@ class OccupancyMap:
if __name__ == "__main__":
from axes3ds import Axes3Ds
import matplotlib.pyplot as plt
import os
@@ -245,7 +244,7 @@ if __name__ == "__main__":
# Create a figure
fig = plt.figure()
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
# Draw the world
world.draw(ax)

View File

@@ -2,7 +2,7 @@ import numpy as np
from scipy.spatial.transform import Rotation
import matplotlib.pyplot as plt
from rotorpy.utils.axes3ds import Axes3Ds
# from rotorpy.utils.axes3ds import Axes3Ds
from rotorpy.utils.animate import animate
import os
@@ -38,7 +38,8 @@ class Plotter():
# 3D Paths
fig = plt.figure('3D Path')
ax = Axes3Ds(fig)
# ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
self.world.draw(ax)
ax.plot3D(self.x[:,0], self.x[:,1], self.x[:,2], 'b.')
ax.plot3D(self.x_des[:,0], self.x_des[:,1], self.x_des[:,2], 'k')

View File

@@ -14,6 +14,58 @@ from mpl_toolkits.mplot3d import art3d
import matplotlib.colors as mcolors
from scipy.spatial.transform import Rotation
"""
Necessary functions for visualization
From original mplot3d version by John Porter (Created: 23 Sep 2005)
Parts fixed by Reinier Heeres <reinier@heeres.eu>
Minor additions by Ben Axelrod <baxelrod@coroware.com>
Significant updates and revisions by Ben Root <ben.v.root@gmail.com>
Current as of matplotlib v3.2.2 but changed at some point.
Modified by Spencer Folk
"""
def _generate_normals(polygons):
'''
Generate normals for polygons by using the first three points.
This normal of course might not make sense for polygons with
more than three points not lying in a plane.
'''
normals = []
for verts in polygons:
v1 = np.array(verts[0]) - np.array(verts[1])
v2 = np.array(verts[2]) - np.array(verts[0])
normals.append(np.cross(v1, v2))
return normals
def _shade_colors(color, normals):
'''
Shade *color* using normal vectors given by *normals*.
*color* can also be an array of the same length as *normals*.
'''
shade = np.array([np.dot(n / np.linalg.norm(n), [-1, -1, 0.5])
if np.linalg.norm(n) else np.nan
for n in normals])
mask = ~np.isnan(shade)
if len(shade[mask]) > 0:
norm = mcolors.Normalize(min(shade[mask]), max(shade[mask]))
shade[~mask] = min(shade[mask])
color = mcolors.to_rgba_array(color)
# shape of color should be (M, 4) (where M is number of faces)
# shape of shade should be (M,)
# colors should have final shape of (M, 4)
alpha = color[:, 3]
colors = (0.5 + norm(shade)[:, np.newaxis] * 0.5) * color
colors[:, 3] = alpha
else:
colors = np.asanyarray(color).copy()
return colors
class Face():
def __init__(self, ax, corners, *,
@@ -45,7 +97,7 @@ class Face():
# Precompute verticies and normal vectors in reference configuration.
self.verts = np.reshape(corners, (1, -1, 3))
self.normals = np.asarray(self.ax._generate_normals(self.verts))
self.normals = np.asarray(_generate_normals(self.verts))
# Instantiate and add collection.
self.polyc = art3d.Poly3DCollection(self.verts, linewidth=linewidth, antialiased=antialiased, alpha=alpha, edgecolors=edgecolors, facecolors=self.facecolors)
@@ -65,7 +117,7 @@ class Face():
if self.shade:
normals = np.matmul(rotation, self.normals.T).T
colset = self.ax._shade_colors(self.facecolors, normals)
colset = _shade_colors(self.facecolors, normals)
else:
colset = self.facecolors
self.polyc.set_facecolors(colset)
@@ -103,7 +155,7 @@ class Cuboid():
# Precompute verticies and normal vectors in reference configuration.
self.verts = self.build_verts(x_span, y_span, z_span)
self.normals = np.asarray(self.ax._generate_normals(self.verts))
self.normals = np.asarray(_generate_normals(self.verts))
# Instantiate and add collection.
self.polyc = art3d.Poly3DCollection(self.verts, linewidth=linewidth, antialiased=antialiased, alpha=alpha, edgecolors=edgecolors, facecolors=self.facecolors)
@@ -123,7 +175,7 @@ class Cuboid():
if self.shade:
normals = np.matmul(rotation, self.normals.T).T
colset = self.ax._shade_colors(self.facecolors, normals)
colset = _shade_colors(self.facecolors, normals)
else:
colset = self.facecolors
self.polyc.set_facecolors(colset)
@@ -181,7 +233,7 @@ class Cylinder():
# Precompute verticies and normal vectors in reference configuration.
self.verts = self.build_verts(radius, height, n_pts)
self.normals = np.asarray(self.ax._generate_normals(self.verts))
self.normals = np.asarray(_generate_normals(self.verts))
# Instantiate and add collection.
self.polyc = art3d.Poly3DCollection(self.verts, color='b', linewidth=0, antialiased=False)
@@ -200,7 +252,7 @@ class Cylinder():
if self.shade:
normals = np.matmul(rotation, self.normals.T).T
colset = self.ax._shade_colors(self.color, normals)
colset = _shade_colors(self.color, normals)
else:
colset = self.color
self.polyc.set_facecolors(colset)
@@ -311,12 +363,11 @@ class Quadrotor():
self.wind_vector = [self.ax.quiver(position[0], position[1], position[2], wind[0], wind[1], wind[2], color='r', linewidth=1.5)]
if __name__ == '__main__':
from axes3ds import Axes3Ds
import matplotlib.pyplot as plt
# Test Face
fig = plt.figure(num=4, clear=True)
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
corners = np.array([(1,1,1), (-1,1,1), (-1,-1,1), (1,-1,1)])
z_plus_face = Face(ax, corners=corners, facecolors='b')
x_plus_face = Face(ax, corners=corners, facecolors='r')
@@ -336,7 +387,7 @@ if __name__ == '__main__':
# Test Cuboid
fig = plt.figure(num=0, clear=True)
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
cuboid = Cuboid(ax, x_span=1, y_span=1, z_span=1)
cuboid.transform(position=np.array([[0, 0, 0]]), rotation=np.identity(3))
rotation = Rotation.from_rotvec(np.pi/4 * np.array([1, 0, 0])).as_matrix()
@@ -348,7 +399,7 @@ if __name__ == '__main__':
# Test Cylinder
fig = plt.figure(num=1, clear=True)
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
cylinder = Cylinder(ax, radius=0.2, height=0.2)
cylinder.transform(position=np.array([[0, 0, 0]]), rotation=np.identity(3))
rotation = Rotation.from_rotvec(np.pi/4 * np.array([1, 0, 0])).as_matrix()
@@ -360,7 +411,7 @@ if __name__ == '__main__':
# Test Quadrotor
fig = plt.figure(num=2, clear=True)
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
quad = Quadrotor(ax)
quad.transform(position=np.array([[0.5, 0.5, 0.5]]), rotation=np.identity(3))
quad = Quadrotor(ax)
@@ -372,7 +423,7 @@ if __name__ == '__main__':
# Test Cuboid coloring.
fig = plt.figure(num=3, clear=True)
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
ax.set_xlim(-3.25,3.25)
ax.set_ylim(-3.25,3.25)
ax.set_zlim(-3.25,3.25)

View File

@@ -289,7 +289,6 @@ if __name__ == '__main__':
import argparse
from pathlib import Path
import matplotlib.pyplot as plt
from rotorpy.axes3ds import Axes3Ds
parser = argparse.ArgumentParser(description='Display a map file in a Matplotlib window.')
parser.add_argument('filename', help="Filename for map file json.")
@@ -299,7 +298,7 @@ if __name__ == '__main__':
world = World.from_file(file)
fig = plt.figure(f"{file.name}")
ax = Axes3Ds(fig)
ax = fig.add_subplot(projection='3d')
world.draw(ax)
plt.show()

View File

@@ -3,7 +3,7 @@ from os.path import isdir
from itertools import product
# Gather our flightsim and any projXX packages that happen to exist.
all_packages = ['rotorpy', 'rotorpy.wind-dynamics']
all_packages = ['rotorpy']
packages = list(filter(isdir, all_packages))
setup(
@@ -12,7 +12,7 @@ setup(
version='1.0.1',
install_requires=[
'cvxopt',
'matplotlib == 3.2.2',
'matplotlib',
'filterpy == 1.4.5',
'numpy',
'scipy',