|
29 | 29 | ) |
30 | 30 | from scipy.spatial.transform import Rotation |
31 | 31 | from vispy import app, scene, use |
32 | | -from vispy.geometry.generation import create_cylinder, create_sphere |
| 32 | +from vispy.geometry.generation import create_sphere |
| 33 | +from vispy.geometry.meshdata import MeshData |
33 | 34 | from vispy.scene.visuals import InstancedMesh |
34 | 35 | from vispy.util.transforms import rotate |
35 | 36 |
|
@@ -825,8 +826,8 @@ def create_instanced_meshes(meshdata, plot_type, current_view, min_width): |
825 | 826 | logger.debug(f"Created spherical mesh template with radius {r1}") |
826 | 827 | else: |
827 | 828 | rows = 2 + int(length / 2) |
828 | | - seg_mesh = create_cylinder( |
829 | | - rows=rows, cols=9, radius=[r1, r2], length=length |
| 829 | + seg_mesh = create_cylindrical_mesh( |
| 830 | + rows=rows, cols=9, radius=[r1, r2], length=length, closed=True |
830 | 831 | ) |
831 | 832 | logger.debug( |
832 | 833 | f"Created cylinderical mesh template with radii {r1}, {r2}, {length}" |
@@ -1053,3 +1054,88 @@ def plot_3D_schematic( |
1053 | 1054 | if not nogui: |
1054 | 1055 | create_instanced_meshes(meshdata, "Detailed", current_view, width) |
1055 | 1056 | app.run() |
| 1057 | + |
| 1058 | + |
| 1059 | +def create_cylindrical_mesh( |
| 1060 | + rows: int, |
| 1061 | + cols: int, |
| 1062 | + radius: typing.Union[float, typing.List[float]] = [1.0, 1.0], |
| 1063 | + length: float = 1.0, |
| 1064 | + closed: bool = True, |
| 1065 | +): |
| 1066 | + """Create a cylinderical mesh, adapted from vispy's generation method: |
| 1067 | + https://github.com/vispy/vispy/blob/main/vispy/geometry/generation.py#L451 |
| 1068 | +
|
| 1069 | + :param rows: number of rows to use for mesh |
| 1070 | + :type rows: int |
| 1071 | + :param cols: number of columns |
| 1072 | + :type cols: int |
| 1073 | + :param radius: float or pair of floats for the two radii of the cylinder |
| 1074 | + :type radius: float or [float, float][] |
| 1075 | + :param length: length of cylinder |
| 1076 | + :type length: float |
| 1077 | + :param closed: whether the cylinder should be closed |
| 1078 | + :type closed: bool |
| 1079 | + :returns: Vertices and faces computed for a cylindrical surface. |
| 1080 | + :rtype: MeshData |
| 1081 | +
|
| 1082 | + """ |
| 1083 | + verts = numpy.empty((rows + 1, cols, 3), dtype=numpy.float32) |
| 1084 | + if isinstance(radius, int) or isinstance(radius, float): |
| 1085 | + radius = [radius, radius] # convert to list |
| 1086 | + |
| 1087 | + # compute theta values |
| 1088 | + th = numpy.linspace(2 * numpy.pi, 0, cols).reshape(1, cols) |
| 1089 | + logger.debug(f"Thetas are: {th}") |
| 1090 | + |
| 1091 | + # radius as a function of z |
| 1092 | + r = numpy.linspace(radius[0], radius[1], num=rows + 1, endpoint=True).reshape( |
| 1093 | + rows + 1, 1 |
| 1094 | + ) |
| 1095 | + |
| 1096 | + verts[..., 0] = r * numpy.cos(th) # x = r cos(th) |
| 1097 | + verts[..., 1] = r * numpy.sin(th) # y = r sin(th) |
| 1098 | + verts[..., 2] = numpy.linspace(0, length, num=rows + 1, endpoint=True).reshape( |
| 1099 | + rows + 1, 1 |
| 1100 | + ) # z |
| 1101 | + # just reshape: no redundant vertices... |
| 1102 | + verts = verts.reshape((rows + 1) * cols, 3) |
| 1103 | + |
| 1104 | + # add extra points for center of two circular planes that form the caps |
| 1105 | + if closed is True: |
| 1106 | + verts = numpy.append(verts, [[0.0, 0.0, 0.0], [0.0, 0.0, 1.0]], axis=0) |
| 1107 | + logger.debug(f"Verts are: {verts}") |
| 1108 | + |
| 1109 | + # compute faces |
| 1110 | + faces = numpy.empty((rows * cols * 2, 3), dtype=numpy.uint32) |
| 1111 | + rowtemplate1 = ( |
| 1112 | + (numpy.arange(cols).reshape(cols, 1) + numpy.array([[0, 1, 0]])) % cols |
| 1113 | + ) + numpy.array([[0, 0, cols]]) |
| 1114 | + logger.debug(f"Template1 is: {rowtemplate1}") |
| 1115 | + |
| 1116 | + rowtemplate2 = ( |
| 1117 | + (numpy.arange(cols).reshape(cols, 1) + numpy.array([[0, 1, 1]])) % cols |
| 1118 | + ) + numpy.array([[cols, 0, cols]]) |
| 1119 | + # logger.debug(f"Template2 is: {rowtemplate2}") |
| 1120 | + |
| 1121 | + for row in range(rows): |
| 1122 | + start = row * cols * 2 |
| 1123 | + faces[start : start + cols] = rowtemplate1 + row * cols |
| 1124 | + faces[start + cols : start + (cols * 2)] = rowtemplate2 + row * cols |
| 1125 | + |
| 1126 | + # add extra faces to cover the caps |
| 1127 | + if closed is True: |
| 1128 | + cap1 = (numpy.arange(cols).reshape(cols, 1) + numpy.array([[0, 0, 1]])) % cols |
| 1129 | + cap1[..., 0] = len(verts) - 2 |
| 1130 | + cap2 = (numpy.arange(cols).reshape(cols, 1) + numpy.array([[0, 0, 1]])) % cols |
| 1131 | + cap2[..., 0] = len(verts) - 1 |
| 1132 | + |
| 1133 | + logger.debug(f"cap1 is {cap1}") |
| 1134 | + logger.debug(f"cap2 is {cap2}") |
| 1135 | + |
| 1136 | + faces = numpy.append(faces, cap1, axis=0) |
| 1137 | + faces = numpy.append(faces, cap2, axis=0) |
| 1138 | + |
| 1139 | + logger.debug(f"Faces are: {faces}") |
| 1140 | + |
| 1141 | + return MeshData(vertices=verts, faces=faces) |
0 commit comments