context.py 11.5 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'])

Dario Seyb's avatar
Dario Seyb committed
44 45
        self.show_bounds = False

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

51 52 53
        obj.prepare_render()

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

        return self
            


Dario Seyb's avatar
Dario Seyb committed
64
    def draw_faces(self, vertices, face_indices, normals=None, colors=None, uvs=None,
65
                   shading='flat', z_offset=0.5, texture=None, clipping_planes = []):
66 67 68 69
        """
        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
70

71 72
        assert(len(face_indices) > 0 and len(vertices) > 0)

73

Dario Seyb's avatar
Dario Seyb committed
74
        vertices = np.array(vertices)
75
        face_indices = np.array(face_indices)
Dario Seyb's avatar
Dario Seyb committed
76

Dario Seyb's avatar
Dario Seyb committed
77
        mat = None
78
        # Setup Material
Dario Seyb's avatar
Dario Seyb committed
79 80 81 82 83
        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')

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

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

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

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

101 102 103 104 105
        # 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
106

107 108 109 110 111 112 113 114
        # 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
115

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

121 122 123 124 125
        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
126 127 128

        mesh_geom = three.BufferGeometry(attributes=attributes)

Dario Seyb's avatar
Dario Seyb committed
129
        mesh_obj = three.Mesh(geometry=mesh_geom, material=mat)
Dario Seyb's avatar
Dario Seyb committed
130 131
        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
132

Dario Seyb's avatar
Dario Seyb committed
133 134 135
        self.scene.add(mesh_obj)
        return self

136
    def draw_edges(self, vertices, edge_indices=None, colors=None, uvs=None, z_offset=0, texture=None, line_width=1, clipping_planes = []):
137 138 139 140
        """
        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
141

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

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

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

155 156
        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
157

Dario Seyb's avatar
Dario Seyb committed
158 159
        assert(len(edge_indices) > 0 and len(vertices) > 0)

Dario Seyb's avatar
Dario Seyb committed
160
        vertices = np.array(vertices)
Dario Seyb's avatar
Dario Seyb committed
161
        edge_indices = np.array(edge_indices)
Dario Seyb's avatar
Dario Seyb committed
162

163 164 165 166 167 168 169 170 171 172 173 174 175
        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
176
            mat.color = 'white'
177
            attributes['color'] = three.BufferAttribute(np.asarray(resolved_colors, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
178

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

Dario Seyb's avatar
Dario Seyb committed
182
        geom = three.BufferGeometry(attributes = attributes)
Dario Seyb's avatar
Dario Seyb committed
183

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

Dario Seyb's avatar
Dario Seyb committed
186 187
        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
188 189 190 191

        self.scene.add(mesh_obj)
        return self

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

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

        if colors is None:
            matColor = 'black'
        elif hasattr(colors, '__len__') and (not isinstance(colors, str)):
203
            matColor = '#ffffff'
Dario Seyb's avatar
Dario Seyb committed
204 205 206 207 208 209 210 211
        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)
212
        mat.map = texture
Dario Seyb's avatar
Dario Seyb committed
213

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

Dario Seyb's avatar
Dario Seyb committed
218 219 220 221 222 223 224 225
        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'
226 227 228
        
        if uvs is not None:
            attributes['uv'] = three.BufferAttribute(np.asarray(uvs, dtype=np.float32))
Dario Seyb's avatar
Dario Seyb committed
229

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

Dario Seyb's avatar
Dario Seyb committed
233 234
        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
235

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

Dario Seyb's avatar
Dario Seyb committed
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
    def setCameraPosition(self, position):
        self.camera.position = tuple(position)

    def showBounds(self):
        self.show_bounds = True
    
    def hideBounds(self):
        self.show_bounds = False

    def setBounds(self, val):
        self.show_bounds = val

    def draw_text(self, text, position=(0, 0, 0), color='white', size=100, height=1):
        """
        Draw a text object at the specified location with a given height
        """
        height *= len(text)
        sm = three.SpriteMaterial(map=three.TextTexture(string=text, color=color, size=size, squareTexture=True))
        sprite = three.Sprite(material=sm, position=tuple(position), scaleToTexture=True, scale=[height, height, 1])
        self.scene.add(sprite)

Dario Seyb's avatar
Dario Seyb committed
260
    def display(self):
261 262 263 264
        """
        Display a window containing all the previous draw calls.
        """

Dario Seyb's avatar
Dario Seyb committed
265 266 267
        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
268 269
        self.orbit_controls.target = tuple(center)

Dario Seyb's avatar
Dario Seyb committed
270 271 272 273 274 275 276 277 278 279 280 281 282 283
        if self.show_bounds:
            extends = self.maxCorner - self.minCorner
            boxEdges = [self.minCorner, 
                        self.minCorner + [extends[0], 0, 0], 
                        self.minCorner + [0, extends[1], 0],
                        self.minCorner + [0, 0, extends[2]]]

            self.draw_edges(boxEdges, [[0, 1], [0, 2], [0, 3]], FaceAttribute([[1, 0, 0], [0, 1, 0], [0, 0, 1]]))
            self.draw_text(str(map( lambda v: round(v, 3), self.minCorner)), self.minCorner - [0, extends[1]*0.1, 0], color = 'black', height=extends[1]/25)
            
            self.draw_text(str(round(extends[0], 3)), (boxEdges[0] * 0.25 + boxEdges[1] * 0.75), color = 'black', height=extends[1]/20)
            self.draw_text(str(round(extends[1], 3)), (boxEdges[0] * 0.25 + boxEdges[2] * 0.75), color = 'black', height=extends[1]/20)
            self.draw_text(str(round(extends[2], 3)), (boxEdges[0] * 0.25 + boxEdges[3] * 0.75), color = 'black', height=extends[1]/20)

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