def mesh(vertices, normals, colours, triangles): # TODO: Make this name meaningful in some way name = 'test' v3n3c4 = GeomVertexFormat.get_v3n3c4() data = GeomVertexData(name, v3n3c4, Geom.UHStatic) data.set_num_rows(len(vertices)) vertex_writer = GeomVertexWriter(data, 'vertex') normal_writer = GeomVertexWriter(data, 'normal') colour_writer = GeomVertexWriter(data, 'color') for vertex in vertices: vertex_writer.add_data3(*vertex) for normal in normals: normal_writer.add_data3(*normal) for colour in colours: colour_writer.add_data4(*colour) prim = GeomTriangles(Geom.UHStatic) for triangle in triangles: prim.add_vertices(*triangle) geom = Geom(data) geom.add_primitive(prim) node = GeomNode(name) node.add_geom(geom) return node
def make_node_from_mesh(points: np.ndarray, faces: np.ndarray, normals: np.ndarray, rgba: np.ndarray = np.asarray([1, 1, 1, 1])): vertex_normal_format = GeomVertexFormat.get_v3n3c4() v_data = GeomVertexData('sphere', vertex_normal_format, Geom.UHStatic) num_rows = np.max([points.shape[0], faces.shape[0]]) v_data.setNumRows(int(num_rows)) vertex_data = GeomVertexWriter(v_data, 'vertex') normal_data = GeomVertexWriter(v_data, 'normal') color_data = GeomVertexWriter(v_data, 'color') for point, normal in zip(points, normals): vertex_data.addData3(point[0], point[1], point[2]) normal_data.addData3(normal[0], normal[1], normal[2]) color_data.addData4(rgba[0], rgba[1], rgba[2], rgba[3]) geom = Geom(v_data) for face in faces: tri = GeomTriangles(Geom.UHStatic) p_1 = points[face[0], :] p_2 = points[face[1], :] p_3 = points[face[2], :] norm = normals[face[0], :] if np.dot(np.cross(p_2 - p_1, p_3 - p_2), norm) < 0: tri.add_vertices(face[2], face[1], face[0]) else: tri.add_vertices(face[0], face[1], face[2]) geom.addPrimitive(tri) node = GeomNode('gnode') node.addGeom(geom) return node
def gpugemsTerrain(): W, H, D = (1,1,2) noise = PerlinNoise3(1,1,1,256,0) warpNoise = PerlinNoise3(1,1,1,256,1) # noise = PerlinNoise3() # warpNoise = PerlinNoise3() def densityFunction(x, y, z): point = LVecBase3f(x, y, z) warpfactor = 0.0004 warp = warpNoise(LVecBase3f(x*warpfactor, y*warpfactor, z*warpfactor)) * 8 point.componentwiseMult(LVecBase3f(warp, warp, warp)) density = -point.z floor = 4 ω = [16.03, 8.05, 4.03, 1.96, 1.01, 0.49, 0.23, 0.097] # frequencies A = [0.25, 0.25, 0.5, 0.5, 1, 2, 8, 32] # amplitudes for i in range(len(ω)): ωVec = LVecBase3f(ω[i], ω[i], ω[i]) ωVec.componentwiseMult(point) density += noise(ωVec) * A[i] density += max(min((floor - point.z)*3, 1), 0)*40; return density def casePoint(density): if density < 0: return 0 else: return 1 g = 1 # granularity chunk = [[[ [[[densityFunction((x+32*w)/g, (y+32*h)/g, (z+32*d)/g) for x in range(33)] for y in range(33)] for z in range(33)] for w in range(W)] for h in range(H)] for d in range(D)] vdata = GeomVertexData('Land', GeomVertexFormat.get_v3n3c4(), Geom.UHStatic) vdata.setNumRows(33*33*4) vertices = GeomVertexWriter(vdata, 'vertex') normals = GeomVertexWriter(vdata, 'normal') colors = GeomVertexWriter(vdata, 'color') waterNoise = PerlinNoise2() ct = 0 for h in range(H): for w in range(W): for d in range(D): # bigger! for z in range(32): for y in range(32): for x in range(32): v0 = chunk[d][h][w][z+1][y][x] v1 = chunk[d][h][w][z][y][x] v2 = chunk[d][h][w][z][y][x+1] v3 = chunk[d][h][w][z+1][y][x+1] v4 = chunk[d][h][w][z+1][y+1][x] v5 = chunk[d][h][w][z][y+1][x] v6 = chunk[d][h][w][z][y+1][x+1] v7 = chunk[d][h][w][z+1][y+1][x+1] case = casePoint(chunk[d][h][w][z+1][y][x])\ + 2*casePoint(chunk[d][h][w][z][y][x])\ + 4*casePoint(chunk[d][h][w][z][y][x+1])\ + 8*casePoint(chunk[d][h][w][z+1][y][x+1])\ + 16*casePoint(chunk[d][h][w][z+1][y+1][x])\ + 32*casePoint(chunk[d][h][w][z][y+1][x])\ + 64*casePoint(chunk[d][h][w][z][y+1][x+1])\ + 128*casePoint(chunk[d][h][w][z+1][y+1][x+1])\ if case == 0 or case == 255: continue numpolys = TABLE['case_to_numpolys'][case][0] for polyIndex in range(numpolys): edgeConnects = TABLE['g_triTable'][case][polyIndex] currentTriangleVertices = [] currentTriangleColors = [] for edgeIndex in range(3): edge = edgeConnects[edgeIndex] X = x+32*w Y = y+32*h Z = z+32*d scale = 1 X = scale*X Y = scale*Y Z = scale*Z ph = min(1, (d*32+z)/32) # point height ph = ph*ph*ph color = None # if d == 0 and z <= 7: # v = waterNoise.noise(LVecBase2f(X, Y)) # b= 1 - v**2 # if b > 0.99: # color = [0.12, 0.29, b, 1] if edge == 0: diff = abs(v0)/(abs(v0)+abs(v1)) diff = scale * diff currentTriangleVertices.append([X, Y, Z-diff]) if color is None: color = [0.49+0.51*ph, 0.84+0.16*ph, 0.06+0.94*ph, 1] currentTriangleColors.append(color) elif edge == 1: diff = abs(v1)/(abs(v1)+abs(v2)) diff = scale * diff currentTriangleVertices.append([X+diff, Y, Z-scale]) currentTriangleColors.append([0.89, 0.17, 0.1, 1]) elif edge == 2: diff = abs(v3)/(abs(v3)+abs(v2)) diff = scale * diff currentTriangleVertices.append([X+scale, Y, Z-diff]) if color is None: color = [0.49+0.51*ph, 0.84+0.16*ph, 0.06+0.94*ph, 1] currentTriangleColors.append(color) elif edge == 3: diff = abs(v0)/(abs(v0)+abs(v3)) diff = scale * diff currentTriangleVertices.append([X+diff, Y, Z]) currentTriangleColors.append([0.89, 0.17, 0.1, 1]) elif edge == 4: diff = abs(v4)/(abs(v4)+abs(v5)) diff = scale * diff currentTriangleVertices.append([X, Y+scale, Z-diff]) if color is None: color = [0.49+0.51*ph, 0.84+0.16*ph, 0.06+0.94*ph, 1] currentTriangleColors.append(color) elif edge == 5: diff = abs(v5)/(abs(v5)+abs(v6)) diff = scale * diff currentTriangleVertices.append([X+diff, Y+scale, Z-scale]) currentTriangleColors.append([0.89, 0.17, 0.1, 1]) elif edge == 6: diff = abs(v7)/(abs(v7)+abs(v6)) diff = scale * diff currentTriangleVertices.append([X+scale, Y+scale, Z-diff]) if color is None: color = [0.49+0.51*ph, 0.84+0.16*ph, 0.06+0.94*ph, 1] currentTriangleColors.append(color) elif edge == 7: diff = abs(v4)/(abs(v4)+abs(v7)) diff = scale * diff currentTriangleVertices.append([X+diff, Y+scale, Z]) currentTriangleColors.append([0.89, 0.17, 0.1, 1]) elif edge == 8: diff = abs(v0)/(abs(v0)+abs(v4)) diff = scale * diff currentTriangleVertices.append([X, Y+diff, Z]) currentTriangleColors.append([0.89, 0.34, 0.1, 1]) elif edge == 9: diff = abs(v1)/(abs(v1)+abs(v5)) diff = scale * diff currentTriangleVertices.append([X, Y+diff, Z-scale]) currentTriangleColors.append([0.89, 0.34, 0.1, 1]) elif edge == 10: diff = abs(v2)/(abs(v2)+abs(v6)) diff = scale * diff currentTriangleVertices.append([X+scale, Y+diff, Z-scale]) currentTriangleColors.append([0.89, 0.34, 0.1, 1]) elif edge == 11: diff = abs(v3)/(abs(v3)+abs(v7)) diff = scale * diff currentTriangleVertices.append([X+scale, Y+diff, Z]) currentTriangleColors.append([0.89, 0.34, 0.1, 1]) a = currentTriangleVertices[0] b = currentTriangleVertices[1] c = currentTriangleVertices[2] ba = LVecBase3f(b[0]-a[0], b[1]-a[1], b[2]-a[2]) ca = LVecBase3f(c[0]-a[0], c[1]-a[1], c[2]-a[2]) normal = ba.cross(ca).normalized() for i in range(3): ct += 1 cv = currentTriangleVertices[i] cn = normal cc = currentTriangleColors[i] vertices.addData3f(cv[0], cv[1], cv[2]) normals.addData3f(cn[0], cn[1], cn[2]) colors.addData4f(cc[0], cc[1], cc[2], cc[3])
def create_node(self): # Set up the vertex arrays vformat = GeomVertexFormat.get_v3n3c4t2() vdata = GeomVertexData("Data", vformat, Geom.UHDynamic) vertex = GeomVertexWriter(vdata, 'vertex') normal = GeomVertexWriter(vdata, 'normal') color = GeomVertexWriter(vdata, 'color') texcoord = GeomVertexWriter(vdata, 'texcoord') geom = Geom(vdata) # Write vertex data for v in range(0, self.res[1] + 1): for u in range(0, self.res[0] + 1): # vertex_number = u * (self.res[0] + 1) + v t_u, t_v = float(u)/float(self.res[0]), float(v)/float(self.res[1]) # Vertex positions and normals will be overwritten before the first displaying anyways. vertex.addData3f(0, 0, 0) normal.addData3f(0, 0, 0) # Color is actually not an issue and should probably be kicked out of here. color.addData4f(1, 1, 1, 1) # Texcoords are constant, so this should be moved into its own array. texcoord.addData2f(t_u, t_v) # Add triangles for u in range(0, self.res[0]): for v in range(0, self.res[1]): # The vertex arrangement (y up, x right) # 2 3 # 0 1 u_verts = self.res[0] + 1 v_0 = u + v * u_verts v_1 = (u + 1) + v * u_verts v_2 = u + (v + 1) * u_verts v_3 = (u + 1) + (v + 1) * u_verts tris = GeomTriangles(Geom.UHDynamic) tris.addVertices(v_2, v_0, v_1) tris.closePrimitive() geom.addPrimitive(tris) tris = GeomTriangles(Geom.UHDynamic) tris.addVertices(v_1, v_3, v_2) tris.closePrimitive() geom.addPrimitive(tris) # Create the actual node sphere = GeomNode('geom_node') sphere.addGeom(geom) sphere_np = NodePath(sphere) tex = base.loader.loadTexture("assets/geosphere/geosphere_day.jpg") sphere_np.setTexture(tex) self.sphere_np = sphere_np self.sphere_vdata = vdata # ----- vformat = GeomVertexFormat.get_v3n3c4() vdata = GeomVertexData("Data", vformat, Geom.UHDynamic) vertex = GeomVertexWriter(vdata, 'vertex') color = GeomVertexWriter(vdata, 'color') geom = Geom(vdata) vertex.addData3f(-1, -1, 0) color.addData4f(1, 1, 1, 1) vertex.addData3f(1, -1, 0) color.addData4f(1, 1, 1, 1) tris = GeomLines(Geom.UHDynamic) tris.addVertices(0, 1) tris.closePrimitive() geom.addPrimitive(tris) connections = GeomNode('geom_node') connections.addGeom(geom) connections_np = NodePath(connections) connections_np.setRenderModeThickness(3) self.connections_np = connections_np self.connections_vdata = vdata self.connections_geom = geom self.connections_np.reparent_to(sphere_np) return sphere_np
from itertools import islice from typing import List from panda3d.core import (Geom, GeomNode, GeomTristrips, GeomVertexData, GeomVertexFormat, GeomVertexWriter, NodePath, TransparencyAttrib) from tsim.core.geometry import Point, sec from tsim.core.network.lane import LANE_WIDTH from tsim.core.network.path import Path from tsim.ui.objects.way import LEVEL_HEIGHT from tsim.utils.color import interpolate_rgb from tsim.utils.iterators import window_iter VERTEX_FORMAT = GeomVertexFormat.get_v3n3c4() HEIGHT = LEVEL_HEIGHT + 1.0 SHIFT = LANE_WIDTH START_COLOR = (0.2, 0.2, 1.0, 0.5) END_COLOR = (1.0, 0.0, 0.6, 0.5) def create(parent: NodePath, path: Path) -> NodePath: """Create node for given path and attach it to the parent.""" points = path.oriented_points() if len(points) >= 2: geom = _generate_mesh(points) node = GeomNode('path') node.add_geom(geom) node.adjust_draw_mask(0x00000000, 0x00010000, 0xfffeffff)
class LayerNodeBuilder(NodeBuilder): """Layer node builder""" # noinspection PyArgumentList _GEOM_VERTEX_FORMAT = GeomVertexFormat.get_v3n3c4( ) # coords + normals + RGBA _COLOR_DEFAULT = (0, 60 / 255, 200 / 255, 1) @classmethod def build_node( cls, layer: Layer, height: float, nozzle_diam: float, name: str, ) -> NodePath: """Build layer node. Parameters ---------- layer : Layer Layer for which the data should be generated and written. nozzle_diam : float Nozzle diameter. height : float Layer height, thickness. name : str Name for generated NodePath. Returns ------- NodePath Generated NodePath. """ geom_data = cls._get_geom_vertex_data(layer=layer, nozzle_diam=nozzle_diam, height=height, name=name) primitive = cls._get_primitive(layer=layer) # Prepare Geom geom = Geom(geom_data) geom.addPrimitive(primitive) # Prepare GeomNode geom_node = GeomNode(name) geom_node.addGeom(geom) # Create NodePath from GeomNode node_path = NodePath(geom_node) return node_path @classmethod def _get_geom_vertex_data(cls, layer: Layer, nozzle_diam: float, height: float, name: str) -> GeomVertexData: """Generate GeomVertexData for the provided Layer. Parameters ---------- layer : Layer Layer for which the data should be generated and written. nozzle_diam : float Nozzle diameter. height : float Layer height, thickness. name : str Name for generated GeomVertexData Returns ------- GeomVertexData Generated GeomVertexData. """ geom_vertex_count = sum([len(path) * 4 for path in layer.paths]) geom_data = GeomVertexData(name, cls._GEOM_VERTEX_FORMAT, Geom.UHStatic) geom_data.setNumRows(geom_vertex_count) cls._write_geom_data(geom_data=geom_data, layer=layer, width=nozzle_diam, height=height) return geom_data @classmethod def _write_geom_data( cls, geom_data: GeomVertexData, layer: Layer, width: float, height: float, ): """Write layer geom data into the provided GeomVertexData. Parameters ---------- geom_data : GeomVertexData GeomVertexData to write data into. layer : Layer Layer for which the data should be generated and written. width : float Segment width (nozzle diameter). height : float Segment height (layer height, thickness). """ # Write colors cls._write_geom_data_colors( geom_data=geom_data, layer_vertex_count=cls._get_layer_vertex_count(layer), ) # Write vertex coords and normals cls._write_geom_data_coords_and_normals(geom_data=geom_data, layer=layer, width=width, height=height) @classmethod def _write_geom_data_coords_and_normals( cls, geom_data: GeomVertexData, layer: Layer, width: float, height: float, ): """Write vertex coords and normals into the provided GeomVertexData. Parameters ---------- geom_data : GeomVertexData GeomVertexData to write coords and normals into. layer : Layer Layer for which the coords and normals should be generated and written. width : float Segment width (nozzle diameter). height : float Segment height (layer height, thickness) """ writer_vertex = GeomVertexWriter(geom_data, "vertex") writer_normal = GeomVertexWriter(geom_data, "normal") for toolpath in layer.paths: for index, path_point in enumerate(toolpath): wall_tilt_angle = cls._get_wall_tilt_angle(toolpath, index) width_scale_factor = cls._get_wall_width_scale_factor( toolpath, index) # VERTEX COORDS # Calculate 2D points, rotated x_shift = LVector2d(width / 2, 0) * width_scale_factor v0_2d = path_point v1_2d = vec_rotated(vector=v0_2d - x_shift, pivot=v0_2d, angle=wall_tilt_angle) v3_2d = vec_rotated(vector=v0_2d + x_shift, pivot=v0_2d, angle=wall_tilt_angle) # Create 3D vertices. Take Z into account. v0_3d = LVecBase3d(*v0_2d, 0) # v0 3D v1_3d = LVecBase3d(*v1_2d, -height / 2) # v1 3D v2_3d = LVecBase3d(*v0_2d, -height) # v2 3D v3_3d = LVecBase3d(*v3_2d, -height / 2) # v3_3D for vertex_coords in [v0_3d, v1_3d, v2_3d, v3_3d]: writer_vertex.addData3d(vertex_coords) # VERTEX NORMALS if 0 < index < len(toolpath) - 1: # Intermediate wall n0 = LVecBase3d(0, 0, 1) n1 = LVecBase3d( *((toolpath[index] - toolpath[index - 1]).normalized() + (toolpath[index] - toolpath[index + 1]).normalized()), 0, ).normalized() n2 = -n0 n3 = -n1 else: # Start or end wall if index == 0: # Start wall # v[0] - v[1] ref_vec: LVecBase2d = LVecBase2d(*(toolpath[1] - toolpath[0])) normal_rot_angle = math.pi / -4 else: # index == toolpath_point_count-1: # End wall # v[-1] - v[-2] ref_vec: LVecBase2d = LVecBase2d(*(toolpath[-1] - toolpath[-2])) normal_rot_angle = math.pi / 4 ref_vec.normalize() # Top and bottom n0 = LVecBase3d(*ref_vec, 1).normalized() n2 = LVecBase3d(*ref_vec, -1).normalized() # Sides n1 = LVecBase3d( *vec_rotated(vector=ref_vec, pivot=v1_2d, angle=normal_rot_angle), 0, ) n3 = LVecBase3d( *vec_rotated(vector=ref_vec, pivot=v1_2d, angle=normal_rot_angle), 0, ) for normal in [n0, n1, n2, n3]: writer_normal.addData3d(normal) @classmethod def _write_geom_data_colors( cls, geom_data: GeomVertexData, layer_vertex_count: int, color: Tuple[float, float, float, float] = _COLOR_DEFAULT, ): """Write color data into the provided GeomVertexData. Parameters ---------- geom_data : GeomVertexData GeomVertexData to write color data into. layer_vertex_count : int Total vertex count in layer. color : Tuple[float, float, float, float] Target vertex color. """ writer_color = GeomVertexWriter(geom_data, "color") for _ in range(layer_vertex_count): writer_color.addData4(color) @classmethod def _get_primitive(cls, layer: Layer) -> GeomTristrips: """Generate GeomTristrips primitive for provided layer.""" primitive = GeomTristrips(Geom.UHStatic) cur_point_num_in_layer = 0 # Iterate over paths in layer for path in layer.paths: segment_count = len(path) - 1 path_start_point_index = ( cur_point_num_in_layer * 4 ) # Relative to first point in first path in layer # Start cap primitive.addVertices( *[path_start_point_index + i for i in [0, 1, 3, 2]]) primitive.closePrimitive() # Segment walls for segment_index in range(segment_count): v_start_index = path_start_point_index + segment_index * 4 # Zigzag around segment walls: 0, 4, 1, 5, 2, 6, 3, 7, 0, 4 for i in range(v_start_index, v_start_index + 4): primitive.addVertex(i) primitive.addVertex(i + 4) primitive.addVertex(v_start_index) primitive.addVertex(v_start_index + 4) primitive.closePrimitive() # End cap end_wall_start_index = (cur_point_num_in_layer + segment_count) * 4 primitive.addVertices( *[end_wall_start_index + i for i in [0, 3, 1, 2]]) primitive.closePrimitive() cur_point_num_in_layer += len(path) return primitive @classmethod def _get_wall_width_scale_factor(cls, toolpath: Path, i: int) -> float: """Return wall width scale factor. Parameters ---------- toolpath : Path Toolpath. i : int Index of wall in toolpath. Returns ------- float Wall width scale factor for i-th wall in toolpath. """ if 0 < i < len(toolpath) - 1: # Intermediate point angle_delta = angle_signed( toolpath[i] - toolpath[i - 1], toolpath[i + 1] - toolpath[i], ) return 1.0 + abs(math.sin(angle_delta)) * (math.sqrt(2) - 1) return 1.0 @classmethod def _get_wall_tilt_angle(cls, toolpath: Path, i: int) -> float: """Returns wall tilt angle for specified point_cloud point. Positive angles are counterclockwise, negative are clockwise. Parameters ---------- toolpath : Path 2D point_cloud i : int Wall index (path vertex index). Returns ------- rotation_angle : float Wall rotation angle for segment wall corresponding to vertex v. """ # Aliases v_count = len(toolpath) v = toolpath if 0 < i < v_count - 1: # Intermediate point, most probable case target_vec = (v[i] - v[i - 1]).normalized() + (v[i + 1] - v[i]).normalized() elif i == 0: # Start point target_vec = v[1] - v[0] elif i == v_count - 1: # End point target_vec = v[i] - v[i - 1] else: # Out of range raise IndexError("Index is out of vertices range.") base_vec = LVecBase2d(0, 1) tilt_angle = angle_signed(base_vec, target_vec) return tilt_angle @classmethod def _get_layer_point_count(cls, layer: Layer) -> int: """Return total point count in provided layer.""" return sum([len(path) for path in layer.paths]) @classmethod def _get_layer_vertex_count(cls, layer: Layer) -> int: """Return expected total vertex count in provided layer.""" return cls._get_layer_point_count(layer) * 4
class BuildPlateNodeBuilder(NodeBuilder): """Build plate node builder.""" # noinspection PyTypeChecker _PATH_TEXTURE = Filename.fromOsSpecific( str((Path(__file__).parent / "assets/textures/layerview.png").absolute().as_posix())) _TEXTURE_OPACITY = 0.3 _TEXTURE_SCALE = 0.75 # noinspection PyArgumentList _GEOM_VERTEX_FORMAT_GRID = GeomVertexFormat.get_v3c4() # noinspection PyArgumentList _VERTEX_FORMAT_PLATE = GeomVertexFormat.get_v3n3c4() _GRID_SPACING = 10 # mm _GRID_COLOR = (0, 0, 0, 1) @classmethod def build_node( cls, loader: Loader, build_plate_size: LVector2d, grid_spacing: float = _GRID_SPACING, name: str = "", ) -> NodePath: """Build build area NodePath. Parameters ---------- loader : Loader Panda3D asset loader. build_plate_size : LVector2D Build plate size. grid_spacing: float Build plate grid spacing. Defaults to _GRID_SPACING. name : str Name for the returned NodePath. Returns ------- NodePath Built build plate NodePath. """ # X geom_data_x = cls._get_geom_data_grid_x(build_plate_size) primitive_x = cls._get_primitive_linestrips( line_count=int(build_plate_size.x / grid_spacing) - 1) geom_x = Geom(geom_data_x) geom_x.addPrimitive(primitive_x) # Y geom_data_y = cls._get_geom_data_grid_y(build_plate_size) primitive_y = cls._get_primitive_linestrips( line_count=int(build_plate_size.y / grid_spacing) - 1) geom_y = Geom(geom_data_y) geom_y.addPrimitive(primitive_y) # Assemble into one NodePath node_path = NodePath(top_node_name="") geom_node_x = GeomNode(f"{name}_x") geom_node_x.addGeom(geom_x) node_path.attachNewNode(geom_node_x) geom_node_y = GeomNode(f"{name}_y") geom_node_y.addGeom(geom_y) node_path.attachNewNode(geom_node_y) node_path_plate = cls._get_node_plate( loader=loader, build_plate_size=build_plate_size) node_path_plate.reparentTo(node_path) return node_path @staticmethod def _get_primitive_linestrips(line_count: int) -> GeomPrimitive: """Generate GeomLinestrips primitive for build plate grid. Parameters ---------- line_count : int Line count for the generated GeomLinestrips primitive. Returns ------- GeomLinestrips Generated primitive. """ primitive = GeomLinestrips(Geom.UHStatic) for i in range(line_count): primitive.addVertices(2 * i, 2 * i + 1) primitive.closePrimitive() return primitive @classmethod def _get_geom_data_grid_x( cls, build_plate_size: LVector2d, grid_spacing: float = _GRID_SPACING, name: str = "", ) -> GeomVertexData: """Generate GeomVertexData for build plate grid in X axis. Parameters ---------- build_plate_size : LVector2d Build plate size. grid_spacing : float Grid spacing; distance between successive grid lines. name : str Generated GeomVertexData name. """ geom_data = GeomVertexData(name, cls._GEOM_VERTEX_FORMAT_GRID, Geom.UHStatic) geom_data.setNumRows(int(math.ceil(build_plate_size.x / grid_spacing))) writer_vertex = GeomVertexWriter(geom_data, "vertex") writer_color = GeomVertexWriter(geom_data, "color") current_x = grid_spacing while current_x <= build_plate_size.x: writer_vertex.addData3d(LVecBase3d(current_x, 0, 0)) writer_vertex.addData3d( LVecBase3d(current_x, build_plate_size.y, 0)) for _ in range(2): writer_color.addData4(cls._GRID_COLOR) current_x += grid_spacing return geom_data @classmethod def _get_geom_data_grid_y( cls, build_plate_size: LVector2d, grid_spacing: float = _GRID_SPACING, name: str = "", ) -> GeomVertexData: """Generate GeomVertexData for build plate grid in Y axis. Parameters ---------- build_plate_size : LVector2d Build plate size. grid_spacing : float Grid spacing; distance between successive grid lines. name : str Generated GeomVertexData name. """ geom_data = GeomVertexData(name, cls._GEOM_VERTEX_FORMAT_GRID, Geom.UHStatic) geom_data.setNumRows(int(math.ceil(build_plate_size.y / grid_spacing))) writer_vertex = GeomVertexWriter(geom_data, "vertex") writer_color = GeomVertexWriter(geom_data, "color") current_y = grid_spacing while current_y < build_plate_size.y: writer_vertex.addData3d(LVecBase3d(0, current_y, 0)) writer_vertex.addData3d( LVecBase3d(build_plate_size.x, current_y, 0)) for _ in range(2): writer_color.addData4(cls._GRID_COLOR) current_y += grid_spacing return geom_data @classmethod def _get_node_plate(cls, loader: Loader, build_plate_size: LVector2d, name: str = "") -> NodePath: """Generate the textured build plate NodePath. This NodePath's only purpose is to display the app's logo. Parameters ---------- loader : Loader Panda3D asset loader. build_plate_size : LVector2d Builder plate size. name : str Name for the generated NodePath. """ # Geom data geom_data = cls._get_geom_data_plate(build_plate_size) # Primitive primitive = GeomTristrips(Geom.UHStatic) primitive.addVertices(0, 1, 3, 2) primitive.closePrimitive() # Geom, GeomNode geom = Geom(geom_data) geom.addPrimitive(primitive) geom_node = GeomNode("") geom_node.addGeom(geom) # NodePath node_path = NodePath(top_node_name=name) node_path.attachNewNode(geom_node) # Texture tex = loader.loadTexture(cls._PATH_TEXTURE) ts = TextureStage("ts") node_path.setTexture(ts, tex) tex.setBorderColor((0, 0, 0, 0)) tex.setWrapU(Texture.WMBorderColor) tex.setWrapV(Texture.WMBorderColor) node_path.setTransparency(TransparencyAttrib.MAlpha) node_path.setAlphaScale(cls._TEXTURE_OPACITY) texture_scale = cls._TEXTURE_SCALE width, height = build_plate_size ratio = width / height if ratio >= 1: # Landscape or square scale_v = 1 / texture_scale scale_u = scale_v * ratio else: # Portrait scale_u = 1 / texture_scale scale_v = scale_u / ratio node_path.setTexScale(ts, scale_u, scale_v) node_path.setTexOffset(ts, -0.5 * (scale_u - 1), -0.5 * (scale_v - 1)) return node_path @staticmethod def _get_geom_data_plate(build_plate_size: LVector2d, name: str = "") -> GeomVertexData: """Generate build plate GeomVertexData. Parameters ---------- build_plate_size : LVector2d Build plate size. name : str Name for the generated GeomVertexData. """ # noinspection PyArgumentList geom_data = GeomVertexData(name, GeomVertexFormat.get_v3t2(), Geom.UHStatic) geom_data.setNumRows(4) writer_vertex = GeomVertexWriter(geom_data, "vertex") writer_texture = GeomVertexWriter(geom_data, "texcoord") # Add build plate vertices writer_vertex.addData3d(0, 0, 0) writer_vertex.addData3d(build_plate_size.x, 0, 0) writer_vertex.addData3d(build_plate_size.x, build_plate_size.y, 0) writer_vertex.addData3d(0, build_plate_size.y, 0) for uv in [(0, 0), (1, 0), (1, 1), (0, 1)]: writer_texture.addData2(*uv) return geom_data