Beispiel #1
0
    def __add_line_geometry(self, lines, shading, obj=None):
        lines = lines.astype("float32", copy=False)
        mi = np.min(lines, axis=0)
        ma = np.max(lines, axis=0)

        geometry = p3s.LineSegmentsGeometry(positions=lines.reshape((-1, 2,
                                                                     3)))
        material = p3s.LineMaterial(linewidth=shading["line_width"],
                                    color=shading["line_color"])
        #, vertexColors='VertexColors'),
        lines = p3s.LineSegments2(geometry=geometry,
                                  material=material)  #type='LinePieces')
        line_obj = {
            "geometry": geometry,
            "mesh": lines,
            "material": material,
            "max": ma,
            "min": mi,
            "type": "Lines",
            "wireframe": None
        }

        if obj:
            return self.__add_object(line_obj, obj), line_obj
        else:
            return self.__add_object(line_obj)
Beispiel #2
0
 def draw_lines(self, lines, colors, line_width=1):
     positions = np.array(lines)
     colors = [[colors[i], colors[i]] for i, line in enumerate(lines)]
     g = p3js.LineSegmentsGeometry(positions=positions, colors=colors)
     m = p3js.LineMaterial(linewidth=line_width, vertexColors='VertexColors')
     lines = p3js.LineSegments2(g, m)
     self.geometry.append(lines)
     return lines
Beispiel #3
0
 def draw_line(self, line, color, line_width=1):
     positions = [[list(line[0]), list(line[1])]]
     colors = [[color, color]]
     g = p3js.LineSegmentsGeometry(positions=positions, colors=colors)
     m = p3js.LineMaterial(linewidth=line_width, vertexColors='VertexColors')
     line = p3js.LineSegments2(g, m)
     self.geometry.append(line)
     return line
Beispiel #4
0
    def add_arrow(self, quaternion, *, scale=1, color='red', linewidth=2):
        vertices = [
            (0, 0, 1),
            (-0.02, -0.01, 1),
            (0, 0.04, 1),
            (0.02, -0.01, 1),
            (0, 0, 1),
        ]
        line = _three.Line2(
            _three.LineGeometry(positions=vertices),
            _three.LineMaterial(color=color, linewidth=linewidth))
        line.scale = scale, scale, 1
        line.quaternion = tuple(quaternion)
        self._scene.add(line)

        vertices = [
            (0, 1, 0),
            (-0.02, 1, -0.01),
            (0, 1, 0.04),
            (0.02, 1, -0.01),
            (0, 1, 0),
        ]
        line = _three.Line2(
            _three.LineGeometry(positions=vertices),
            _three.LineMaterial(color=color, linewidth=linewidth))
        line.scale = scale, 1, scale
        line.quaternion = tuple(quaternion)
        self._scene.add(line)

        vertices = [
            (1, 0, 0),
            (1, -0.02, -0.01),
            (1, 0, 0.04),
            (1, 0.02, -0.01),
            (1, 0, 0),
        ]
        line = _three.Line2(
            _three.LineGeometry(positions=vertices),
            _three.LineMaterial(color=color, linewidth=linewidth))
        line.scale = 1, scale, scale
        line.quaternion = tuple(quaternion)
        self._scene.add(line)
Beispiel #5
0
 def __init__(self, segment, color, **kwargs):
     g = three.LineSegmentsGeometry(positions=self.getPositions(segment))
     m = three.LineMaterial(linewidth=10, color=color, light=True)
     super().__init__(g, m)
     self.segment = segment
Beispiel #6
0
def generate_3js_render(
    element_groups,
    canvas_size,
    zoom,
    camera_fov=30,
    background_color="white",
    background_opacity=1.0,
    reuse_objects=False,
    use_atom_arrays=False,
    use_label_arrays=False,
):
    """Create a pythreejs scene of the elements.

    Regarding initialisation performance, see: https://github.com/jupyter-widgets/pythreejs/issues/154
    """
    import pythreejs as pjs

    key_elements = {}
    group_elements = pjs.Group()
    key_elements["group_elements"] = group_elements

    unique_atom_sets = {}
    for el in element_groups["atoms"]:
        element_hash = (
            ("radius", el.sradius),
            ("color", el.color),
            ("fill_opacity", el.fill_opacity),
            ("stroke_color", el.get("stroke_color", "black")),
            ("ghost", el.ghost),
        )
        unique_atom_sets.setdefault(element_hash, []).append(el)

    group_atoms = pjs.Group()
    group_ghosts = pjs.Group()

    atom_geometries = {}
    atom_materials = {}
    outline_materials = {}

    for el_hash, els in unique_atom_sets.items():
        el = els[0]
        data = dict(el_hash)

        if reuse_objects:
            atom_geometry = atom_geometries.setdefault(
                el.sradius,
                pjs.SphereBufferGeometry(radius=el.sradius,
                                         widthSegments=30,
                                         heightSegments=30),
            )
        else:
            atom_geometry = pjs.SphereBufferGeometry(radius=el.sradius,
                                                     widthSegments=30,
                                                     heightSegments=30)

        if reuse_objects:
            atom_material = atom_materials.setdefault(
                (el.color, el.fill_opacity),
                pjs.MeshLambertMaterial(color=el.color,
                                        transparent=True,
                                        opacity=el.fill_opacity),
            )
        else:
            atom_material = pjs.MeshLambertMaterial(color=el.color,
                                                    transparent=True,
                                                    opacity=el.fill_opacity)

        if use_atom_arrays:
            atom_mesh = pjs.Mesh(geometry=atom_geometry,
                                 material=atom_material)
            atom_array = pjs.CloneArray(
                original=atom_mesh,
                positions=[e.position.tolist() for e in els],
                merge=False,
            )
        else:
            atom_array = [
                pjs.Mesh(
                    geometry=atom_geometry,
                    material=atom_material,
                    position=e.position.tolist(),
                    name=e.info_string,
                ) for e in els
            ]

        data["geometry"] = atom_geometry
        data["material_body"] = atom_material

        if el.ghost:
            key_elements["group_ghosts"] = group_ghosts
            group_ghosts.add(atom_array)
        else:
            key_elements["group_atoms"] = group_atoms
            group_atoms.add(atom_array)

        if el.get("stroke_width", 1) > 0:
            if reuse_objects:
                outline_material = outline_materials.setdefault(
                    el.get("stroke_color", "black"),
                    pjs.MeshBasicMaterial(
                        color=el.get("stroke_color", "black"),
                        side="BackSide",
                        transparent=True,
                        opacity=el.get("stroke_opacity", 1.0),
                    ),
                )
            else:
                outline_material = pjs.MeshBasicMaterial(
                    color=el.get("stroke_color", "black"),
                    side="BackSide",
                    transparent=True,
                    opacity=el.get("stroke_opacity", 1.0),
                )
            # TODO use stroke width to dictate scale
            if use_atom_arrays:
                outline_mesh = pjs.Mesh(
                    geometry=atom_geometry,
                    material=outline_material,
                    scale=(1.05, 1.05, 1.05),
                )
                outline_array = pjs.CloneArray(
                    original=outline_mesh,
                    positions=[e.position.tolist() for e in els],
                    merge=False,
                )
            else:
                outline_array = [
                    pjs.Mesh(
                        geometry=atom_geometry,
                        material=outline_material,
                        position=e.position.tolist(),
                        scale=(1.05, 1.05, 1.05),
                    ) for e in els
                ]

            data["material_outline"] = outline_material

            if el.ghost:
                group_ghosts.add(outline_array)
            else:
                group_atoms.add(outline_array)

        key_elements.setdefault("atom_arrays", []).append(data)

    group_elements.add(group_atoms)
    group_elements.add(group_ghosts)

    group_labels = add_labels(element_groups, key_elements, use_label_arrays)
    group_elements.add(group_labels)

    if len(element_groups["cell_lines"]) > 0:
        cell_line_mat = pjs.LineMaterial(
            linewidth=1,
            color=element_groups["cell_lines"].group_properties["color"])
        cell_line_geo = pjs.LineSegmentsGeometry(positions=[
            el.position.tolist() for el in element_groups["cell_lines"]
        ])
        cell_lines = pjs.LineSegments2(geometry=cell_line_geo,
                                       material=cell_line_mat)
        key_elements["cell_lines"] = cell_lines
        group_elements.add(cell_lines)

    if len(element_groups["bond_lines"]) > 0:
        bond_line_mat = pjs.LineMaterial(
            linewidth=element_groups["bond_lines"].
            group_properties["stroke_width"],
            vertexColors="VertexColors",
        )
        bond_line_geo = pjs.LineSegmentsGeometry(
            positions=[
                el.position.tolist() for el in element_groups["bond_lines"]
            ],
            colors=[[Color(c).rgb for c in el.color]
                    for el in element_groups["bond_lines"]],
        )
        bond_lines = pjs.LineSegments2(geometry=bond_line_geo,
                                       material=bond_line_mat)
        key_elements["bond_lines"] = bond_lines
        group_elements.add(bond_lines)

    group_millers = pjs.Group()
    if len(element_groups["miller_lines"]) or len(
            element_groups["miller_planes"]):
        key_elements["group_millers"] = group_millers

    if len(element_groups["miller_lines"]) > 0:
        miller_line_mat = pjs.LineMaterial(
            linewidth=3,
            vertexColors="VertexColors"  # TODO use stroke_width
        )
        miller_line_geo = pjs.LineSegmentsGeometry(
            positions=[
                el.position.tolist() for el in element_groups["miller_lines"]
            ],
            colors=[[Color(el.stroke_color).rgb] * 2
                    for el in element_groups["miller_lines"]],
        )
        miller_lines = pjs.LineSegments2(geometry=miller_line_geo,
                                         material=miller_line_mat)
        group_millers.add(miller_lines)

    for el in element_groups["miller_planes"]:
        vertices = el.position.tolist()
        faces = [(
            0,
            1,
            2,
            triangle_normal(vertices[0], vertices[1], vertices[2]),
            "black",
            0,
        )]
        if len(vertices) == 4:
            faces.append((
                2,
                3,
                0,
                triangle_normal(vertices[2], vertices[3], vertices[0]),
                "black",
                0,
            ))
        elif len(vertices) != 3:
            raise NotImplementedError("polygons with more than 4 points")
        plane_geom = pjs.Geometry(vertices=vertices, faces=faces)
        plane_mat = pjs.MeshBasicMaterial(
            color=el.fill_color,
            transparent=True,
            opacity=el.fill_opacity,
            side="DoubleSide",
        )
        plane_mesh = pjs.Mesh(geometry=plane_geom, material=plane_mat)
        group_millers.add(plane_mesh)

    group_elements.add(group_millers)

    scene = pjs.Scene(background=None)
    scene.add([group_elements])

    view_width, view_height = canvas_size

    minp, maxp = element_groups.get_position_range()
    # compute a minimum camera distance, that is guaranteed to encapsulate all elements
    camera_dist = maxp[2] + sqrt(maxp[0]**2 + maxp[1]**2) / tan(
        radians(camera_fov / 2))

    camera = pjs.PerspectiveCamera(
        fov=camera_fov,
        position=[0, 0, camera_dist],
        aspect=view_width / view_height,
        zoom=zoom,
    )
    scene.add([camera])
    ambient_light = pjs.AmbientLight(color="lightgray")
    key_elements["ambient_light"] = ambient_light
    direct_light = pjs.DirectionalLight(position=(maxp * 2).tolist())
    key_elements["direct_light"] = direct_light
    scene.add([camera, ambient_light, direct_light])

    camera_control = pjs.OrbitControls(controlling=camera,
                                       screenSpacePanning=True)

    atom_picker = pjs.Picker(controlling=group_atoms, event="dblclick")
    key_elements["atom_picker"] = atom_picker
    material = pjs.SpriteMaterial(
        map=create_arrow_texture(right=False),
        transparent=True,
        depthWrite=False,
        depthTest=False,
    )
    atom_pointer = pjs.Sprite(material=material,
                              scale=(4, 3, 1),
                              visible=False)
    scene.add(atom_pointer)
    key_elements["atom_pointer"] = atom_pointer

    renderer = pjs.Renderer(
        camera=camera,
        scene=scene,
        controls=[camera_control, atom_picker],
        width=view_width,
        height=view_height,
        alpha=True,
        clearOpacity=background_opacity,
        clearColor=background_color,
    )
    return renderer, key_elements
Beispiel #7
0
def create_world_axes(camera,
                      controls,
                      initial_rotation=np.eye(3),
                      length=30,
                      width=3,
                      camera_fov=10):
    """Create a renderer, containing an axes and camera that is synced to another camera.

    adapted from http://jsfiddle.net/aqnL1mx9/

    Parameters
    ----------
    camera : pythreejs.PerspectiveCamera
    controls : pythreejs.OrbitControls
    initial_rotation : list or numpy.array
        initial rotation of the axes
    length : int
        length of axes lines
    width : int
        line width of axes

    Returns
    -------
    pythreejs.Renderer

    """
    import pythreejs as pjs

    canvas_width = length * 2
    canvas_height = length * 2

    ax_scene = pjs.Scene()

    group_ax = pjs.Group()
    # NOTE: could use AxesHelper, but this does not allow for linewidth seletion
    # TODO: add arrow heads (ArrowHelper doesn't seem to work)
    ax_line_mat = pjs.LineMaterial(linewidth=width,
                                   vertexColors="VertexColors")
    ax_line_geo = pjs.LineSegmentsGeometry(
        positions=[[[0, 0, 0], length * r / np.linalg.norm(r)]
                   for r in initial_rotation],
        colors=[[Color(c).rgb] * 2 for c in ("red", "green", "blue")],
    )
    ax_lines = pjs.LineSegments2(geometry=ax_line_geo, material=ax_line_mat)
    group_ax.add(ax_lines)

    ax_scene.add([group_ax])

    camera_dist = length / tan(radians(camera_fov / 2))

    ax_camera = pjs.PerspectiveCamera(fov=camera_fov,
                                      aspect=canvas_width / canvas_height,
                                      near=1,
                                      far=1000)
    ax_camera.up = camera.up
    ax_renderer = pjs.Renderer(
        scene=ax_scene,
        camera=ax_camera,
        width=canvas_width,
        height=canvas_height,
        alpha=True,
        clearOpacity=0.0,
        clearColor="white",
    )

    def align_axes(change=None):
        """Align axes to world."""
        # TODO: this is not working correctly for TrackballControls, when rotated upside-down
        # (OrbitControls enforces the camera up direction,
        # so does not allow the camera to rotate upside-down).
        # TODO how could this be implemented on the client (js) side?
        new_position = np.array(camera.position) - np.array(controls.target)
        new_position = camera_dist * new_position / np.linalg.norm(
            new_position)
        ax_camera.position = new_position.tolist()
        ax_camera.lookAt(ax_scene.position)

    align_axes()

    camera.observe(align_axes, names="position")
    controls.observe(align_axes, names="target")
    ax_scene.observe(align_axes, names="position")

    return ax_renderer