Example #1
0
 def add_text(self, text, shading={}, **kwargs):
     shading.update(kwargs)
     sh = self.__get_shading(shading)
     tt = p3s.TextTexture(string=text, color=sh["text_color"])
     sm = p3s.SpriteMaterial(map=tt)
     self.text = p3s.Sprite(material=sm, scaleToTexture=True)
     self.scene.add(self.text)
Example #2
0
def _create_text_sprite(position, bounding_box, display_text):
    # Position offset in y
    text_position = tuple(position.value +
                          np.array([0, 0.8 * bounding_box[1], 0]))
    text = p3.TextTexture(string=display_text, color='black', size=300)
    text_material = p3.SpriteMaterial(map=text, transparent=True)
    size = 1.0
    return p3.Sprite(material=text_material,
                     position=text_position,
                     scale=[size, size, size])
Example #3
0
 def _make_axis_tick(self, string, position, color="black", size=1.0):
     """
     Make a text-based sprite for axis tick
     """
     sm = p3.SpriteMaterial(map=p3.TextTexture(string=string,
                                               color=color,
                                               size=300,
                                               squareTexture=True),
                            transparent=True)
     return p3.Sprite(material=sm,
                      position=position,
                      scaleToTexture=True,
                      scale=[size, size, size])
Example #4
0
def add_labels(element_groups, key_elements, use_label_arrays):
    """Create label elements for the scene."""
    import pythreejs as pjs

    group_labels = pjs.Group()
    unique_label_sets = {}

    for el in element_groups["atoms"]:
        if "label" in el and el.label is not None:
            unique_label_sets.setdefault(
                (("label", el.label),
                 ("color", el.get("font_color", "black"))), []).append(el)

    if unique_label_sets:
        key_elements["group_labels"] = group_labels

    for el_hash, els in unique_label_sets.items():
        el = els[0]
        data = dict(el_hash)
        # depthWrite=depthTest=False is required, for the sprite to remain on top,
        # and not have the whitespace obscure objects behind, see:
        # https://stackoverflow.com/questions/11165345/three-js-webgl-transparent-planes-hiding-other-planes-behind-them
        # TODO can this be improved?
        text_material = pjs.SpriteMaterial(
            map=pjs.TextTexture(
                string=el.label,
                color=el.get("font_color", "black"),
                size=2000,  # this texttexture size seems to work, not sure why?
            ),
            opacity=1.0,
            transparent=True,
            depthWrite=False,
            depthTest=False,
        )
        data["material"] = text_material
        key_elements.setdefault("label_arrays", []).append(data)
        if use_label_arrays:
            text_sprite = pjs.Sprite(material=text_material)
            label_array = pjs.CloneArray(
                original=text_sprite,
                positions=[e.position.tolist() for e in els],
                merge=False,
            )
        else:
            label_array = [
                pjs.Sprite(material=text_material,
                           position=e.position.tolist()) for e in els
            ]
        group_labels.add(label_array)

    return group_labels
Example #5
0
 def add_text(self, text, shading={}):
     self.update_shading(shading)
     tt = p3s.TextTexture(string=text, color=self.s["text_color"])
     sm = p3s.SpriteMaterial(map=tt)
     self.text = p3s.Sprite(material=sm, scaleToTexture=True)
     self.scene.add(self.text)
Example #6
0
def create_jslabelmesh_view(gobject, mapping=None, jslink=False):
    """create PyThreeJS Text Mesh for GeometricObject
    and with one-way synchronisation

    Properties
    ----------
    gobject : GeometricObject
    mapping : None or dict
        if None, use default gobject->jsobject mapping
    jslink : bool
        if True, where possible, create client side links
        http://ipywidgets.readthedocs.io/en/latest/examples/Widget%20Events.html#The-difference-between-linking-in-the-kernel-and-linking-in-the-client 

    Examples
    --------
    
    >>> import pandas3js as pjs
    >>> sphere = pjs.models.Sphere()
    >>> lmesh = pjs.views.create_jslabelmesh_view(sphere)
    >>> lmesh.position
    [0.0, 0.0, 0.0]
    >>> str(lmesh.material.map.string)
    '-'
    >>> lmesh.scale
    [1.0, 1.0, 1.0]
    
    >>> sphere.position = (1,0,0)
    >>> lmesh.position
    [1.0, 0.0, 0.0]
    
    >>> sphere.label = 'test'
    >>> str(lmesh.material.map.string)
    'test'
                  
    >>> sphere.radius = 3.0
    >>> lmesh.scale
    [1.0, 3.0, 1.0]
    
    """
    if jslink:
        direct_link = widget.jsdlink
    else:
        direct_link = trait.dlink

    class_str = obj_to_str(gobject)
    if hasattr(gobject, '_use_default_viewmap'):
        class_map = copy.deepcopy(gobject_jsmapping['default'])
        class_map['grep'] = 'pythreejs.' + class_str.split('.')[-1]
        # directly link all traits to geometry object
        for trait_name in gobject.class_own_traits():
            class_map['gdmap'][trait_name] = trait_name
        if gobject._use_default_viewmap is not None:
            class_map['show_label'] = True
            class_map['label_height'] = gobject._use_default_viewmap
    elif not class_str in gobject_jsmapping:
        raise ValueError(
            'No pythreejs mapping available for {}'.format(class_str))
    else:
        class_map = gobject_jsmapping[class_str]

    text_map = js.TextTexture(string=gobject.label,
                              color=colors.to_hex(gobject.label_color),
                              size=100,
                              squareTexture=False)
    material = js.SpriteMaterial(map=text_map,
                                 opacity=gobject.label_transparency,
                                 transparent=False,
                                 depthTest=False,
                                 depthWrite=True)
    height = class_map['label_height']
    height_attr = getattr(gobject, height) if isinstance(
        height, basestring) else height
    mesh = js.Sprite(material=material,
                     position=gobject.position,
                     scaleToTexture=True,
                     scale=[1, height_attr, 1])

    # add special traits
    mesh.add_traits(gobject_id=HashableType())
    mesh.gobject_id = gobject.id
    mesh.add_traits(other_info=trait.CUnicode().tag(sync=True))

    if not class_map['show_label']:
        mesh.visible = False
        return mesh

    # add directional synchronisation
    direct_link((gobject, 'other_info'), (mesh, 'other_info'))
    direct_link((gobject, 'label'), (text_map, 'string'))
    direct_link((gobject, 'position'), (mesh, 'position'))
    direct_link((gobject, 'label_visible'), (mesh, 'visible'))
    direct_link((gobject, 'label_transparency'), (material, 'opacity'))
    trait.dlink((gobject, 'label_color'), (text_map, 'color'), colors.to_hex)
    trait.dlink((gobject, 'label_transparency'), (material, 'transparent'),
                lambda t: True if t <= 0.999 else False)

    if isinstance(height, basestring):

        def change_height(change):
            mesh.scale = [1, change.new, 1]

        gobject.observe(change_height, names=height)

    return mesh
Example #7
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