Beispiel #1
0
    def __init__(self, renderer, max_update_delay=0.2, *args, **keys):
        ''' Inits this canvas.
            Things setupped: background color, default camera, node factory,
            model/view/primitve renderer registries, adapter registry and
            render/update policies.
        '''

        if 'name' not in keys:
            keys['name'] = 'unnamed canvas'

        self.renderer = renderer

        if 'backgroundColor' in keys:
            self.backgroundColor = keys['backgroundColor']
            del keys['backgroundColor']
        else:
            self.backgroundColor = None

        if 'renderPolicy' in keys:
            self.renderPolicy = keys['renderPolicy']
            del keys['renderPolicy']
        else:
            #self.renderPolicy = DefaultRenderPolicy()
            self.renderPolicy = CullingRenderPolicy()

        Canvas.__init__(self, self.renderer, False, None, None, None, None,
                        observables.ObservableLinearTransform2D(), *args,
                        **keys)

        self.camera = observables.ObservableCamera(
            observables.ObservableLinearTransform2D(), name='default camera')
        self.addChild(self.camera)

        self.model_kinds = [
            'Rectangle', 'RoundedRectangle', 'Circle', 'Ellipse', 'Arc',
            'Text', 'Line', 'LineLength', 'Lines', 'LinesList', 'LineSegments',
            'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline',
            'Polygon', 'PolygonList', 'Arrow', 'AngleArrow'
        ]
        self.primitive_kinds = [
            'Rectangle', 'RoundedRectangle', 'Ellipse', 'Arc', 'Text',
            'LinesList', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline',
            'QuadraticSpline', 'PolygonList', 'Arrow'
        ]

        self._setupRegistries()
        self._setupNodeFactory()
        self._setupAdapters()

        if 'updatePolicy' in keys:
            self.updatePolicy = keys['updatePolicy']
            del keys['updatePolicy']
        else:
            self.updatePolicy = DefaultUpdatePolicy(self, max_update_delay)

        self.controllers = []

        self.subscribe(self.onDirty, 'attribChanged')
Beispiel #2
0
    def __init__(self, renderer, max_update_delay = 0.2, *args, **keys):
        ''' Inits this canvas.
            Things setupped: background color, default camera, node factory,
            model/view/primitve renderer registries, adapter registry and
            render/update policies.
        '''
        
        if 'name' not in keys:
            keys['name'] = 'unnamed canvas'

        self.renderer = renderer

        if 'backgroundColor' in keys:
            self.backgroundColor = keys['backgroundColor']
            del keys['backgroundColor']
        else:
            self.backgroundColor = None
        
        if 'renderPolicy' in keys:
            self.renderPolicy = keys['renderPolicy']
            del keys['renderPolicy']
        else:
            #self.renderPolicy = DefaultRenderPolicy()
            self.renderPolicy = CullingRenderPolicy()

        Canvas.__init__(self, self.renderer, False, None, None, None, None, observables.ObservableLinearTransform2D(), *args, **keys)
               
        self.camera = observables.ObservableCamera( observables.ObservableLinearTransform2D(), name = 'default camera' )
        self.addChild( self.camera )
        
        self.model_kinds = [ 'Rectangle', 'RoundedRectangle', 'Circle', 'Ellipse', 'Arc', 'Text', 'Line', 'LineLength', 'Lines', 'LinesList', 'LineSegments', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline', 'Polygon', 'PolygonList', 'Arrow', 'AngleArrow' ]
        self.primitive_kinds = [ 'Rectangle', 'RoundedRectangle', 'Ellipse', 'Arc', 'Text', 'LinesList', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline', 'PolygonList', 'Arrow' ]

        self._setupRegistries()
        self._setupNodeFactory()
        self._setupAdapters()

        if 'updatePolicy' in keys:
            self.updatePolicy = keys['updatePolicy']
            del keys['updatePolicy']
        else:
            self.updatePolicy = DefaultUpdatePolicy( self, max_update_delay )

        self.controllers = []
        
        self.subscribe( self.onDirty, 'attribChanged' )
Beispiel #3
0
class SimpleCanvas(Canvas):
    ''' I provide an various methods for a functional canvas.
        This includes registries so I can create according nodes/views/
        primitive renderers when given a model.
        I also provide a bunch of default models (and know how to map them
        to their views).
        I provide a default camera.
        Rendering and updates can be customized with policies.
        Other things I do include: zooming, screen shots, hit-testing, adapter
        registry. 
    '''

    def __init__(self, renderer, max_update_delay = 0.2, *args, **keys):
        ''' Inits this canvas.
            Things setupped: background color, default camera, node factory,
            model/view/primitve renderer registries, adapter registry and
            render/update policies.
        '''
        
        if 'name' not in keys:
            keys['name'] = 'unnamed canvas'

        self.renderer = renderer

        if 'backgroundColor' in keys:
            self.backgroundColor = keys['backgroundColor']
            del keys['backgroundColor']
        else:
            self.backgroundColor = None
        
        if 'renderPolicy' in keys:
            self.renderPolicy = keys['renderPolicy']
            del keys['renderPolicy']
        else:
            #self.renderPolicy = DefaultRenderPolicy()
            self.renderPolicy = CullingRenderPolicy()

        Canvas.__init__(self, self.renderer, False, None, None, None, None, observables.ObservableLinearTransform2D(), *args, **keys)
               
        self.camera = observables.ObservableCamera( observables.ObservableLinearTransform2D(), name = 'default camera' )
        self.addChild( self.camera )
        
        self.model_kinds = [ 'Rectangle', 'RoundedRectangle', 'Circle', 'Ellipse', 'Arc', 'Text', 'Line', 'LineLength', 'Lines', 'LinesList', 'LineSegments', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline', 'Polygon', 'PolygonList', 'Arrow', 'AngleArrow' ]
        self.primitive_kinds = [ 'Rectangle', 'RoundedRectangle', 'Ellipse', 'Arc', 'Text', 'LinesList', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline', 'PolygonList', 'Arrow' ]

        self._setupRegistries()
        self._setupNodeFactory()
        self._setupAdapters()

        if 'updatePolicy' in keys:
            self.updatePolicy = keys['updatePolicy']
            del keys['updatePolicy']
        else:
            self.updatePolicy = DefaultUpdatePolicy( self, max_update_delay )

        self.controllers = []
        
        self.subscribe( self.onDirty, 'attribChanged' )


    def _setupRegistries(self):
        ''' Setups the registries. First one is the adapter registry which
            has all the information how models can be adapted to different
            interfaces.
            The other registries know how to map a model interface to a node()
            view/primitive renderer.
            They're fed with the default objects (all kinds of models, primitive
            renderers, ...)
            Finally, there's the serialization registry where you can register
            serializers for nodes.
        '''
        self.adapterRegistry = adapterRegistry = AdapterRegistry()
        
        self.primitiveRendererRegistry = PrimitiveRendererRegistry( adapterRegistry )
        self.viewRegistry = ViewRegistry( adapterRegistry )
        self.renderNodeRegistry = RenderNodeRegistry( adapterRegistry )
        
        for primitive_kind in self.primitive_kinds:
            primitiveRendererType = getattr(views, 'Default%sRenderer' % primitive_kind)
            modelInterface = primitiveRendererType.can_render
            self.primitiveRendererRegistry.register( modelInterface, primitiveRendererType )
            
        for model_kind in self.model_kinds:
            modelInterface = getattr(models, 'I%s' % model_kind)
            def createDefaultView(model, look, scaled):
                primitiveRendererConstructor, model = self.primitiveRendererRegistry.getRendererConstructor( model )
                primitiveRenderer = primitiveRendererConstructor()
                primitiveRenderer = observables.ObservableBaseRenderer( self.renderer, look, model, primitiveRenderer, scaled )
                return observables.ObservableDefaultView( look, primitiveRenderer )
            self.viewRegistry.register( modelInterface, createDefaultView )
            
        for model_kind in self.model_kinds:
            modelInterface = getattr(models, 'I%s' % model_kind)
            def createDefaultRenderableNode(model, renderer, transform, look, scaled, name, render_to_surface, surface_size, filter, show):
                viewConstructor, model = self.viewRegistry.getViewConstructor( model )
                view = viewConstructor( model = model, look = look, scaled = scaled )
                renderNode = observables.ObservableDefaultRenderableNode( renderer, render_to_surface, surface_size, filter, model, view, transform, show, name )
                return renderNode
            self.renderNodeRegistry.register( modelInterface, createDefaultRenderableNode )        


        # create serialization registries
        self.serializerRegistry = FactoryUsingDict()
        self.unserializerRegistry = FactoryUsingDict()

        def createNativeSerializer():
            # import those two here, because they might need to import the simpleCanvas module itself
            # so importing here avoids a circular dependency
            from ..serialization.serializer import Serializer
            from ..serialization import defaultSerializers
            serializer = Serializer()
            defaultSerializers.registerDefaultSerializers( serializer.nodeSerializerRegistry )
            return serializer

        # register our native serializer and unserializer here
        self.serializerRegistry.register( 'fcsf', createNativeSerializer )
        self.unserializerRegistry.register( 'fcsf', createNativeSerializer )
        
        def createSvgSerializer():
            # import those two here, because they might need to import the simpleCanvas module itself
            # so importing here avoids a circular dependency
            from ..serialization.svg import SVGExporter
            from ..serialization.svg import defaultSVGSerializers
            serializer = SVGExporter( self.adapterRegistry )
            defaultSVGSerializers.registerDefaultSVGSerializers( serializer.elementCreatorRegistry )
            defaultSVGSerializers.registerDefaultNodeSerializers( serializer.nodeSerializerRegistry )

            # register the viewNodel-from-model connections to the svg exporter
            for model_kind in self.model_kinds:
                modelInterface = getattr(models, 'I%s' % model_kind)
                def createViewModel(model, look):
                    viewModelConstructor, model = self.primitiveRendererRegistry.getRendererConstructor( model )
                    viewModel = viewModelConstructor()
                    return viewModel, model
                serializer.viewModelCreatorRegistry.register( modelInterface, createViewModel )

            return serializer

        # register svg exporter, no importer defined        
        self.serializerRegistry.register( 'svg', createSvgSerializer )
        
        
    def _setupAdapters(self):
        ''' Internal. Feeds the adapter registries with the default model adapters '''
        for (from_interface, to_interface, adapter) in defaultAdapters:
            self.adapterRegistry.register( from_interface, to_interface, adapter )


    def create(self, *args, **keys):
        ''' Creates a node from a model. 
            Forwarded from nodeFactory.
        '''            
        return self.nodeFactory.create( *args, **keys )

    def registerNode(self, model_kind, create, modelType):
        ''' registers a node constructor for a model type.
            Forwarded from nodeFactory.
        '''
        setattr( self, 'create%s' % model_kind, partial( create, modelType ) )
        return self.nodeFactory.register( model_kind, create, modelType )


    def unregisterNode(self, *args, **keys):
        ''' unregisters a node constructor for a model type.
            Forwarded from nodeFactory.
        '''
        return self.nodeFactory.unregister( *args, **keys )

    def isNodeRegistered(self, *args, **keys):
        ''' is a node constructor for a model type registered?
            Forwarded from nodeFactory.
        '''
        return self.nodeFactory.is_registered( *args, **keys )
    
    def createFromModel(self, model, **keys):
        keywords = { 'transform'            : None,
                     'pos'                  : None,
                     'position'             : None,
                     'rotation'             : None,
                     'scale'                : None,
                     'look'                 : None,
                     'where'                : 'front',
                     'scaled'               : True,
                     'render_to_surface'    : False,
                     'surface_size'         : (500, 500),
                     'filter'               : None,
                     'parent'               : self,
                     'name'                 : '<unnamed node>',
                     'show'                 : True,
                   }
        
        def get_keyword(dikt, name):
            return dikt.pop( name, keywords[name] )

        where = get_keyword(keys, 'where')
        transform = get_keyword(keys, 'transform')
        if transform is None:
            transform = observables.ObservableLinearTransform2D()
        elif isinstance( transform, basestring ):
            transform = getattr(observables, transform)()
            transform = observables.ObservableLinearTransform2D() * transform
        else:
            transform = observables.ObservableLinearTransform2D() * transform

        pos = get_keyword(keys, 'pos') or get_keyword(keys, 'position')
        if pos is not None:
            # assume linear transform
            transform.position = pos
            
        rotation = get_keyword(keys, 'rotation')
        if rotation is not None:
            # assume linear transform
            transform.rotation = rotation
        
        scale = get_keyword(keys, 'scale')
        if scale is not None:
            # assume linear transform
            transform.scale = scale

        scaled = get_keyword(keys, 'scaled')
        parent = get_keyword(keys, 'parent')
        name = get_keyword(keys, 'name')
        show = get_keyword(keys, 'show')

        
        # renderable node properties
        render_to_surface = get_keyword(keys, 'render_to_surface')
        surface_size = get_keyword(keys, 'surface_size')

        filter = get_keyword(keys, 'filter')

        if not model is None:
            look = get_keyword(keys, 'look')
            if look is None:
                raise ValueError( 'You need to supply a look! Use look.NoLook or "nolook" if you want none.')
            if look == 'nolook':
                look = NoLook
            if isinstance(look, (tuple, list)):
                look = observables.ObservableSolidColourLook(*look)

            renderNodeConstructor, model = self.renderNodeRegistry.getRenderNodeConstructor( model )
            renderNode = renderNodeConstructor( model, renderer = self.renderer, transform = transform, look = look, name = name, scaled = scaled, render_to_surface = render_to_surface, surface_size = surface_size, filter = filter, show = show, **keys )
        else:
            renderNode = observables.ObservableDefaultRenderableNode( self.renderer, render_to_surface, surface_size, filter, None, None, transform = transform, name = name )

        parent.addChild( renderNode, where = where )

        return renderNode        
        
    def _setupNodeFactory(self):
        ''' Internal. Sets up the node factory. The keyword argument for the
            create method is done here, as well as adding the createRectangle,
            create* methods to self.
        '''
        self.nodeFactory = FactoryUsingDict()        
               
        for model_kind in self.model_kinds:
            modelType = getattr(observables, 'Observable%s' % model_kind)
            self.registerModel( model_kind, modelType, None, False )
        
        # the group node is a special one, it doesn't need any model
        self.registerNode( 'Group', self.createFromModel, None )

    def registerModel(self, name, modelClass, observableAttributes, makeObservable = True):
        def create(modelType, *args, **keys):                                       
            model = modelType( *args )
            return self.createFromModel( model, **keys )
                
        if makeObservable:
            modelClass = observables.createObservableClass( modelClass, observableAttributes )

        self.registerNode( name, create, modelClass )
        return modelClass
        
    def registerModelAndView(self, name, modelClass, observableAttributes, makeObservable, nodeConstructor):
        modelClass = self.registerModel( name, modelClass, observableAttributes, makeObservable )
        self.renderNodeRegistry.register( modelClass, nodeConstructor )

    def onDirty(self, evt):
        ''' If we're dirty, tell the update policy '''
        if self.dirty:
            self.updatePolicy.onDirty()

    def DoRender(self, renderer, camera):
        ''' The canvas itself doesn't have to render anything '''
        pass
    
    def Render(self, backgroundColor = None, camera = None, renderChildren = True):
        ''' Render all objects on the canvas with camera. By default the default
            camera is used.
            Calls the render policy.
        '''
        for controller in self.controllers:
            controller.update()
        backgroundColor = backgroundColor or self.backgroundColor
        if camera is None:
            camera = self.camera
        self.renderPolicy.render(self, camera, backgroundColor)
        camera.dirty = False
        camera.transform.dirty = False
        self.dirty = False
        self._children.dirty = False
        
    def zoomToExtents(self, boundingBox = None, padding_percent = 0.05, maintain_aspect_ratio = True):
        ''' Tells the default camera to fit the entire canvas nodes on the
            screen.
        '''
        if boundingBox is None:
            boundingBox = self.boundingBoxRecursive #self.rtree.boundingBox
            
        self.camera.zoomToExtents( boundingBox, padding_percent, maintain_aspect_ratio )
        
    def zoom(self, factor, center = None, centerCoords = 'world', alignment = 'cc'):
        ''' Zooms and possibly recenters the default camera on the canvas. The
            center coordinates can be given in 'world' and 'pixel' coordinates.
            Todo: Make alignment work. This specifies how to zoom (for example
            should the left upper corner stay the same and the rest is zoomed).
            Todo: Make the world/pixel a property of the coordinate. Then we can
                  do something like center.world() which retrieves the
                  coordinate in world units, no matter if it was in pixel
                  or world coordinates before.
        '''
        self.camera.zoom *= factor
        
        if not center is None:
            if centerCoords == 'pixel':
                center = self.pointToWorld( center )
            self.camera.position = center
        
        
    def pointToWorld(self, screen_pnt):
        ''' Transform a point on screen to world coordinates (if possible) '''
        return self.camera.viewTransform.inverse( (screen_pnt,) )[0]

    def hitTest( self, screen_pnt, exact = True, order = True ):
        ''' Performs a hit test given a point on screen.
            If exact is False then an intersection test only against the bounding boxes
            of the nodes is performed. exact = True is only valid for point
            queries and does not only check the node bounding boxes against this
            query's point, but performs more elaborate checks to see if the
            query point is inside the bounds of the node.
            The return value is a list of the nodes which have been hit. If order
            is True, the nodes in the list are ordered front-to-back, meaning the
            first element is the top-most node, the last element is the "back-most" node.
        '''
        world_pnt = self.pointToWorld( screen_pnt )
        query = QueryWithPrimitive( primitive = boundingBox.fromPoint( world_pnt ), exact = exact )
        pickedNodes = self.performSpatialQuery( query, order = order )
        
        return pickedNodes


    def getScreenshot(self, file_format, renderFirst = True):
        ''' Returns the rendered picture as a string with file_format, where
            file format can be something like 'png', 'jpg' or 'raw' or any other
            kind of supported image format.
            If renderFirst is true, the canvas will be rendered before a screenshot
            is saved. This is to make sure the user gets the most up-to-date picture.
        '''
        if renderFirst:
            self.Render()
        return self.renderer.getScreenshot( file_format )
    
    def saveScreenshot(self, filename, renderFirst = True):
        ''' Saves the rendered picture as a file to disk '''
        import os.path        
        extension = os.path.splitext(filename)[1][1:]
        data = self.getScreenshot( extension, renderFirst )
        f = file( filename, 'wb' )
        f.write( data )
        f.close()
        
        
    def _getScreenSize(self):
        return self.renderer.framebuffer.size
    
    def _setScreenSize(self, size):
        self.renderer.framebuffer.size = size
        
    screen_size = property( _getScreenSize, _setScreenSize )
    
    
    def serialize(self, format):
        serializer = self.serializerRegistry.create( format )
        return serializer.serialize( self, self.camera )
    
    def unserialize(self, data, format):
        unserializer = self.unserializerRegistry.create( format )
        return unserializer.unserialize( self, data )
           
    def serializeToFile(self, filename, format = None):
        if format is None:  # auto-detect by filename extension
            import os.path        
            format = os.path.splitext( filename )[1][1:].lower()
            
        data = self.serialize( format )
        
        f = file( filename, 'wb' )
        f.write( data )
        f.close()        
        return data
     
    def unserializeFromFile(self, filename, format = None):
        if format is None:  # auto-detect by filename extension
            import os.path        
            format = os.path.splitext( filename )[1][1:].lower()

        f = file( filename, 'rb' )
        data = f.read()
        f.close()        
        return self.unserialize( data, format )

    def addController(self, controller):
        if hasattr( controller, 'onSize' ):
            self.subscribe( controller.onSize, 'onSize' )
        self.controllers.append( controller )
    
    def destroy(self):
        self.updatePolicy.stop()

    # todo: need to revisit this, maybe allow NodesWithBounds to return None 
    def _getBoundingBox(self):
        return boundingBox.BoundingBox( [ (0,0), (0,0) ] )
    
    boundingBox = property( _getBoundingBox )
    
    def _getLocalBoundingBox(self):
        return boundingBox.BoundingBox( [ (0,0), (0,0) ] )
    
    localBoundingBox = property( _getLocalBoundingBox )
Beispiel #4
0
class SimpleCanvas(Canvas):
    ''' I provide an various methods for a functional canvas.
        This includes registries so I can create according nodes/views/
        primitive renderers when given a model.
        I also provide a bunch of default models (and know how to map them
        to their views).
        I provide a default camera.
        Rendering and updates can be customized with policies.
        Other things I do include: zooming, screen shots, hit-testing, adapter
        registry. 
    '''
    def __init__(self, renderer, max_update_delay=0.2, *args, **keys):
        ''' Inits this canvas.
            Things setupped: background color, default camera, node factory,
            model/view/primitve renderer registries, adapter registry and
            render/update policies.
        '''

        if 'name' not in keys:
            keys['name'] = 'unnamed canvas'

        self.renderer = renderer

        if 'backgroundColor' in keys:
            self.backgroundColor = keys['backgroundColor']
            del keys['backgroundColor']
        else:
            self.backgroundColor = None

        if 'renderPolicy' in keys:
            self.renderPolicy = keys['renderPolicy']
            del keys['renderPolicy']
        else:
            #self.renderPolicy = DefaultRenderPolicy()
            self.renderPolicy = CullingRenderPolicy()

        Canvas.__init__(self, self.renderer, False, None, None, None, None,
                        observables.ObservableLinearTransform2D(), *args,
                        **keys)

        self.camera = observables.ObservableCamera(
            observables.ObservableLinearTransform2D(), name='default camera')
        self.addChild(self.camera)

        self.model_kinds = [
            'Rectangle', 'RoundedRectangle', 'Circle', 'Ellipse', 'Arc',
            'Text', 'Line', 'LineLength', 'Lines', 'LinesList', 'LineSegments',
            'LineSegmentsSeparate', 'Bitmap', 'CubicSpline', 'QuadraticSpline',
            'Polygon', 'PolygonList', 'Arrow', 'AngleArrow'
        ]
        self.primitive_kinds = [
            'Rectangle', 'RoundedRectangle', 'Ellipse', 'Arc', 'Text',
            'LinesList', 'LineSegmentsSeparate', 'Bitmap', 'CubicSpline',
            'QuadraticSpline', 'PolygonList', 'Arrow'
        ]

        self._setupRegistries()
        self._setupNodeFactory()
        self._setupAdapters()

        if 'updatePolicy' in keys:
            self.updatePolicy = keys['updatePolicy']
            del keys['updatePolicy']
        else:
            self.updatePolicy = DefaultUpdatePolicy(self, max_update_delay)

        self.controllers = []

        self.subscribe(self.onDirty, 'attribChanged')

    def _setupRegistries(self):
        ''' Setups the registries. First one is the adapter registry which
            has all the information how models can be adapted to different
            interfaces.
            The other registries know how to map a model interface to a node()
            view/primitive renderer.
            They're fed with the default objects (all kinds of models, primitive
            renderers, ...)
            Finally, there's the serialization registry where you can register
            serializers for nodes.
        '''
        self.adapterRegistry = adapterRegistry = AdapterRegistry()

        self.primitiveRendererRegistry = PrimitiveRendererRegistry(
            adapterRegistry)
        self.viewRegistry = ViewRegistry(adapterRegistry)
        self.renderNodeRegistry = RenderNodeRegistry(adapterRegistry)

        for primitive_kind in self.primitive_kinds:
            primitiveRendererType = getattr(
                views, 'Default%sRenderer' % primitive_kind)
            modelInterface = primitiveRendererType.can_render
            self.primitiveRendererRegistry.register(modelInterface,
                                                    primitiveRendererType)

        for model_kind in self.model_kinds:
            modelInterface = getattr(models, 'I%s' % model_kind)

            def createDefaultView(model, look, scaled):
                primitiveRendererConstructor, model = self.primitiveRendererRegistry.getRendererConstructor(
                    model)
                primitiveRenderer = primitiveRendererConstructor()
                primitiveRenderer = observables.ObservableBaseRenderer(
                    self.renderer, look, model, primitiveRenderer, scaled)
                return observables.ObservableDefaultView(
                    look, primitiveRenderer)

            self.viewRegistry.register(modelInterface, createDefaultView)

        for model_kind in self.model_kinds:
            modelInterface = getattr(models, 'I%s' % model_kind)

            def createDefaultRenderableNode(model, renderer, transform, look,
                                            scaled, name, render_to_surface,
                                            surface_size, filter, show):
                viewConstructor, model = self.viewRegistry.getViewConstructor(
                    model)
                view = viewConstructor(model=model, look=look, scaled=scaled)
                renderNode = observables.ObservableDefaultRenderableNode(
                    renderer, render_to_surface, surface_size, filter, model,
                    view, transform, show, name)
                return renderNode

            self.renderNodeRegistry.register(modelInterface,
                                             createDefaultRenderableNode)

        # create serialization registries
        self.serializerRegistry = FactoryUsingDict()
        self.unserializerRegistry = FactoryUsingDict()

        def createNativeSerializer():
            # import those two here, because they might need to import the simpleCanvas module itself
            # so importing here avoids a circular dependency
            from ..serialization.serializer import Serializer
            from ..serialization import defaultSerializers
            serializer = Serializer()
            defaultSerializers.registerDefaultSerializers(
                serializer.nodeSerializerRegistry)
            return serializer

        # register our native serializer and unserializer here
        self.serializerRegistry.register('fcsf', createNativeSerializer)
        self.unserializerRegistry.register('fcsf', createNativeSerializer)

        def createSvgSerializer():
            # import those two here, because they might need to import the simpleCanvas module itself
            # so importing here avoids a circular dependency
            from ..serialization.svg import SVGExporter
            from ..serialization.svg import defaultSVGSerializers
            serializer = SVGExporter(self.adapterRegistry)
            defaultSVGSerializers.registerDefaultSVGSerializers(
                serializer.elementCreatorRegistry)
            defaultSVGSerializers.registerDefaultNodeSerializers(
                serializer.nodeSerializerRegistry)

            # register the viewNodel-from-model connections to the svg exporter
            for model_kind in self.model_kinds:
                modelInterface = getattr(models, 'I%s' % model_kind)

                def createViewModel(model, look):
                    viewModelConstructor, model = self.primitiveRendererRegistry.getRendererConstructor(
                        model)
                    viewModel = viewModelConstructor()
                    return viewModel, model

                serializer.viewModelCreatorRegistry.register(
                    modelInterface, createViewModel)

            return serializer

        # register svg exporter, no importer defined
        self.serializerRegistry.register('svg', createSvgSerializer)

    def _setupAdapters(self):
        ''' Internal. Feeds the adapter registries with the default model adapters '''
        for (from_interface, to_interface, adapter) in defaultAdapters:
            self.adapterRegistry.register(from_interface, to_interface,
                                          adapter)

    def create(self, *args, **keys):
        ''' Creates a node from a model. 
            Forwarded from nodeFactory.
        '''
        return self.nodeFactory.create(*args, **keys)

    def registerNode(self, model_kind, create, modelType):
        ''' registers a node constructor for a model type.
            Forwarded from nodeFactory.
        '''
        setattr(self, 'create%s' % model_kind, partial(create, modelType))
        return self.nodeFactory.register(model_kind, create, modelType)

    def unregisterNode(self, *args, **keys):
        ''' unregisters a node constructor for a model type.
            Forwarded from nodeFactory.
        '''
        return self.nodeFactory.unregister(*args, **keys)

    def isNodeRegistered(self, *args, **keys):
        ''' is a node constructor for a model type registered?
            Forwarded from nodeFactory.
        '''
        return self.nodeFactory.is_registered(*args, **keys)

    def createFromModel(self, model, **keys):
        keywords = {
            'transform': None,
            'pos': None,
            'position': None,
            'rotation': None,
            'scale': None,
            'look': None,
            'where': 'front',
            'scaled': True,
            'render_to_surface': False,
            'surface_size': (500, 500),
            'filter': None,
            'parent': self,
            'name': '<unnamed node>',
            'show': True,
        }

        def get_keyword(dikt, name):
            return dikt.pop(name, keywords[name])

        where = get_keyword(keys, 'where')
        transform = get_keyword(keys, 'transform')
        if transform is None:
            transform = observables.ObservableLinearTransform2D()
        elif isinstance(transform, basestring):
            transform = getattr(observables, transform)()
            transform = observables.ObservableLinearTransform2D() * transform
        else:
            transform = observables.ObservableLinearTransform2D() * transform

        pos = get_keyword(keys, 'pos') or get_keyword(keys, 'position')
        if pos is not None:
            # assume linear transform
            transform.position = pos

        rotation = get_keyword(keys, 'rotation')
        if rotation is not None:
            # assume linear transform
            transform.rotation = rotation

        scale = get_keyword(keys, 'scale')
        if scale is not None:
            # assume linear transform
            transform.scale = scale

        scaled = get_keyword(keys, 'scaled')
        parent = get_keyword(keys, 'parent')
        name = get_keyword(keys, 'name')
        show = get_keyword(keys, 'show')

        # renderable node properties
        render_to_surface = get_keyword(keys, 'render_to_surface')
        surface_size = get_keyword(keys, 'surface_size')

        filter = get_keyword(keys, 'filter')

        if not model is None:
            look = get_keyword(keys, 'look')
            if look is None:
                raise ValueError(
                    'You need to supply a look! Use look.NoLook or "nolook" if you want none.'
                )
            if look == 'nolook':
                look = NoLook
            if isinstance(look, (tuple, list)):
                look = observables.ObservableSolidColourLook(*look)

            renderNodeConstructor, model = self.renderNodeRegistry.getRenderNodeConstructor(
                model)
            renderNode = renderNodeConstructor(
                model,
                renderer=self.renderer,
                transform=transform,
                look=look,
                name=name,
                scaled=scaled,
                render_to_surface=render_to_surface,
                surface_size=surface_size,
                filter=filter,
                show=show,
                **keys)
        else:
            renderNode = observables.ObservableDefaultRenderableNode(
                self.renderer,
                render_to_surface,
                surface_size,
                filter,
                None,
                None,
                transform=transform,
                name=name)

        parent.addChild(renderNode, where=where)

        return renderNode

    def _setupNodeFactory(self):
        ''' Internal. Sets up the node factory. The keyword argument for the
            create method is done here, as well as adding the createRectangle,
            create* methods to self.
        '''
        self.nodeFactory = FactoryUsingDict()

        for model_kind in self.model_kinds:
            modelType = getattr(observables, 'Observable%s' % model_kind)
            self.registerModel(model_kind, modelType, None, False)

        # the group node is a special one, it doesn't need any model
        self.registerNode('Group', self.createFromModel, None)

    def registerModel(self,
                      name,
                      modelClass,
                      observableAttributes,
                      makeObservable=True):
        def create(modelType, *args, **keys):
            model = modelType(*args)
            return self.createFromModel(model, **keys)

        if makeObservable:
            modelClass = observables.createObservableClass(
                modelClass, observableAttributes)

        self.registerNode(name, create, modelClass)
        return modelClass

    def registerModelAndView(self, name, modelClass, observableAttributes,
                             makeObservable, nodeConstructor):
        modelClass = self.registerModel(name, modelClass, observableAttributes,
                                        makeObservable)
        self.renderNodeRegistry.register(modelClass, nodeConstructor)

    def onDirty(self, evt):
        ''' If we're dirty, tell the update policy '''
        if self.dirty:
            self.updatePolicy.onDirty()

    def DoRender(self, renderer, camera):
        ''' The canvas itself doesn't have to render anything '''
        pass

    def Render(self, backgroundColor=None, camera=None, renderChildren=True):
        ''' Render all objects on the canvas with camera. By default the default
            camera is used.
            Calls the render policy.
        '''
        for controller in self.controllers:
            controller.update()
        backgroundColor = backgroundColor or self.backgroundColor
        if camera is None:
            camera = self.camera
        self.renderPolicy.render(self, camera, backgroundColor)
        camera.dirty = False
        camera.transform.dirty = False
        self.dirty = False
        self._children.dirty = False

    def zoomToExtents(self,
                      boundingBox=None,
                      padding_percent=0.05,
                      maintain_aspect_ratio=True):
        ''' Tells the default camera to fit the entire canvas nodes on the
            screen.
        '''
        if boundingBox is None:
            boundingBox = self.boundingBoxRecursive  #self.rtree.boundingBox

        self.camera.zoomToExtents(boundingBox, padding_percent,
                                  maintain_aspect_ratio)

    def zoom(self, factor, center=None, centerCoords='world', alignment='cc'):
        ''' Zooms and possibly recenters the default camera on the canvas. The
            center coordinates can be given in 'world' and 'pixel' coordinates.
            Todo: Make alignment work. This specifies how to zoom (for example
            should the left upper corner stay the same and the rest is zoomed).
            Todo: Make the world/pixel a property of the coordinate. Then we can
                  do something like center.world() which retrieves the
                  coordinate in world units, no matter if it was in pixel
                  or world coordinates before.
        '''
        self.camera.zoom *= factor

        if not center is None:
            if centerCoords == 'pixel':
                center = self.pointToWorld(center)
            self.camera.position = center

    def pointToWorld(self, screen_pnt):
        ''' Transform a point on screen to world coordinates (if possible) '''
        return self.camera.viewTransform.inverse((screen_pnt, ))[0]

    def hitTest(self, screen_pnt, exact=True, order=True):
        ''' Performs a hit test given a point on screen.
            If exact is False then an intersection test only against the bounding boxes
            of the nodes is performed. exact = True is only valid for point
            queries and does not only check the node bounding boxes against this
            query's point, but performs more elaborate checks to see if the
            query point is inside the bounds of the node.
            The return value is a list of the nodes which have been hit. If order
            is True, the nodes in the list are ordered front-to-back, meaning the
            first element is the top-most node, the last element is the "back-most" node.
        '''
        world_pnt = self.pointToWorld(screen_pnt)
        query = QueryWithPrimitive(primitive=boundingBox.fromPoint(world_pnt),
                                   exact=exact)
        pickedNodes = self.performSpatialQuery(query, order=order)

        return pickedNodes

    def getScreenshot(self, file_format, renderFirst=True):
        ''' Returns the rendered picture as a string with file_format, where
            file format can be something like 'png', 'jpg' or 'raw' or any other
            kind of supported image format.
            If renderFirst is true, the canvas will be rendered before a screenshot
            is saved. This is to make sure the user gets the most up-to-date picture.
        '''
        if renderFirst:
            self.Render()
        return self.renderer.getScreenshot(file_format)

    def saveScreenshot(self, filename, renderFirst=True):
        ''' Saves the rendered picture as a file to disk '''
        import os.path
        extension = os.path.splitext(filename)[1][1:]
        data = self.getScreenshot(extension, renderFirst)
        f = file(filename, 'wb')
        f.write(data)
        f.close()

    def _getScreenSize(self):
        return self.renderer.framebuffer.size

    def _setScreenSize(self, size):
        self.renderer.framebuffer.size = size

    screen_size = property(_getScreenSize, _setScreenSize)

    def serialize(self, format):
        serializer = self.serializerRegistry.create(format)
        return serializer.serialize(self, self.camera)

    def unserialize(self, data, format):
        unserializer = self.unserializerRegistry.create(format)
        return unserializer.unserialize(self, data)

    def serializeToFile(self, filename, format=None):
        if format is None:  # auto-detect by filename extension
            import os.path
            format = os.path.splitext(filename)[1][1:].lower()

        data = self.serialize(format)

        f = file(filename, 'wb')
        f.write(data)
        f.close()
        return data

    def unserializeFromFile(self, filename, format=None):
        if format is None:  # auto-detect by filename extension
            import os.path
            format = os.path.splitext(filename)[1][1:].lower()

        f = file(filename, 'rb')
        data = f.read()
        f.close()
        return self.unserialize(data, format)

    def addController(self, controller):
        if hasattr(controller, 'onSize'):
            self.subscribe(controller.onSize, 'onSize')
        self.controllers.append(controller)

    def destroy(self):
        self.updatePolicy.stop()

    # todo: need to revisit this, maybe allow NodesWithBounds to return None
    def _getBoundingBox(self):
        return boundingBox.BoundingBox([(0, 0), (0, 0)])

    boundingBox = property(_getBoundingBox)

    def _getLocalBoundingBox(self):
        return boundingBox.BoundingBox([(0, 0), (0, 0)])

    localBoundingBox = property(_getLocalBoundingBox)