Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 5
0
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)
Esempio n. 6
0
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
Esempio n. 7
0
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