context.py 9.72 KB
Newer Older
Dario Seyb's avatar
Dario Seyb committed
1 2
import pythreejs as three
import numpy as np
3 4
from ipywidgets import HTML, Text
from traitlets import link, dlink, Bool
Dario Seyb's avatar
Dario Seyb committed
5
from IPython.display import display
6
from .indexed_attribute import *
Dario Seyb's avatar
Dario Seyb committed
7
from .mesh_helper import calculateFaceNormals, calculatePointNormals
8
from .mesh import *
Dario Seyb's avatar
Dario Seyb committed
9 10

class Context(object):
11
    """
12 13
    The Context represents everything that's drawn in one output window. 
    The output of any draw* call gets added to the window. This allows you to draw multiple models in one output window.
14
    """
Dario Seyb's avatar
Dario Seyb committed
15

16 17 18 19 20
    def __init__(self, width=600, height=400, background_color = '#dddddd'):    
        """
        Initialize a new Context.
        """

Dario Seyb's avatar
Dario Seyb committed
21 22 23
        self.camera = three.PerspectiveCamera(position=[1,1,1], fov=20,
                children=[three.DirectionalLight(color='#ffffff', position=[-30, 50, 10], intensity=1.0)])

Dario Seyb's avatar
Dario Seyb committed
24
        self.camera.aspect = width/float(height)
Dario Seyb's avatar
Dario Seyb committed
25

Dario Seyb's avatar
Dario Seyb committed
26 27 28
        self.minCorner = np.array([ 100000.0,  100000.0,  100000.0])
        self.maxCorner = np.array([-100000.0, -100000.0, -100000.0])

29
        self.scene = three.Scene(children=[three.AmbientLight(color='#aaaaaa'), self.camera])
Dario Seyb's avatar
Dario Seyb committed
30
        self.scene.background = background_color
Dario Seyb's avatar
Dario Seyb committed
31 32

        self.orbit_controls = three.OrbitControls(controlling=self.camera)
33 34
        self.click_picker = three.Picker(controlling=self.scene, root=self.scene, event='dblclick')

Dario Seyb's avatar
Dario Seyb committed
35
        self.renderer = three.Renderer(camera=self.camera,  background=background_color, background_opacity=1,
36
                                       scene=self.scene, controls=[self.orbit_controls, self.click_picker],
37
                                       width=width, height=height, antialias=True, localClippingEnabled=True )
Dario Seyb's avatar
Dario Seyb committed
38

39 40 41 42 43
        def on_picked(change):
            self.orbit_controls.target = change['new']

        self.click_picker.observe(on_picked, names=['point'])

44
    def draw(self, obj, shading='flat', z_offset=0.5, texture=None, point_size = 1, perspective = False, line_width = 1, clipping_planes = []):
45 46 47 48
        """
        Draw the given object. Not all named parameters are relevant for all object types.
        """

49 50 51
        obj.prepare_render()

        if isinstance(obj, Mesh):
52
            return self.draw_faces(obj.vertices, obj.tri_face_indices, obj.normals, obj.colors, obj.uvs, shading, z_offset, texture, clipping_planes)
53
        elif isinstance(obj, EdgeList):
54
            return self.draw_edges(obj.vertices, obj.edge_indices, obj.colors, obj.uvs, z_offset, texture, line_width, clipping_planes)
55
        elif isinstance(obj, PointList):
56
            return self.draw_vertices(obj.vertices, obj.colors, obj.uvs, point_size, z_offset, texture, perspective, clipping_planes)
57 58 59 60 61

        return self
            


Dario Seyb's avatar
Dario Seyb committed
62
    def draw_faces(self, vertices, face_indices, normals=None, colors=None, uvs=None,
63
                   shading='flat', z_offset=0.5, texture=None, clipping_planes = []):
64 65 66 67
        """
        Draw a triangle mesh described by a list of vertex positions and face indices.
        face_indices is expected to be a n x 3 matrix.
        """
Dario Seyb's avatar
Dario Seyb committed
68

69 70
        assert(len(face_indices) > 0 and len(vertices) > 0)

71

Dario Seyb's avatar
Dario Seyb committed
72
        vertices = np.array(vertices)
73
        face_indices = np.array(face_indices)
Dario Seyb's avatar
Dario Seyb committed
74

Dario Seyb's avatar
Dario Seyb committed
75
        mat = None
76
        # Setup Material
Dario Seyb's avatar
Dario Seyb committed
77 78 79 80 81
        if shading == 'flat' or shading == 'hidden' or shading == 'wireframe':
            mat = three.MeshLambertMaterial(color='#dddddd', colorWrite=shading is not 'hidden')
        elif shading == 'phong':
            mat = three.MeshPhongMaterial(color='#dddddd')

82 83 84 85
        if len(clipping_planes) != 0:
            mat.clipShadows = True
            mat.clippingPlanes = tuple(clipping_planes)

Dario Seyb's avatar
Dario Seyb committed
86
        mat.wireframe = shading is 'wireframe'
87
        mat.map = texture
Dario Seyb's avatar
Dario Seyb committed
88 89 90 91 92 93

        if z_offset is not 0:
            mat.polygonOffset = True
            mat.polygonOffsetFactor = z_offset
            mat.polygonOffsetUnits = 0.1

Dario Seyb's avatar
Dario Seyb committed
94
        if normals is 'vertex':
Dario Seyb's avatar
Dario Seyb committed
95 96
            normals = calculatePointNormals(vertices, face_indices)
        elif normals is 'face':
97 98
            normals = calculateFaceNormals(vertices, face_indices)

99 100 101 102 103
        # Resolve the given attributes, it's ok if they are None
        resolved_attribs = resolve_attributes(face_indices, [normals, colors, uvs])
        resolved_normals    = resolved_attribs[0]
        resolved_colors     = resolved_attribs[1]
        resolved_uvs        = resolved_attribs[2]
Dario Seyb's avatar
Dario Seyb committed
104

105 106 107 108 109 110 111 112
        # De-index our vertices
        vertices, face_indices = stretch_vertices(vertices, face_indices)

        # Create attributes dictionary, always use at least position and index
        attributes=dict(
            position = three.BufferAttribute(np.asarray(vertices, dtype=np.float32), normalized=False),
            index =    three.BufferAttribute(np.asarray(face_indices, dtype=np.uint16).ravel(), normalized=False),
        )
Dario Seyb's avatar
Dario Seyb committed
113

114 115
        if resolved_colors is not None:
            mat.vertexColors = 'VertexColors'
Dario Seyb's avatar
Dario Seyb committed
116
            mat.color = 'white'
117
            attributes['color'] = three.BufferAttribute(np.asarray(resolved_colors, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
118

119 120 121 122 123
        if resolved_normals is not None:
            attributes['normal'] = three.BufferAttribute(np.asarray(resolved_normals, dtype=np.float32))

        if resolved_uvs is not None:
            attributes['uv'] = three.BufferAttribute(np.asarray(resolved_uvs, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
124 125 126

        mesh_geom = three.BufferGeometry(attributes=attributes)

Dario Seyb's avatar
Dario Seyb committed
127
        mesh_obj = three.Mesh(geometry=mesh_geom, material=mat)
Dario Seyb's avatar
Dario Seyb committed
128 129
        self.minCorner = np.minimum(self.minCorner, vertices.min(axis=0))
        self.maxCorner = np.maximum(self.maxCorner, vertices.max(axis=0))
Dario Seyb's avatar
Dario Seyb committed
130

Dario Seyb's avatar
Dario Seyb committed
131 132 133
        self.scene.add(mesh_obj)
        return self

134
    def draw_edges(self, vertices, edge_indices=None, colors=None, uvs=None, z_offset=0, texture=None, line_width=1, clipping_planes = []):
135 136 137 138
        """
        Draw a list of edges described by a list of vertex positions and edge indices.
        edge_indices is expected to be a n x 2 matrix. If no indices are given, a continous line is connecting the vertices is drawn.
        """
Dario Seyb's avatar
Dario Seyb committed
139

Dario Seyb's avatar
Dario Seyb committed
140
        mat = three.LineBasicMaterial(color='#000000')
141
        mat.linewidth = line_width
142
        mat.map = texture
Dario Seyb's avatar
Dario Seyb committed
143

144 145 146 147
        if len(clipping_planes) != 0:
            mat.clipShadows = True
            mat.clippingPlanes = clipping_planes

Dario Seyb's avatar
Dario Seyb committed
148 149 150 151
        if z_offset is not 0:
            mat.polygonOffset = True
            mat.polygonOffsetFactor = z_offset
            mat.polygonOffsetUnits = 0.1
Dario Seyb's avatar
Dario Seyb committed
152

153 154
        if edge_indices is None:
            edge_indices = zip(np.arange(0, len(vertices)-1), np.arange(1, len(vertices)))
Dario Seyb's avatar
Dario Seyb committed
155

Dario Seyb's avatar
Dario Seyb committed
156 157
        assert(len(edge_indices) > 0 and len(vertices) > 0)

Dario Seyb's avatar
Dario Seyb committed
158
        vertices = np.array(vertices)
Dario Seyb's avatar
Dario Seyb committed
159
        edge_indices = np.array(edge_indices)
Dario Seyb's avatar
Dario Seyb committed
160

161 162 163 164 165 166 167 168 169 170 171 172 173
        resolved_attribs = resolve_attributes(edge_indices, [colors, uvs])
        resolved_colors = resolved_attribs[0]
        resolved_uvs    = resolved_attribs[1]

        vertices, edge_indices = stretch_vertices(vertices, edge_indices)

        attributes=dict(
            position = three.BufferAttribute(np.asarray(vertices, dtype=np.float32), normalized=False),
            index =    three.BufferAttribute(np.asarray(edge_indices, dtype=np.uint16).ravel(), normalized=False),
        )

        if resolved_colors is not None:
            mat.vertexColors = 'VertexColors'
Dario Seyb's avatar
Dario Seyb committed
174
            mat.color = 'white'
175
            attributes['color'] = three.BufferAttribute(np.asarray(resolved_colors, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
176

177 178
        if resolved_uvs is not None:
            attributes['uv'] = three.BufferAttribute(np.asarray(resolved_uvs, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
179

Dario Seyb's avatar
Dario Seyb committed
180
        geom = three.BufferGeometry(attributes = attributes)
Dario Seyb's avatar
Dario Seyb committed
181

Dario Seyb's avatar
Dario Seyb committed
182
        mesh_obj = three.LineSegments(geometry=geom, material=mat)
Dario Seyb's avatar
Dario Seyb committed
183

Dario Seyb's avatar
Dario Seyb committed
184 185
        self.minCorner = np.minimum(self.minCorner, vertices.min(axis=0))
        self.maxCorner = np.maximum(self.maxCorner, vertices.max(axis=0))
Dario Seyb's avatar
Dario Seyb committed
186 187 188 189

        self.scene.add(mesh_obj)
        return self

190
    def draw_vertices(self, vertices, colors=None, uvs=None, point_size=1, z_offset=0, texture=None, perspective=False, clipping_planes = []):
191 192 193
        """
        Draw a list of unconnected vertices.
        """
Dario Seyb's avatar
Dario Seyb committed
194

Dario Seyb's avatar
Dario Seyb committed
195
        vertices = np.array(vertices)
Dario Seyb's avatar
Dario Seyb committed
196 197 198 199 200
        matColor = colors

        if colors is None:
            matColor = 'black'
        elif hasattr(colors, '__len__') and (not isinstance(colors, str)):
201
            matColor = '#ffffff'
Dario Seyb's avatar
Dario Seyb committed
202 203 204 205 206 207 208 209
        else:
            colors = None

        attributes = dict(
            position = three.BufferAttribute(np.asarray(vertices, dtype=np.float32), normalized=False),
        )

        mat = three.PointsMaterial(color=matColor, sizeAttenuation=perspective, size=point_size)
210
        mat.map = texture
Dario Seyb's avatar
Dario Seyb committed
211

212 213 214 215
        if len(clipping_planes) != 0:
            mat.clipShadows = True
            mat.clippingPlanes = clipping_planes

Dario Seyb's avatar
Dario Seyb committed
216 217 218 219 220 221 222 223
        if z_offset is not 0:
            mat.polygonOffset = True
            mat.polygonOffsetFactor = z_offset
            mat.polygonOffsetUnits = 0.1

        if colors is not None:
            attributes['color'] = three.BufferAttribute(np.asarray(colors, dtype=np.float32))
            mat.vertexColors = 'VertexColors'
224 225 226
        
        if uvs is not None:
            attributes['uv'] = three.BufferAttribute(np.asarray(uvs, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
227

Dario Seyb's avatar
Dario Seyb committed
228 229
        geom = three.BufferGeometry(attributes=attributes)
        mesh_obj = three.Points(geometry=geom, material=mat)
Dario Seyb's avatar
Dario Seyb committed
230

Dario Seyb's avatar
Dario Seyb committed
231 232
        self.minCorner = np.minimum(self.minCorner, vertices.min(axis=0))
        self.maxCorner = np.maximum(self.maxCorner, vertices.max(axis=0))
Dario Seyb's avatar
Dario Seyb committed
233

Dario Seyb's avatar
Dario Seyb committed
234 235 236 237
        self.scene.add(mesh_obj)
        return self

    def display(self):
238 239 240 241
        """
        Display a window containing all the previous draw calls.
        """

Dario Seyb's avatar
Dario Seyb committed
242 243 244
        center = (self.minCorner + self.maxCorner) * 0.5
        distance = max(np.linalg.norm(center-self.minCorner), np.linalg.norm(center-self.maxCorner))
        self.camera.position = tuple( np.array(self.camera.position) * distance * 5)
Dario Seyb's avatar
Dario Seyb committed
245 246
        self.orbit_controls.target = tuple(center)

Dario Seyb's avatar
Dario Seyb committed
247
        display(self.renderer)