Ejemplo n.º 1
0
class MessagesResponse(Response):
    """ Expected response from api/messages """
    Limit = Int()
    Total = Int()
    Messages = List(Message)
Ejemplo n.º 2
0
class QtTextCompleter(RawWidget):
    """Simple text editor supporting completion.

    """
    #: Text being edited by this widget.
    text = d_(Unicode())

    #: Static list of entries used to propose completion. This member value is
    #: not updated by the entries_updater.
    entries = d_(List())

    #: Callable to use to refresh the completions.
    entries_updater = d_(Callable())

    #: Delimiters marking the begining and end of completed section.
    delimiters = d_(Tuple(Unicode(), ('{', '}')))

    hug_width = 'ignore'
    features = Feature.FocusEvents

    #: Flag avoiding circular updates.
    _no_update = Bool(False)

    #: Reference to the QCompleter used by the widget.
    _completer = Value()

    def create_widget(self, parent):
        """Finishes initializing by creating the underlying toolkit widget.

        """
        widget = QCompletableTexEdit(parent)
        self._completer = QDelimitedCompleter(widget, self.delimiters,
                                              self.entries,
                                              self.entries_updater)
        widget.completer = self._completer
        widget.setText(self.text)
        widget.textChanged.connect(self.update_object)
        return widget

    def update_object(self):
        """ Handles the user entering input data in the edit control.

        """
        if (not self._no_update) and self.activated:
            value = self.get_widget().toPlainText()

            self._no_update = True
            self.text = value
            self._no_update = False

    def focus_lost(self):
        """Notify the completer the focus was lost.

        """
        self._completer.on_focus_lost()

    def _post_setattr_text(self, old, new):
        """Updates the editor when the object changes externally to the editor.

        """
        if (not self._no_update) and self.get_widget():
            self._no_update = True
            self.get_widget().setText(new)
            self._no_update = False

    def _post_setattr_entries(self, old, new):
        """Updates the completer entries.

        """
        if self.proxy_is_active and self._completer:
            self._completer._update_entries(new)
Ejemplo n.º 3
0
class Device(Model):
    """ The standard device. This is a standard model used throughout the
    application. An instance of this is configured by specifying a 
    'DeviceDriver' in a plugin manifest. 
    
    It simply delegates connection and handling to the selected transport
    and protocol respectively.
    
    """

    #: Internal model for drawing the preview on screen
    area = Instance(AreaBase)

    #: The declaration that defined this device
    declaration = Typed(extensions.DeviceDriver).tag(config=True)

    #: Protocols supported by this device (ex the HPGLProtocol)
    protocols = List(extensions.DeviceProtocol)

    #: Transports supported by this device (ex the SerialPort
    transports = List(extensions.DeviceTransport)

    #: The active transport
    connection = Instance(DeviceTransport).tag(config=True)

    #: List of jobs that were run on this device
    jobs = List(Model).tag(config=True)

    #: List of jobs queued to run on this device
    queue = List(Model).tag(config=True)

    #: Current job being processed
    job = Instance(Model)  #.tag(config=True)

    #: The device specific config
    config = Instance(DeviceConfig, ()).tag(config=True)

    #: Position. Defaults to x,y,z. The protocol can
    #: handle this however necessary.
    position = ContainerList(default=[0, 0, 0])

    #: Origin position. Defaults to [0, 0, 0]. The system will translate
    #: jobs to the origin so multiple can be run.
    origin = ContainerList(default=[0, 0, 0])

    #: Device is currently busy processing a job
    busy = Bool()

    #: Status
    status = Unicode()

    def _default_connection(self):
        if not self.transports:
            return None
        declaration = self.transports[0]
        transport = declaration.factory()
        transport.declaration = declaration
        transport.protocol = self._default_protocol()
        return transport

    def _default_protocol(self):
        if not self.protocols:
            return None
        declaration = self.protocols[0]
        protocol = declaration.factory()
        protocol.declaration = declaration
        return protocol

    def _default_area(self):
        """ Create the area based on the size specified by the Device Driver
        
        """
        d = self.declaration
        area = AreaBase()
        w = parse_unit(d.width)
        if d.length:
            h = parse_unit(d.length)
        else:
            h = 900000
        area.size = [w, h]
        return area

    @observe('declaration.width', 'declaration.length')
    def _refresh_area(self, change):
        self.area = self._default_area()

    @contextmanager
    def device_busy(self):
        """ Mark the device as busy """
        self.busy = True
        try:
            yield
        finally:
            self.busy = False

    @contextmanager
    def device_connection(self, test=False):
        """ """
        connection = self.connection
        try:
            #: Create a test connection if necessary
            if test:
                self.connection = TestTransport(
                    protocol=connection.protocol,
                    declaration=connection.declaration)
            #: Connect
            yield self.connection
        finally:
            #: Restore if necessary
            if test:
                self.connection = connection

    @defer.inlineCallbacks
    def test(self):
        """ Execute a test job on the device. This creates
        and submits new job that is simply a small square. 
        
        """
        raise NotImplementedError

    def transform(self, path):
        """ Apply the device output transform to the given path. This
        is used by other plugins that may need to display or work with
        tranformed output.
        
        Parameters
        ----------
            path: QPainterPath
                Path to transform
        
        Returns
        -------
            path: QPainterPath 
                
        """
        config = self.config

        t = QtGui.QTransform()

        #: Order matters!
        if config.scale:
            #: Do final output scaling
            t.scale(*config.scale)

        if config.rotation:
            #: Do final output rotation
            t.rotate(config.rotation)

        #: TODO: Translate back to 0,0 so all coordinates are positive
        path = path * t

        return path

    def init(self, job):
        """ Initialize the job. This should do any final path manipulation
        required by the device (or as specified by the config) and any filters
        should be applied here (overcut, blade offset compensation, etc..).
        
        The connection is not active at this stage.
        
        Parameters
        -----------
            job: inkcut.job.models.Job instance
                The job to handle. 
        
        Returns
        --------
            model: QtGui.QPainterPath instance or Deferred that resolves
                to a QPainterPath if heavy processing is needed. This path
                is then interpolated and sent to the device. 
        
        """
        log.debug("device | init {}".format(job))
        config = self.config

        #: Set the speed of this device for tracking purposes
        units = config.speed_units.split("/")[0]
        job.info.speed = from_unit(config.speed, units)

        #: Get the internal QPainterPath "model"
        model = job.model

        #: Transform the path to the device coordinates
        model = self.transform(model)

        if job.feed_to_end:
            #: Move the job to the new origin
            x, y, z = self.origin
            model.translate(x, -y)

        #: TODO: Apply filters here

        #: Return the transformed model
        return model

    @defer.inlineCallbacks
    def connect(self):
        """ Connect to the device. By default this delegates handling
        to the active transport or connection handler. 
         
        Returns
        -------
            result: Deferred or None
                May return a Deferred object that the process will wait for
                completion before continuing.
        
        """
        log.debug("device | connect")
        yield defer.maybeDeferred(self.connection.connect)
        cmd = self.config.commands_connect
        if cmd:
            yield defer.maybeDeferred(self.connection.write, cmd)

    def move(self, position, absolute=True):
        """ Move to the given position. By default this delegates handling
        to the active protocol.
        
        Parameters
        ----------
            position: List of coordinates to move to. 
                Desired position to move or move to (if using absolute 
                coordinates).
            absolute: bool
                Position is in absolute coordinates
        Returns
        -------
            result: Deferred or None
                May return a deferred object that the process will wait for
                completion before continuing.
        
        """
        if absolute:
            #: Clip everything to never go below zero in absolute mode
            position = [max(0, p) for p in position]
            self.position = position
        else:
            #: Convert to relative to absolute for the UI
            p = self.position
            p[0] += position[0]
            p[1] += position[1]
            self.position = p

        result = self.connection.protocol.move(*position, absolute=absolute)
        if result:
            return result

    def finish(self):
        """ Finish the job applying any cleanup necessary.  
        
        """
        log.debug("device | finish")
        return self.connection.protocol.finish()

    @defer.inlineCallbacks
    def disconnect(self):
        """ Disconnect from the device. By default this delegates handling
        to the active transport or connection handler. 
        
        """
        log.debug("device | disconnect")
        cmd = self.config.commands_disconnect
        if cmd:
            yield defer.maybeDeferred(self.connection.write, cmd)
        yield defer.maybeDeferred(self.connection.disconnect)

    @defer.inlineCallbacks
    def submit(self, job, test=False):
        """ Submit the job to the device. If the device is currently running
        a job it will be queued and run when this is finished.
        
        This handles iteration over the path model defined by the job and 
        sending commands to the actual device using roughly the procedure is 
        as follows:
                
                device.connect()
                
                model = device.init(job)
                for cmd in device.process(model):
                    device.handle(cmd)
                device.finish()
                
                device.disconnect()
        
        Subclasses provided by your own DeviceDriver may reimplement this
        to handle path interpolation however needed. The return value is
        ignored.
        
        The live plot view will update whenever the device.position object
        is updated. On devices with lower cpu/gpu capabilities this should
        be updated sparingly (ie the raspberry pi).
        
        Parameters
        -----------
            job: Instance of `inkcut.job.models.Job`
                The job to execute on the device
            test: bool
                Do a test run. This specifies whether the commands should be 
                sent to the actual device or not. If True, the connection will 
                be replaced with a virtual connection that captures all the 
                command output.
                
        """
        log.debug("device | submit {}".format(job))
        try:

            #: Only allow one job at a time
            if self.busy:
                queue = self.queue[:]
                queue.append(job)
                self.queue = queue  #: Copy and reassign so the UI updates
                log.info("Job {} put in device queue".format(job))
                return

            with self.device_busy():
                #: Set the current the job
                self.job = job
                self.status = "Initializing job"

                #: Get the time to sleep based for each unit of movement
                config = self.config

                #: Rate px/ms
                if config.custom_rate >= 0:
                    rate = config.custom_rate
                elif self.connection.always_spools or config.spooled:
                    rate = 0
                elif config.interpolate:
                    if config.step_time > 0:
                        rate = config.step_size / float(config.step_time)
                    else:
                        rate = 0  # Undefined
                else:
                    rate = from_unit(
                        config.speed,  # in/s or cm/s
                        config.speed_units.split("/")[0]) / 1000.0

                # Device model is updated in real time
                model = yield defer.maybeDeferred(self.init, job)

                #: Local references are faster
                info = job.info

                #: Determine the length for tracking progress
                whole_path = QtGui.QPainterPath()

                #: Some versions of Qt seem to require a value in
                #: toSubpathPolygons
                m = QtGui.QTransform.fromScale(1, 1)
                for path in model.toSubpathPolygons(m):
                    for i, p in enumerate(path):
                        whole_path.lineTo(p)
                total_length = whole_path.length()
                total_moved = 0
                log.debug("device | Path length: {}".format(total_length))

                #: So a estimate of the duration can be determined
                info.length = total_length
                info.speed = rate * 1000  #: Convert to px/s

                #: Waiting for approval
                info.status = 'waiting'

                #: If marked for auto approve start now
                if info.auto_approve:
                    info.status = 'approved'
                else:
                    #: Check for approval before starting
                    yield defer.maybeDeferred(info.request_approval)
                    if info.status != 'approved':
                        self.status = "Job cancelled"
                        return

                #: Update stats
                info.status = 'running'
                info.started = datetime.now()

                self.status = "Connecting to device"
                with self.device_connection(test
                                            or config.test_mode) as connection:
                    self.status = "Processing job"
                    try:
                        yield defer.maybeDeferred(self.connect)

                        #: Write startup command
                        if config.commands_before:
                            yield defer.maybeDeferred(connection.write,
                                                      config.commands_before)

                        self.status = "Working..."

                        #: For point in the path
                        for (d, cmd, args, kwargs) in self.process(model):

                            #: Check if we paused
                            if info.paused:
                                self.status = "Job paused"
                                #: Sleep until resumed, cancelled, or the
                                #: connection drops
                                while (info.paused and not info.cancelled
                                       and connection.connected):
                                    yield async_sleep(300)  # ms

                            #: Check for cancel for non interpolated jobs
                            if info.cancelled:
                                self.status = "Job cancelled"
                                info.status = 'cancelled'
                                break
                            elif not connection.connected:
                                self.status = "connection error"
                                info.status = 'error'
                                break

                            #: Invoke the command
                            #: If you want to let the device handle more complex
                            #: commands such as curves do it in process and handle
                            yield defer.maybeDeferred(cmd, *args, **kwargs)
                            total_moved += d

                            #: d should be the device must move in px
                            #: so wait a proportional amount of time for the device
                            #: to catch up. This avoids buffer errors from dumping
                            #: everything at once.

                            #: Since sending is way faster than cutting
                            #: we must delay (without blocking the UI) before
                            #: sending the next command or the device's buffer
                            #: quickly gets filled and crappy china piece cutters
                            #: get all jacked up. If the transport sends to a spooled
                            #: output (such as a printer) this can be set to 0
                            log.debug("Rate is :{}".format(rate))
                            if rate > 0:
                                # log.debug("d={}, delay={} t={}".format(
                                #     d, delay, d/delay
                                # ))
                                yield async_sleep(d / rate)

                            #: TODO: Check if we need to update the ui
                            #: Set the job progress based on how far we've gone
                            if total_length > 0:
                                info.progress = int(
                                    max(
                                        0,
                                        min(100,
                                            100 * total_moved / total_length)))

                        if info.status != 'error':
                            #: We're done, send any finalization commands
                            yield defer.maybeDeferred(self.finish)

                        #: Write finalize command
                        if config.commands_after:
                            yield defer.maybeDeferred(connection.write,
                                                      config.commands_after)

                        #: Update stats
                        info.ended = datetime.now()

                        #: If not cancelled or errored
                        if info.status == 'running':
                            info.done = True
                            info.status = 'complete'
                    except Exception as e:
                        log.error(e)
                        raise
                    finally:
                        if connection.connected:
                            yield defer.maybeDeferred(self.disconnect)

            #: Set the origin
            if job.feed_to_end and job.info.status == 'complete':
                self.origin = self.position

            #: If the user didn't cancel, set the origin and
            #: Process any jobs that entered the queue while this was running
            if self.queue and not job.info.cancelled:
                queue = self.queue[:]
                job = queue.pop(0)  #: Pull the first job off the queue
                log.info("Rescheduling {} from queue".format(job))
                self.queue = queue  #: Copy and reassign so the UI updates

                #: Call a minute later
                timed_call(60000, self.submit, job)
        except Exception as e:
            log.error(e)
            raise

    def process(self, model):
        """  Process the path model of a job and return each command
        within the job.
        
        Parameters
        ----------
            model: QPainterPath
                The path to process
        
        Returns
        -------
            generator: A list or generator object that yields each command
             to invoke on the device and the distance moved. In the format
             (distance, cmd, args, kwargs)
        
        """
        config = self.config

        #: Previous point
        _p = QtCore.QPointF(self.origin[0], self.origin[1])

        #: Do a final translation since Qt's y axis is reversed
        t = QtGui.QTransform.fromScale(1, -1)
        model = model * t

        #: Determine if interpolation should be used
        skip_interpolation = self.connection.always_spools or config.spooled or not config.interpolate

        # speed = distance/seconds
        # So distance/speed = seconds to wait
        step_size = config.step_size
        if not skip_interpolation and step_size <= 0:
            raise ValueError("Cannot have a step size <= 0!")
        try:
            #: Some versions of Qt seem to require a value in toSubpathPolygons
            m = QtGui.QTransform.fromScale(1, 1)
            for path in model.toSubpathPolygons(m):

                #: And then each point within the path
                #: this is a polygon
                for i, p in enumerate(path):

                    #: Head state
                    # 0 move, 1 cut
                    z = 0 if i == 0 else 1

                    #: Make a subpath
                    subpath = QtGui.QPainterPath()
                    subpath.moveTo(_p)
                    subpath.lineTo(p)

                    #: Update the last point
                    _p = p

                    #: Total length
                    l = subpath.length()

                    #: If the device does not support streaming
                    #: the path interpolation is skipped entirely
                    if skip_interpolation:
                        x, y = p.x(), p.y()
                        yield (l, self.move, ([x, y, z], ), {})
                        continue

                    #: Where we are within the subpath
                    d = 0

                    #: Interpolate path in steps of dl and ensure we get
                    #: _p and p (t=0 and t=1)
                    #: This allows us to cancel mid point
                    while d <= l:
                        #: Now set d to the next point by step_size
                        #: if the end of the path is less than the step size
                        #: use the minimum of the two
                        dl = min(l - d, step_size)

                        #: Now find the point at the given step size
                        #: the first point d=0 so t=0, the last point d=l so t=1
                        t = subpath.percentAtLength(d)
                        sp = subpath.pointAtPercent(t)
                        #if d == l:
                        #    break  #: Um don't we want to send the last point??

                        #: -y because Qt's axis is from top to bottom not bottom
                        #: to top
                        x, y = sp.x(), sp.y()
                        yield (dl, self.move, ([x, y, z], ), {})

                        #: When we reached the end but instead of breaking above
                        #: with a d < l we do it here to ensure we get the last
                        #: point
                        if d == l:
                            #: We reached the end
                            break

                        #: Add step size
                        d += dl

            #: Make sure we get the endpoint
            ep = model.currentPosition()
            yield (0, self.move, ([ep.x(), ep.y(), 0], ), {})
        except Exception as e:
            log.error("device | processing error: {}".format(e))
            raise e

    def _observe_status(self, change):
        """ Whenever the status changes, log it """
        log.info("device | {}".format(self.status))

    def _observe_job(self, change):
        """ Save the previous jobs """
        if change['type'] == 'update':
            job = change['value']
            if job not in self.jobs:
                jobs = self.jobs[:]
                jobs.append(job)
                self.jobs = jobs
Ejemplo n.º 4
0
class InstrumentManagerPlugin(HasPreferencesPlugin):
    """The instrument plugin manages the instrument drivers and their use.

    """
    #: List of the known instrument profile ids.
    profiles = List()

    #: List of instruments for which at least one driver is declared.
    instruments = List()

    #: List of registered intrument users.
    #: Only registered users can be granted the use of an instrument.
    users = List()

    #: List of registered instrument starters.
    starters = List()

    #: List of registered connection types.
    connections = List()

    #: List of registered settings.
    settings = List()

    #: Currently used profiles.
    #: This dict should be edited by user code.
    used_profiles = Dict()

    def start(self):
        """Start the plugin lifecycle by collecting all contributions.

        """
        super(InstrumentManagerPlugin, self).start()

        core = self.workbench.get_plugin('enaml.workbench.core')
        core.invoke_command('ecpy.app.errors.enter_error_gathering')

        state = core.invoke_command('ecpy.app.states.get',
                                    {'state_id': 'ecpy.app.directory'})

        i_dir = os.path.join(state.app_directory, 'instruments')
        # Create instruments subfolder if it does not exist.
        if not os.path.isdir(i_dir):
            os.mkdir(i_dir)

        p_dir = os.path.join(i_dir, 'profiles')
        # Create profiles subfolder if it does not exist.
        if not os.path.isdir(p_dir):
            os.mkdir(p_dir)

        self._profiles_folders = [p_dir]

        self._users = ExtensionsCollector(workbench=self.workbench,
                                          point=USERS_POINT,
                                          ext_class=InstrUser,
                                          validate_ext=validate_user)
        self._users.start()

        self._starters = ExtensionsCollector(workbench=self.workbench,
                                             point=STARTERS_POINT,
                                             ext_class=Starter,
                                             validate_ext=validate_starter)
        self._starters.start()

        checker = make_extension_validator(Connection, ('new', ),
                                           ('id', 'description'))
        self._connections = ExtensionsCollector(workbench=self.workbench,
                                                point=CONNECTIONS_POINT,
                                                ext_class=Connection,
                                                validate_ext=checker)
        self._connections.start()

        checker = make_extension_validator(Settings, ('new', ),
                                           ('id', 'description'))
        self._settings = ExtensionsCollector(workbench=self.workbench,
                                             point=SETTINGS_POINT,
                                             ext_class=Settings,
                                             validate_ext=checker)
        self._settings.start()

        checker = make_extension_validator(ManufacturerAlias, (), (
            'id',
            'aliases',
        ))
        self._aliases = ExtensionsCollector(workbench=self.workbench,
                                            point=ALIASES_POINT,
                                            ext_class=ManufacturerAlias,
                                            validate_ext=checker)
        self._aliases.start()

        self._drivers = DeclaratorsCollector(workbench=self.workbench,
                                             point=DRIVERS_POINT,
                                             ext_class=[Driver, Drivers])
        self._drivers.start()

        for contrib in ('users', 'starters', 'connections', 'settings'):
            self._update_contribs(contrib, None)

        err = False
        details = {}
        for d_id, d_infos in self._drivers.contributions.items():
            res, tb = d_infos.validate(self)
            if not res:
                err = True
                details[d_id] = tb

        if err:
            core.invoke_command('ecpy.app.errors.signal', {
                'kind': 'ecpy.driver-validation',
                'details': details
            })
        # TODO providing in app a way to have a splash screen while starting to
        # let the user know what is going on would be nice

        # TODO handle dynamic addition of drivers by observing contributions
        # and updating the manufacturers infos accordingly.
        # should also observe manufacturer aliases

        self._refresh_profiles()

        self._bind_observers()

        core.invoke_command('ecpy.app.errors.exit_error_gathering')

    def stop(self):
        """Stop the plugin and remove all observers.

        """
        self._unbind_observers()

        for contrib in ('drivers', 'users', 'starters', 'connections',
                        'settings'):
            getattr(self, '_' + contrib).stop()

    def create_connection(self, connection_id, infos, read_only=False):
        """Create a connection and initialize it.

        Parameters
        ----------
        connection_id : unicode
            Id of the the connection to instantiate.

        infos : dict
            Dictionarry to use to initialize the state of the connection.

        read_only : bool
            Should the connection be created as read-only.

        Returns
        -------
        connection : BaseConnection
            Ready to use widget.

        """
        c_decl = self._connections.contributions[connection_id]
        conn = c_decl.new(self.workbench, infos, read_only)
        if conn.declaration is None:
            conn.declaration = c_decl
        return conn

    def create_settings(self, settings_id, infos, read_only=False):
        """Create a settings and initialize it.

        Parameters
        ----------
        settings_id : unicode
            Id of the the settings to instantiate.

        infos : dict
            Dictionary to use to initialize the state of the settings.

        read_only : bool
            Should the settings be created as read-only.

        Returns
        -------
        connection : BaseSettings
            Ready to use widget.

        """
        if settings_id is None:
            msg = 'No id was found for the settings whose infos are %s'
            logger.warn(msg, infos)
            return None
        s_decl = self._settings.contributions[settings_id]
        sett = s_decl.new(self.workbench, infos, read_only)
        if sett.declaration is None:
            sett.declaration = s_decl
        return sett

    def get_drivers(self, drivers):
        """Query drivers class and the associated starters.

        Parameters
        ----------
        drivers : list
            List of driver ids for which the matching class should be returned.

        Returns
        -------
        drivers : dict
            Requested drivers and associated starter indexed by id.

        missing : list
            List of ids which do not correspond to any known valid driver.

        """
        ds = self._drivers.contributions
        knowns = {d_id: ds[d_id] for d_id in drivers if d_id in ds}
        missing = list(set(drivers) - set(knowns))

        return {
            d_id:
            (infos.cls, self._starters.contributions[infos.starter].starter)
            for d_id, infos in knowns.items()
        }, missing

    def get_profiles(self, user_id, profiles, try_release=True, partial=False):
        """Query profiles for use by a declared user.

        Parameters
        ----------
        user_id : unicode
            Id of the user which request the authorization to use the
            instrument.

        profiles : list
            Ids of the instrument profiles which are requested.

        try_release : bool, optional
            Should we attempt to release currently used profiles.

        partial : bool, optional
            Should only a subset of the requested profiles be returned if some
            profiles are not available.

        Returns
        -------
        profiles : dict
            Requested profiles as a dictionary.

        unavailable : list
            List of profiles that are not currently available and cannot be
            released.

        """
        if user_id not in self.users:
            raise ValueError('Unknown instrument user tried to query profiles')

        used = [p for p in profiles if p in self.used_profiles]
        unavailable = []
        if used:
            released = []
            if not try_release:
                unavailable = used
            else:
                used_by_owner = defaultdict(set)
                for p in used:
                    used_by_owner[self.used_profiles[p]].add(p)
                for o in list(used_by_owner):
                    user = self._users.contributions[o]
                    if user.policy == 'releasable':
                        to_release = used_by_owner[o]
                        r = user.release_profiles(self.workbench, to_release)
                        unavailable.extend(set(to_release) - set(r))
                        released.extend(r)
                    else:
                        unavailable.extend(used_by_owner[o])

        if unavailable and not partial:
            if released:
                used = {
                    k: v
                    for k, v in self.used_profiles.items() if k not in released
                }
                self.used_profiles = used
            return {}, unavailable

        available = ([p for p in profiles
                      if p not in unavailable] if unavailable else profiles)

        with self.suppress_notifications():
            u = self.used_profiles
            self.used_profiles = {}
        u.update({p: user_id for p in available})
        self.used_profiles = u

        queried = {}
        for p in available:
            queried[p] = self._profiles[p]._config.dict()

        return queried, unavailable

    def release_profiles(self, user_id, profiles):
        """Release some previously acquired profiles.

        The user should not maintain any communication with the instruments
        whose profiles have been released after calling this method.

        Parameters
        ----------
        user_id : unicode
            Id of the user releasing the profiles.

        profiles : iterable
            Profiles (ids) which are no longer needed by the user.

        """
        self.used_profiles = {
            k: v
            for k, v in self.used_profiles.items()
            if k not in profiles or v != user_id
        }

    def get_aliases(self, manufacturer):
        """List the known aliases of a manufacturer.

        Parameters
        ----------
        manufacturer : str
            Name of the manufacturer for which to return the aliases.

        Returns
        -------
        aliases : list[unicode]
            Known aliases of the manufacturer.

        """
        aliases = self._aliases.contributions.get(manufacturer, [])
        if aliases:
            aliases = aliases.aliases
        return aliases

    # =========================================================================
    # --- Private API ---------------------------------------------------------
    # =========================================================================

    #: Collector of drivers.
    _drivers = Typed(DeclaratorsCollector)

    #: Collector for the manufacturer aliases.
    _aliases = Typed(ExtensionsCollector)

    #: Declared manufacturers storing the corresponding model infos.
    _manufacturers = Typed(ManufacturersHolder)

    #: Collector of users.
    _users = Typed(ExtensionsCollector)

    #: Collector of starters.
    _starters = Typed(ExtensionsCollector)

    #: Collector of connections.
    _connections = Typed(ExtensionsCollector)

    #: Collector of settings.
    _settings = Typed(ExtensionsCollector)

    #: List of folders in which to search for profiles.
    # TODO make that list editable and part of the preferences
    _profiles_folders = List()

    #: Mapping of profile name to profile infos.
    _profiles = Dict()

    #: Watchdog observer tracking changes to the profiles folders.
    _observer = Typed(Observer)

    def _update_contribs(self, name, change):
        """Update the list of available contributions (editors, engines, tools)
        when they change.

        """
        setattr(self, name, list(getattr(self, '_' + name).contributions))
        if name == 'starters':
            for id_, s in getattr(self, '_' + name).contributions.items():
                s.starter.id = id_

    def _refresh_profiles(self):
        """List of profiles living in the profiles folders.

        """
        profiles = {}
        logger = logging.getLogger(__name__)
        for path in self._profiles_folders:
            if os.path.isdir(path):
                filenames = sorted(f for f in os.listdir(path)
                                   if f.endswith('.instr.ini') and (
                                       os.path.isfile(os.path.join(path, f))))

                for filename in filenames:
                    profile_path = os.path.join(path, filename)
                    # Beware redundant names are overwritten
                    name = filename[:-len('.instr.ini')]
                    # TODO should be delayed and lead to a nicer report
                    i = ProfileInfos(path=profile_path, plugin=self)
                    res, msg = validate_profile_infos(i)
                    if res:
                        profiles[name] = i
                    else:
                        logger.warn(msg)
            else:
                logger.warn('{} is not a valid directory'.format(path))

        self._profiles = profiles

    def _bind_observers(self):
        """Start the observers.

        """
        for contrib in ('users', 'starters', 'connections', 'settings'):
            callback = partial(self._update_contribs, contrib)
            getattr(self, '_' + contrib).observe('contributions', callback)

        def update():
            """Run the handler on the main thread to avoid GUI issues.

            """
            deferred_call(self._refresh_profiles)

        self._observer = Observer()
        for folder in self._profiles_folders:
            handler = SystematicFileUpdater(update)
            self._observer.schedule(handler, folder, recursive=True)

        self._observer.start()

    def _unbind_observers(self):
        """Stop the observers.

        """
        for contrib in ('users', 'starters', 'connections', 'settings'):
            callback = partial(self._update_contribs, contrib)
            getattr(self, '_' + contrib).observe('contributions', callback)

        self._observer.unschedule_all()
        self._observer.stop()
        try:
            self._observer.join()
        except RuntimeError:
            pass

    def _post_setattr__profiles(self, old, new):
        """Automatically update the profiles member.

        """
        self.profiles = sorted(new)

    def _default__manufacturers(self):
        """Delayed till this is first needed.

        """
        holder = ManufacturersHolder(plugin=self)
        valid_drivers = [d for d in self._drivers.contributions.values()]
        holder.update_manufacturers(valid_drivers)

        return holder
Ejemplo n.º 5
0
class NodeItem(GraphicsItem):
    """ A node-item in a node graph

    """

    id = d_(Str())
    name = d_(Unicode())

    width = d_(Int(180))
    height = d_(Int())
    position = d_(Typed(Point2D))

    edge_size = d_(Float(10.0))
    title_height = d_(Float(24.0))
    padding = d_(Float(4.0))

    color_default = d_(ColorMember("#0000007F"))
    color_selected = d_(ColorMember("#FFA637FF"))

    font_title = d_(FontMember('10pt Ubuntu'))

    color_title = d_(ColorMember("#AAAAAAFF"))
    color_title_background = d_(ColorMember("#313131FF"))
    color_background = d_(ColorMember("#212121E3"))

    show_content_inline = d_(Bool(False))

    #: the model item from the underlying graph structure
    model = d_(Typed(Atom))

    context_menu_event = d_(Event())

    recompute_node_layout = d_(Event())

    #: optional Node Content
    content = Instance(NodeContent)
    input_sockets = List(NodeSocket)
    output_sockets = List(NodeSocket)

    input_sockets_visible = Property(lambda self: [s for s in self.input_sockets if s.visible], cached=True)
    output_sockets_visible = Property(lambda self: [s for s in self.output_sockets if s.visible], cached=True)

    input_sockets_dict = Property(lambda self: self._mk_input_dict(), cached=True)
    output_sockets_dict = Property(lambda self: self._mk_output_dict(), cached=True)

    #: Cyclic notification guard. This a bitfield of multiple guards.
    _guard = Int(0)

    #: A reference to the ProxyComboBox object.
    proxy = Typed(ProxyNodeItem)

    def _default_position(self):
        return Point2D(x=0, y=0)

    def _default_height(self):
        return self.compute_height()

    def _default_id(self):
        if self.scene is not None:
            cls = self.__class__
            return self.scene.generate_item_id(cls.__name__, cls)
        return "<undefined>"

    def _default_name(self):
        return self.id

    #--------------------------------------------------------------------------
    # Content Handlers
    #--------------------------------------------------------------------------

    def child_added(self, child):
        """ Reset the item cache when a child is added """
        super(NodeItem, self).child_added(child)
        if isinstance(child, NodeContent):
            self.content = child
        if isinstance(child, NodeSocket):
            if child.socket_type == SocketType.INPUT:
                self.input_sockets.append(child)
                self.get_member('input_sockets_dict').reset(self)
            elif child.socket_type == SocketType.OUTPUT:
                self.output_sockets.append(child)
                self.get_member('output_sockets_dict').reset(self)

    def child_removed(self, child):
        """ Reset the item cache when a child is removed """
        super(NodeItem, self).child_removed(child)
        if isinstance(child, NodeContent):
            self.content = None
        if isinstance(child, NodeSocket):
            if child.socket_type == SocketType.INPUT:
                self.input_sockets.remove(child)
                self.get_member('input_sockets_dict').reset(self)
            elif child.socket_type == SocketType.OUTPUT:
                self.output_sockets.remove(child)
                self.get_member('output_sockets_dict').reset(self)

    def _mk_input_dict(self):
        return {c.id: c for c in self.children if isinstance(c, NodeSocket) and c.socket_type == SocketType.INPUT}

    def _mk_output_dict(self):
        return {c.id: c for c in self.children if isinstance(c, NodeSocket) and c.socket_type == SocketType.OUTPUT}

    def activate_bottom_up(self):
        self.assign_socket_indices()

    #--------------------------------------------------------------------------
    # Observers
    #--------------------------------------------------------------------------

    @observe('id', 'name', 'width', 'height', 'edge_size', 'title_height',
             'padding', 'color_default', 'color_selected', 'color_title',
             'color_title_background', 'color_background', 'show_content_inline',
             'content')
    def _update_proxy(self, change):
        """ An observer which sends state change to the proxy.

        """
        # The superclass handler implementation is sufficient.
        super(NodeItem, self)._update_proxy(change)
        self.request_update()

    @observe('width', 'height', 'edge_size', 'title_height', 'padding')
    def _update_layout(self, change):
        for s in self.input_sockets + self.output_sockets:
            s.update_sockets()
        if self.content is not None:
            self.content.update_content_geometry()

    def _observe_recompute_node_layout(self, change):
        if self.initialized:
            if not self._guard & NODE_UPDATE_LAYOUT_GUARD:
                self.update_node_layout()

    #--------------------------------------------------------------------------
    # NodeItem API
    #--------------------------------------------------------------------------

    def update_node_layout(self):
        self._guard |= NODE_UPDATE_LAYOUT_GUARD
        self.get_member('input_sockets_visible').reset(self)
        self.get_member('output_sockets_visible').reset(self)
        self.assign_socket_indices()
        self.height = self.compute_height()
        self.update_sockets_and_edges()
        self._guard &= ~NODE_UPDATE_LAYOUT_GUARD

    def assign_socket_indices(self):
        for socket in self.input_sockets:
            if socket in self.input_sockets_visible:
                socket.index = self.input_sockets_visible.index(socket)
            else:
                socket.index = 0
        for socket in self.output_sockets:
            if socket in self.output_sockets_visible:
                socket.index = self.output_sockets_visible.index(socket)
            else:
                socket.index = 0

    def update_sockets_and_edges(self):
        for socket in self.input_sockets_visible + self.output_sockets_visible:
            socket.update_sockets()
            for edge in socket.edges:
                edge.update_positions()

    def compute_height(self):
        socket_space = max(sum(s.socket_spacing for s in self.input_sockets_visible),
                           sum(s.socket_spacing for s in self.output_sockets_visible))
        return math.ceil(self.title_height + 2 * self.padding + 2 * self.edge_size + socket_space)


    # XXX must avoid cyclic updates ..
    def set_position(self, pos):
        self.position = pos
        self.proxy.set_position(pos)

    def getContentView(self):
        if not self.show_content_inline and self.content is not None:
            return self.content.content_objects[:]
        return []
Ejemplo n.º 6
0
class TextMonitor(BaseMonitor):
    """ Simple monitor displaying entries as text in a widget.

    """
    # --- Public API ----------------------------------------------------------

    #: List of the entries which should be displayed when a measure is running.
    displayed_entries = ContainerList(Instance(MonitoredEntry))

    #: List of the entries which should not be displayed when a measure is
    #: running.
    undisplayed_entries = ContainerList(Instance(MonitoredEntry))

    #: List of the entries which should be not displayed when a measure is
    #: running because they would be redundant with another entry. (created by
    #: a rule for example.)
    hidden_entries = List(Instance(MonitoredEntry))

    #: Mapping between a database entry and a list of callable used for
    #: updating an entry of the monitor which relies on the database entry.
    updaters = Dict(Str(), List(Callable()))

    #: List of rules which should be used to build monitor entries.
    rules = ContainerList(Instance(AbstractMonitorRule))

    #: List of user created monitor entries.
    custom_entries = List(Instance(MonitoredEntry))

    def start(self, parent_ui):
        if self.auto_show:
            self.show_monitor(parent_ui)

    def stop(self):
        # Avoid raising errors if the view has already been destroyed.
        if getattr(self._view, 'proxy_is_active', None):
            self._view.close()
        self._view = None

    def process_news(self, news):

        values = self._database_values
        values[news[0]] = news[1]
        for updater in self.updaters[news[0]]:
            updater(values)

    def refresh_monitored_entries(self, entries={}):
        if not entries:
            entries = self._database_values
        else:
            self._database_values = entries

        custom = self.custom_entries[:]
        self.clear_state()
        self.custom_entries = custom
        for entry, value in entries.iteritems():
            self.database_modified({'value': (entry, value)})

    def database_modified(self, change):
        entry = change['value']

        # Handle the addition of a new entry to the database
        if len(entry) > 1:

            # Store the new value.
            self._database_values[entry[0]] = entry[1]

            # Add a default entry to the displayed monitor entries.
            self.displayed_entries.append(self._create_default_entry(*entry))

            # Try to apply rules.
            for rule in self.rules:
                rule.try_apply(entry[0], self)

            # Check whether any custom entry is currently hidden.
            hidden_custom = [
                e for e in self.custom_entries
                if e not in self.displayed_entries
                or e not in self.undisplayed_entries
            ]

            # If there is one checks whether all the dependences are once
            # more available.
            if hidden_custom:
                for e in hidden_custom:
                    if all(d in self.database_entries for d in e.depend_on):
                        self.displayed_entries.append(e)

        # Handle the case of a database entry being suppressed, by removing all
        # monitors entries which where depending on this entry.
        else:
            self.displayed_entries[:] = [
                m for m in self.displayed_entries
                if entry[0] not in m.depend_on
            ]
            self.undisplayed_entries[:] = [
                m for m in self.undisplayed_entries
                if entry[0] not in m.depend_on
            ]
            self.hidden_entries[:] = [
                m for m in self.hidden_entries if entry[0] not in m.depend_on
            ]

            if entry[0] in self.database_entries:
                self.database_entries.remove(entry[0])

            if entry[0] in self._database_values:
                del self._database_values[entry[0]]

    def clear_state(self):
        """ Clear the monitor state.

        """
        with self.suppress_notifications():
            self.displayed_entries = []
            self.undisplayed_entries = []
            self.hidden_entries = []
            self.updaters = {}
            self.custom_entries = []
            self.database_entries = []

    def get_state(self):
        prefs = self.preferences_from_members()
        # Save the definitions of the custom entries.
        for i, custom_entry in enumerate(self.custom_entries):
            aux = 'custom_{}'.format(i)
            prefs[aux] = custom_entry.preferences_from_members()

        # Save the definitions of the rules.
        for i, rule in enumerate(self.rules):
            aux = 'rule_{}'.format(i)
            prefs[aux] = rule.preferences_from_members()

        prefs['displayed'] = repr([e.path for e in self.displayed_entries])
        prefs['undisplayed'] = repr([e.path for e in self.undisplayed_entries])
        prefs['hidden'] = repr([e.path for e in self.hidden_entries])

        return prefs

    def set_state(self, config, entries):
        # Request all the rules class from the plugin.
        rules_config = [
            conf for name, conf in config.iteritems()
            if name.startswith('rule_')
        ]

        # Rebuild all rules.
        rules = []
        for rule_config in rules_config:
            rule = self._plugin.build_rule(rule_config)
            if rule is not None:
                rules.append(rule)

        self.rules = rules

        customs_config = [
            conf for name, conf in config.iteritems()
            if name.startswith('custom_')
        ]
        for custom_config in customs_config:
            entry = MonitoredEntry()
            entry.update_members_from_preferences(**custom_config)
            self.custom_entries.append(entry)

        self.refresh_monitored_entries(entries)

        m_entries = set(self.displayed_entries + self.undisplayed_entries +
                        self.hidden_entries + self.custom_entries)

        pref_disp = eval(config['displayed'])
        pref_undisp = eval(config['undisplayed'])
        pref_hidden = eval(config['hidden'])
        disp = [e for e in m_entries if e.path in pref_disp]
        m_entries -= set(disp)
        undisp = [e for e in m_entries if e.path in pref_undisp]
        m_entries -= set(undisp)
        hidden = [e for e in m_entries if e.path in pref_hidden]
        m_entries -= set(hidden)
        if m_entries:
            e_l = [e.name for e in m_entries]
            mess = cleandoc('''The following entries were not
                        expected from the config : {} . These entries has been
                        added to the displayed ones.''')
            information(parent=None,
                        title='Unhandled entries',
                        text=fill(mess.format(e_l)))
            pref_disp += list(m_entries)

        self.displayed_entries = disp
        self.undisplayed_entries = undisp
        self.hidden_entries = hidden
        self.measure_name = config['measure_name']
        self.auto_show = eval(config['auto_show'])

    def get_editor_page(self):
        return TextMonitorPage(monitor=self)

    def show_monitor(self, parent_ui):
        if self._view and self._view.proxy_is_active:
            self._view.restore()
            self._view.send_to_front()
        else:
            view = TextMonitorView(monitor=self)
            view.show()
            self._view = view

    @property
    def all_database_entries(self):
        """ Getter returning all known database entries.

        """
        return self._database_values.keys()

    def add_rule_to_plugin(self, rule_name):
        """ Add a rule definition to the plugin.

        Parameters
        ----------
        rule_name : str
            Name of the rule whose description should be added to the plugin.

        """
        plugin = self._plugin
        if rule_name in self._plugin.rules:
            return

        config = {}
        for rule in self.rules:
            if rule.name == rule_name:
                config = rule.preferences_from_members()
                break

        if config:
            plugin.rules[rule_name] = config

    # --- Private API ---------------------------------------------------------

    # Known values of the database entries used when recomputing an entry value
    # depending not on a single value. During edition all values are stored,
    # regardless of whether or not the entry needs to be observed, when the
    # start method is called the dict is cleaned.
    _database_values = Dict(Str(), Value())

    # Reference to the monitor plugin handling the rules persistence.
    _plugin = ForwardTyped(import_monitor_plugin)

    # Reference to the current display
    _view = Typed(TextMonitorView)

    @staticmethod
    def _create_default_entry(entry_path, value):
        """ Create a monitor entry for a database entry.

        Parameters
        ----------
        entry_path : str
            Path of the database entries for which to create a monitor entry.

        Returns
        -------
        entry : MonitoredEntry
            Monitor entry to be added to the monitor.

        """
        name = entry_path.rsplit('/', 1)[-1]
        formatting = '{' + entry_path + '}'
        entry = MonitoredEntry(name=name,
                               path=entry_path,
                               formatting=formatting,
                               depend_on=[entry_path])
        entry.value = '{}'.format(value)
        return entry

    def _observe_displayed_entries(self, change):
        """ Observer updating internals when the displayed entries change.

        This observer ensure that the list of database entries which need to
        be monitored reflects the actual needs of the monitor and that the
        monitor entries updaters mapping is up to date.

        """
        if change['type'] == 'update':
            added = set(change['value']) - set(change['oldvalue'])
            removed = set(change['oldvalue']) - set(change['value'])

            for entry in removed:
                self._displayed_entry_removed(entry)
            for entry in added:
                self._displayed_entry_added(entry)

        elif change['type'] == 'container':
            op = change['operation']
            if op in ('__iadd__', 'append', 'extend', 'insert'):
                if 'item' in change:
                    self._displayed_entry_added(change['item'])
                if 'items' in change:
                    for entry in change['items']:
                        self._displayed_entry_added(entry)

            elif op in ('__delitem__', 'remove', 'pop'):
                if 'item' in change:
                    self._displayed_entry_removed(change['item'])
                if 'items' in change:
                    for entry in change['items']:
                        self._displayed_entry_removed(entry)

            elif op in '__setitem__':
                old = change['olditem']
                if isinstance(old, list):
                    for entry in old:
                        self._displayed_entry_removed(entry)
                else:
                    self._displayed_entry_removed(old)

                new = change['newitem']
                if isinstance(new, list):
                    for entry in new:
                        self._displayed_entry_added(entry)
                else:
                    self._displayed_entry_added(new)

    def _displayed_entry_added(self, entry):
        """ Tackle the addition of a displayed monitor entry.

        First this method will add the entry updater into the updaters dict for
        each of its dependence and if one dependence is absent from the
        database_entries it will be added.

        Parameters
        ----------
        entry : MonitoredEntry
            The entry being added to the list of displayed entries of the
            monitor.

        """
        for dependence in entry.depend_on:
            if dependence in self.updaters:
                self.updaters[dependence].append(entry.update)
            else:
                self.updaters[dependence] = [entry.update]

            if dependence not in self.database_entries:
                self.database_entries.append(dependence)

    def _displayed_entry_removed(self, entry):
        """ Tackle the deletion of a displayed monitor entry.

        First this method will remove the entry updater for each of its
        dependence and no updater remain for that database entry, the entry
        will be removed from the database_entries

        Parameters
        ----------
        entry : MonitoredEntry
            The entry being added to the list of displayed entries of the
            monitor.

        """
        for dependence in entry.depend_on:
            self.updaters[dependence].remove(entry.update)

            if not self.updaters[dependence]:
                del self.updaters[dependence]
                self.database_entries.remove(dependence)
Ejemplo n.º 7
0
class JDF_Top(SubAgent):
    """Top class that controls distribution of patterns into JDF"""

    base_name="jdf"

    def show(self):
        shower(self, self.wafer_coords)

    def gen_jdf(self, agents):
        self.clear_JDF()
        for n, p in enumerate(agents):
            if p.plot_sep:
                self.patterns.append(JDF_Pattern(num=n+1, name=p.name))
                self.sub_arrays.append(JDF_Array(array_num=n+1, assigns=[JDF_Assign(assign_type=["P({0})".format(n+1)],
                         short_name=p.name, pos_assign=[(1, 1)])]))

                self.main_arrays[0].assigns.append(JDF_Assign(assign_type=["A({0})".format(n+1)],
                         short_name=p.name, pos_assign=[(n+1, 1)]))
        self.input_jdf=self.jdf_produce()

    wafer_coords=FullWafer(name="jdf_wafer_coords")
    plot=Plotter(name="jdf_plot")

    @private_property
    def xy_offsets(self):
        """recursive traces down locations of all patterns"""
        overall_dict={}
        for pd in [self.p_off_recur(m_arr, p_off={}) for m_arr in self.main_arrays]:
            for key in pd:
                overall_dict[key]=overall_dict.get(key, [])+pd[key]
        return overall_dict

    def p_off_recur(self, p_arr, x_off_in=0, y_off_in=0, p_off={}):
        """recursive search function for pattern locations"""
        for a in p_arr.assigns:
            for pa in a.pos_assign:
                x_off=x_off_in+p_arr.x_start+(pa[0]-1)*p_arr.x_step
                y_off=y_off_in+p_arr.y_start-(pa[1]-1)*p_arr.y_step
                for p in [pattern for pattern in self.patterns if pattern.num in a.P_nums]:
                    if p.name not in p_off:
                        p_off[p.name]=[]
                    p_off[p.name].append((x_off+p.x, y_off+p.y))
                for arr in [array for array in self.sub_arrays if array.array_num in a.A_nums]:
                    self.p_off_recur(arr, x_off, y_off, p_off)
        return p_off

    def assign_condition(self, item, n=0):
        return [assign.short_name for assign in self.main_arrays[n].assigns if item in assign.pos_assign]

    distribute_event=Event()

    def _observe_distribute_event(self, change):
        self.distribute_coords()
        self.get_member("xy_offsets").reset(self)
        xmin=-0.05
        xmax=0.05
        ymin=-0.05
        ymax=0.05
        self.plot.axe.texts=[]
        for main_arr in self.main_arrays:
            for asgn in main_arr.assigns:
                for pos_asgn in asgn.pos_assign:
                    x_off=main_arr.x_start+(pos_asgn[0]-1)*main_arr.x_step
                    y_off=main_arr.y_start-(pos_asgn[1]-1)*main_arr.y_step
                    self.plot.add_text(asgn.short_name, x_off, y_off, size=4.5, alpha=0.8, ha='center')
                    xmin=min(xmin, x_off)
                    xmax=max(xmax, x_off)
                    ymin=min(ymin, y_off)
                    ymax=max(ymax, y_off)

        STRETCH=self.wafer_coords.gap_size
        self.plot.set_xlim(xmin-STRETCH, xmax+STRETCH)
        self.plot.set_ylim(ymin-STRETCH, ymax+STRETCH)
        self.plot.xlabel="x (um)"
        self.plot.ylabel="y (um)"
        self.plot.title="JDF Pattern Distribution"
        self.plot.draw()

    @private_property
    def view_window(self):
        from enaml import imports
        with imports():
            from taref.ebl.jdf_e import JDF_View
        return JDF_View(jdf=self)

    def append_valcom(self, inlist, name, fmt_str="{0}{1}", sep=";"):
            comment=format_comment(get_tag(self, name, "comment", ""))
            value=getattr(self, name)
            inlist.append(fmt_str.format(value, comment))

    @property
    def arrays(self):
        return sqze(self.main_arrays, self.sub_arrays)

    def distribute_coords(self, num=None):
        """distribute coords using wafer_coords object"""
        for qw in self.wafer_coords.quarter_wafers:
            qw.get_member('bad_coords').reset(qw)
            qw.get_member('good_coords').reset(qw)
        self.comments=["distributed main array for quarter wafer {}".format(self.wafer_coords.wafer_type)]
        self.Px, self.Py, self.Qx, self.Qy=self.wafer_coords.GLM

        if self.wafer_coords.wafer_type=="Full" and len(self.main_arrays)<4:
            assigns=[assign.dup_assign() for assign in self.main_arrays[0].assigns]
            self.main_arrays=self._default_main_arrays()
            for arr in self.main_arrays:
                arr.assigns=[assign.dup_assign() for assign in assigns]

        for m, qw in enumerate(self.wafer_coords.quarter_wafers):
            if num is None:
                our_num=len(self.main_arrays[m].assigns)
            else:
                our_num=num
            for n, c in enumerate(qw.distribute_coords(our_num)):
                self.main_arrays[m].assigns[n].pos_assign=c[:]
            self.main_arrays[m].x_start=qw.x_offset
            self.main_arrays[m].x_num=qw.N_chips
            self.main_arrays[m].x_step=qw.step_size

            self.main_arrays[m].y_start=qw.y_offset
            self.main_arrays[m].y_num=qw.N_chips
            self.main_arrays[m].y_step=qw.step_size

        self.output_jdf=self.jdf_produce()
        self.input_jdf=self.output_jdf

    input_jdf=Unicode()
    output_jdf=Unicode()
    comments=List()

    Px=Coerced(int, (-40000,)).tag(desc="X coordinate of global P mark")
    Py=Coerced(int, (4000,)).tag(desc="Y coordinate of global P mark")
    Qx=Coerced(int, (-4000,))
    Qy=Coerced(int, (40000,))

    mgn_name=Unicode("IDT")
    wafer_diameter=Coerced(int, (4,))
    write_diameter=Coerced(float, (-4.2,))

    stdcur=Coerced(int, (2,)).tag(desc="Current to use in nA")
    shot=Coerced(int, (8,)).tag(desc="Shot size in nm. should divide 4 um evenly")
    resist=Coerced(int, (165,)).tag(desc="dose")
    resist_comment=Unicode()
    main_arrays=List().tag(desc="main arrays in JDF", private=True)
    sub_arrays=List().tag(desc="arrays in JDF")#.tag(width='max', inside_type=jdf_array)
    patterns=List().tag(desc="patterns in JDF")#.tag(width='max', inside_type=jdf_pattern)
    jdis=List().tag(desc="jdis in JDF")

    def _default_main_arrays(self):
        if self.wafer_coords.wafer_type=="Full":
            return [JDF_Main_Array(), JDF_Main_Array(), JDF_Main_Array(), JDF_Main_Array()]
        return [JDF_Main_Array()]

    def _observe_input_jdf(self, change):
        self.jdf_parse(self.input_jdf)
        self.output_jdf=self.jdf_produce()

    def clear_JDF(self):
        self.comments=[]
        self.main_arrays=self._default_main_arrays()
        self.sub_arrays=[]
        self.patterns=[]
        self.jdis=[]

    def add_pattern(self, tempstr, comment):
        self.patterns.append(JDF_Pattern(tempstr=tempstr, comment=comment))

    def add_array(self, tempstr, comment):
        if ":" in tempstr:
            array=JDF_Array(tempstr=tempstr, comment=comment)
            self.sub_arrays.append(array)
        else:
            array=JDF_Main_Array(tempstr=tempstr, comment=comment)
            self.main_arrays.append(array)
        return array

    def jdf_parse(self, jdf_data):
        """reads a jdf text and puts the data into objects"""
        jdf_list=jdf_data.split("\n")
        inside_path=False
        inside_layer=False
        self.clear_JDF()
        for n, line in enumerate(jdf_list):
            tempstr, comment=parse_comment(line)
            if tempstr=="" and comment!="":
                self.comments.append(comment)
            if tempstr.startswith('GLMPOS'):
                self.Px, self.Py, self.Qx, self.Qy=xy_string_split(tempstr)
            elif tempstr.startswith('JOB'):
                mgn_name, self.wafer_diameter, self.write_diameter=tempstr.split(",")
                self.mgn_name=mgn_name.split("'")[1].strip()
            elif tempstr.startswith("PATH"):
                inside_path=True
            elif "LAYER" in tempstr:
                inside_layer=True
            if inside_path:
                if 'ARRAY' in tempstr:
                    array=self.add_array(tempstr, comment)
                elif 'ASSIGN' in tempstr:
                    array.add_assign(tempstr, comment)
                elif 'CHMPOS' in tempstr:
                    M1x, M1y=tuple_split(tempstr)
                    if len(self.main_arrays)>0:
                        self.main_arrays[-1].M1x=M1x
                        self.main_arrays[-1].M1y=M1y
                elif "PEND" in tempstr:
                    inside_path=False
            elif inside_layer:
                if 'END' in tempstr:
                    inside_layer=False
                elif 'STDCUR' in tempstr:
                    set_attr(self, "stdcur", tempstr.split("STDCUR")[1], comment=comment)
                elif 'SHOT' in tempstr:
                    set_attr(self, "shot", tempstr.split(',')[1], comment=comment)
                elif 'RESIST' in tempstr:
                    set_attr(self, "resist", tempstr.split('RESIST')[1], comment=comment)
                elif 'P(' in tempstr:
                    self.add_pattern(tempstr, comment)
                elif tempstr.startswith('@'):
                    jdi_str=tempstr.split("'")[1].split(".jdi")[0]
                    self.jdis.append(jdi_str)

    def jdf_produce(self):
        """produces a jdf from the data stored in the object"""
        jl=[]
        jl.append("JOB/W '{name}', {waf_diam}, {write_diam}\n".format(name=self.mgn_name,
                  waf_diam=self.wafer_diameter, write_diam=self.write_diameter))
        if len(self.comments)>0:
            jl.append(";{comment}\n".format(comment=self.comments[0]))
        jl.append("GLMPOS P=({Px}, {Py}), Q=({Qx},{Qy})".format(Px=self.Px, Py=self.Py, Qx=self.Qx, Qy=self.Qy))
        jl.append("PATH")

        for n, item in enumerate(self.arrays):
            jl.extend(item.jdf_output)
        jl.append("PEND\n\nLAYER 1")

        for n, item in enumerate(self.patterns):
            jl.append(item.jdf_output)

        self.append_valcom(jl, "stdcur", "\nSTDCUR {0}{1}")
        self.append_valcom(jl, "shot", "SHOT A, {0}{1}")
        self.append_valcom(jl, "resist", "RESIST {0}{1}\n")

        for item in self.jdis:
            jl.append("@ '{jdi_name}.jdi'".format(jdi_name=item))
        jl.append("\nEND 1\n")

        if len(self.comments)>1:
            for item in self.comments[1:]:
                jl.append(";{}".format(item))

        return "\n".join(jl)
Ejemplo n.º 8
0
class KeysResponse(Response):
    """ Expected response from api/keys """
    Keys = List(Key)
    RecipientType = Int()
Ejemplo n.º 9
0
class ComplexTask(BaseTask):
    """Task composed of several subtasks.

    """
    #: List of all the children of the task. The list should not be manipulated
    #: directly by user code.
    #: The tag 'child' is used to mark that a member can contain child tasks
    #: and is used to gather children for operation which must occur on all of
    #: them.
    children = List().tag(child=100)

    #: Signal emitted when the list of children change, the payload will be a
    #: ContainerChange instance.
    #: The tag 'child_notifier' is used to mark that a member emmit
    #: notifications about modification of another 'child' member. This allow
    #: editors to correctly track all of those.
    children_changed = Signal().tag(child_notifier='children')

    def perform(self):
        """Run sequentially all child tasks.

        """
        for child in self.children:
            child.perform_()

    def check(self, *args, **kwargs):
        """Run test of all child tasks.

        """
        test, traceback = super(ComplexTask, self).check(*args, **kwargs)
        for child in self.gather_children():
            try:
                check = child.check(*args, **kwargs)
                test = test and check[0]
                traceback.update(check[1])
            except Exception:
                test = False
                msg = 'An exception occured while running check :\n%s'
                traceback[child.path + '/' + child.name] = msg % format_exc()

        return test, traceback

    def prepare(self):
        """Overridden to prepare also children tasks.

        """
        super(ComplexTask, self).prepare()
        for child in self.gather_children():
            child.prepare()

    def add_child_task(self, index, child):
        """Add a child task at the given index.

        Parameters
        ----------
        index : int
            Index at which to insert the new child task.

        child : BaseTask
            Task to insert in the list of children task.

        """
        self.children.insert(index, child)

        # In the absence of a root task do nothing else than inserting the
        # child.
        if self.root is not None:
            child.depth = self.depth + 1
            child.database = self.database
            child.path = self._child_path()

            # Give him its root so that it can proceed to any child
            # registration it needs to.
            child.parent = self
            child.root = self.root

            # Ask the child to register in database
            child.register_in_database()

            # Register anew preferences to keep the right ordering for the
            # children
            self.register_preferences()

            change = ContainerChange(obj=self, name='children',
                                     added=[(index, child)])
            self.children_changed(change)

    def move_child_task(self, old, new):
        """Move a child task.

        Parameters
        ----------
        old : int
            Index at which the child to move is currently located.

        new : BaseTask
            Index at which to insert the child task.

        """
        child = self.children.pop(old)
        self.children.insert(new, child)

        # In the absence of a root task do nothing else than moving the
        # child.
        if self.root is not None:
            # Register anew preferences to keep the right ordering for the
            # children
            self.register_preferences()

            change = ContainerChange(obj=self, name='children',
                                     moved=[(old, new, child)])
            self.children_changed(change)

    def remove_child_task(self, index):
        """Remove a child task from the children list.

        Parameters
        ----------
        index : int
            Index at which the child to remove is located.

        """
        child = self.children.pop(index)

        # Cleanup database, update preferences
        child.unregister_from_database()
        child.root = None
        child.parent = None
        child.database = None
        self.register_preferences()

        change = ContainerChange(obj=self, name='children',
                                 removed=[(index, child)])
        self.children_changed(change)

    def gather_children(self):
        """Build a flat list of all children task.

        Children tasks are ordered according to their 'child' tag value.

        Returns
        -------
        children : list
            List of all the task children.

        """
        children = []
        tagged = tagged_members(self, 'child')
        for name in sorted(tagged, key=lambda m: tagged[m].metadata['child']):

            child = getattr(self, name)
            if child:
                if isinstance(child, Iterable):
                    children.extend(child)
                else:
                    children.append(child)

        return children

    def traverse(self, depth=-1):
        """Reimplemented to yield all child task.

        """
        yield self

        if depth == 0:
            for c in self.gather_children():
                if c:
                    yield c

        else:
            for c in self.gather_children():
                if c:
                    for subc in c.traverse(depth - 1):
                        yield subc

    def register_in_database(self):
        """Create a node in the database and register all entries.

        This method registers both the task entries and all the tasks tagged
        as child.

        """
        super(ComplexTask, self).register_in_database()
        self.database.create_node(self.path, self.name)

        # ComplexTask defines children so we always get something
        for child in self.gather_children():
            child.register_in_database()

    def unregister_from_database(self):
        """Unregister all entries and delete associated database node.

        This method unregisters both the task entries and all the tasks tagged
        as child.

        """
        super(ComplexTask, self).unregister_from_database()

        for child in self.gather_children():
            child.unregister_from_database()

        self.database.delete_node(self.path, self.name)

    def register_preferences(self):
        """Register the task preferences into the preferences system.

        This method registers both the task preferences and all the
        preferences of the tasks tagged as child.

        """

        self.preferences.clear()
        for name, member in tagged_members(self, 'pref').items():
            # Register preferences.
            val = getattr(self, name)
            self.preferences[name] = member_to_pref(self, member, val)

        # Find all tagged children.
        for name in tagged_members(self, 'child'):
            child = getattr(self, name)
            if child:
                if isinstance(child, Iterable):
                    for i, aux in enumerate(child):
                        child_id = name + '_{}'.format(i)
                        self.preferences[child_id] = {}
                        aux.preferences = \
                            self.preferences[child_id]
                        aux.register_preferences()
                else:
                    self.preferences[name] = {}
                    child.preferences = self.preferences[name]
                    child.register_preferences()

    def update_preferences_from_members(self):
        """Update the values stored in the preference system.

        This method updates both the task preferences and all the
        preferences of the tasks tagged as child.

        """
        for name, member in tagged_members(self, 'pref').items():
            val = getattr(self, name)
            self.preferences[name] = member_to_pref(self, member, val)

        for child in self.gather_children():
            child.update_preferences_from_members()

    @classmethod
    def build_from_config(cls, config, dependencies):
        """Create a new instance using the provided infos for initialisation.

        Parameters
        ----------
        config : dict(str)
            Dictionary holding the new values to give to the members in string
            format, or dictionnary like for instance with prefs.

        dependencies : dict
            Dictionary holding the necessary classes needed when rebuilding.
            This is assembled by the TaskManager.

        Returns
        -------
        task : BaseTask
            Newly created and initiliazed task.

        Notes
        -----
        This method is fairly powerful and can handle a lot of cases so
        don't override it without checking that it works.

        """
        task = cls()
        update_members_from_preferences(task, config)
        for name, member in tagged_members(task, 'child').items():

            if isinstance(member, List):
                i = 0
                pref = name + '_{}'
                validated = []
                while True:
                    child_name = pref.format(i)
                    if child_name not in config:
                        break
                    child_config = config[child_name]
                    child_class_name = child_config.pop('task_id')
                    child_cls = dependencies[DEP_TYPE][child_class_name]
                    child = child_cls.build_from_config(child_config,
                                                        dependencies)
                    validated.append(child)
                    i += 1

            else:
                if name not in config:
                    continue
                child_config = config[name]
                child_class_name = child_config.pop('task_id')
                child_class = dependencies[DEP_TYPE][child_class_name]
                validated = child_class.build_from_config(child_config,
                                                          dependencies)

            setattr(task, name, validated)

        return task

    # =========================================================================
    # --- Private API ---------------------------------------------------------
    # =========================================================================

    #: Last removed child and list of database access exceptions attached to
    #: it and necessity to observe its _access_exs.
    _last_removed = Tuple(default=(None, None, False))

    #: Last access exceptions desactivated from a child.
    _last_exs = Coerced(set)

    #: List of access_exs, linked to access exs in child, disabled because
    #: child disabled some access_exs.
    _disabled_exs = List()

    def _child_path(self):
        """Convenience function returning the path to set for child task.

        """
        return self.path + '/' + self.name

    def _update_children_path(self):
        """Update the path of all children.

        """
        for child in self.gather_children():
            child.path = self._child_path()
            if isinstance(child, ComplexTask):
                child._update_children_path()

    def _post_setattr_name(self, old, new):
        """Handle the task being renamed at runtime.

        If the task is renamed at runtime, it means that the path of all the
        children task is now obsolete and that the database node
        of this task must be renamed (database handles the exception.

        """
        if old and self.database:
            super(ComplexTask, self)._post_setattr_name(old, new)
            self.database.rename_node(self.path, old, new)

            # Update the path of all children.
            self._update_children_path()

    def _post_setattr_root(self, old, new):
        """Make sure that all children get all the info they need to behave
        correctly when the task get its root parent (ie the task is now
        in a 'correct' environnement).

        """
        if new is None:
            self.database = None
            for child in self.gather_children():
                child.root = None
                child.database = None
            return

        for child in self.gather_children():
            child.depth = self.depth + 1
            child.database = self.database
            child.path = self._child_path()

            # Give him its root so that it can proceed to any child
            # registration it needs to.
            child.parent = self
            child.root = self.root
Ejemplo n.º 10
0
class AddressesResponse(Response):
    """ Expected response from api/addresses """
    Addresses = List(Address)
Ejemplo n.º 11
0
class MessageReadResponse(Response):
    """ Expected response from put api/messages/read """
    Responses = List(MessageReadResult)
Ejemplo n.º 12
0
class ContactsResponse(Response):
    """ Expected response from api/contacts """
    Limit = Int()
    Total = Int()
    Contacts = List(Contact)
Ejemplo n.º 13
0
class ConversationResponse(Response):
    """ Expected response from api/conversations/<id> """
    Conversation = Instance(Conversation)
    Messages = List(Message)
Ejemplo n.º 14
0
class ConversationsResponse(Response):
    """ Expected response from api/conversations """
    Limit = Int()
    Total = Int()
    Conversations = List(Conversation)
Ejemplo n.º 15
0
class QtOccViewer(QtControl, ProxyOccViewer):

    #: Viewer widget
    widget = Typed(QtViewer3d)

    #: Update count
    _redraw_blocked = Bool()

    #: Displayed Shapes
    _displayed_shapes = Dict()
    _displayed_dimensions = Dict()
    _displayed_graphics = Dict()
    _selected_shapes = List()

    #: Errors
    errors = Dict()

    #: Tuple of (Quantity_Color, transparency)
    shape_color = Typed(tuple)

    #: Grid colors
    grid_colors = Dict()

    #: Shapes
    shapes = Property(lambda self: self.get_shapes(), cached=True)

    #: Dimensions
    dimensions = Typed(set)
    graphics = Typed(set)

    # -------------------------------------------------------------------------
    # OpenCascade specific members
    # -------------------------------------------------------------------------
    display_connection = Typed(Aspect_DisplayConnection)
    v3d_viewer = Typed(V3d_Viewer)
    v3d_view = Typed(V3d_View)

    ais_context = Typed(AIS_InteractiveContext)
    prs3d_drawer = Typed(Prs3d_Drawer)
    prs_mgr = Typed(PrsMgr_PresentationManager)
    v3d_window = Typed(V3d_Window)
    gfx_structure_manager = Typed(Graphic3d_StructureManager)
    gfx_structure = Typed(Graphic3d_Structure)
    graphics_driver = Typed(OpenGl_GraphicDriver)
    camera = Typed(Graphic3d_Camera)

    #: List of lights
    lights = List()

    #: Fired
    _redisplay_timer = Typed(QTimer, ())

    _qt_app = Property(lambda self: Application.instance()._qapp, cached=True)

    def get_shapes(self):
        return [c for c in self.children() if not isinstance(c, QtControl)]

    def create_widget(self):
        self.widget = QtViewer3d(parent=self.parent_widget())

    def init_widget(self):
        super().init_widget()
        widget = self.widget
        widget.proxy = self

        redisplay_timer = self._redisplay_timer
        redisplay_timer.setSingleShot(True)
        redisplay_timer.setInterval(8)
        redisplay_timer.timeout.connect(self.on_redisplay_requested)

    def init_viewer(self):
        """ Init viewer when the QOpenGLWidget is ready

        """
        d = self.declaration
        widget = self.widget
        if sys.platform == 'win32':
            display = Aspect_DisplayConnection()
        else:
            display_name = TCollection_AsciiString(
                os.environ.get('DISPLAY', '0'))
            display = Aspect_DisplayConnection(display_name)
        self.display_connection = display

        # Create viewer
        graphics_driver = self.graphics_driver = OpenGl_GraphicDriver(display)

        viewer = self.v3d_viewer = V3d_Viewer(graphics_driver)
        view = self.v3d_view = viewer.CreateView()

        # Setup window
        win_id = widget.get_window_id()
        if sys.platform == 'win32':
            window = WNT_Window(win_id)
        elif sys.platform == 'darwin':
            window = Cocoa_Window(win_id)
        else:
            window = Xw_Window(self.display_connection, win_id)
        if not window.IsMapped():
            window.Map()
        self.v3d_window = window
        view.SetWindow(window)
        view.MustBeResized()

        # Setup viewer
        ais_context = self.ais_context = AIS_InteractiveContext(viewer)
        drawer = self.prs3d_drawer = ais_context.DefaultDrawer()

        # Needed for displaying graphics
        prs_mgr = self.prs_mgr = ais_context.MainPrsMgr()
        gfx_mgr = self.gfx_structure_manager = prs_mgr.StructureManager()
        self.gfx_structure = Graphic3d_Structure(gfx_mgr)

        # Lights camera
        self.camera = view.Camera()

        try:
            self.set_lights(d.lights)
        except Exception as e:
            log.exception(e)
            viewer.SetDefaultLights()

        #viewer.DisplayPrivilegedPlane(True, 1)
        #view.SetShadingModel(Graphic3d_TypeOfShadingModel.V3d_PHONG)

        # background gradient
        with self.redraw_blocked():
            self.set_background_gradient(d.background_gradient)
            self.set_draw_boundaries(d.draw_boundaries)
            self.set_trihedron_mode(d.trihedron_mode)
            self.set_display_mode(d.display_mode)
            self.set_hidden_line_removal(d.hidden_line_removal)
            self.set_selection_mode(d.selection_mode)
            self.set_view_mode(d.view_mode)
            self.set_view_projection(d.view_projection)
            self.set_lock_rotation(d.lock_rotation)
            self.set_lock_zoom(d.lock_zoom)
            self.set_shape_color(d.shape_color)
            self.set_chordial_deviation(d.chordial_deviation)
            self._update_rendering_params()
            self.set_grid_mode(d.grid_mode)
            self.set_grid_colors(d.grid_colors)

            self.init_signals()
            self.dump_gl_info()

        self.redraw()

        qt_app = self._qt_app
        for child in self.children():
            self.child_added(child)
            qt_app.processEvents()

    def dump_gl_info(self):
        # Debug info
        try:
            ctx = self.graphics_driver.GetSharedContext()
            if ctx is None or not ctx.IsValid():
                return
            v1 = ctx.VersionMajor()
            v2 = ctx.VersionMinor()
            log.info("OpenGL version: {}.{}".format(v1, v2))
            log.info("GPU memory: {}".format(ctx.AvailableMemory()))
            log.info("GPU memory info: {}".format(
                ctx.MemoryInfo().ToCString()))
            log.info("Max MSAA samples: {}".format(ctx.MaxMsaaSamples()))

            supports_raytracing = ctx.HasRayTracing()
            log.info("Supports ray tracing: {}".format(supports_raytracing))
            if supports_raytracing:
                log.info("Supports textures: {}".format(
                    ctx.HasRayTracingTextures()))
                log.info("Supports adaptive sampling: {}".format(
                    ctx.HasRayTracingAdaptiveSampling()))
                log.info("Supports adaptive sampling atomic: {}".format(
                    ctx.HasRayTracingAdaptiveSamplingAtomic()))
            else:
                ver_too_low = ctx.IsGlGreaterEqual(3, 1)
                if not ver_too_low:
                    log.info("OpenGL version must be >= 3.1")
                else:
                    ext = "GL_ARB_texture_buffer_object_rgb32"
                    if not ctx.CheckExtension(ext):
                        log.info("OpenGL extension {} is missing".format(ext))
                    else:
                        log.info("OpenGL glBlitFramebuffer is missing")
        except Exception as e:
            log.exception(e)

    def init_signals(self):
        d = self.declaration
        callbacks = self.widget._callbacks
        for name in callbacks.keys():
            cb = getattr(d, name, None)
            if cb is not None:
                callbacks[name].append(cb)

    def child_added(self, child):
        if isinstance(child, OccShape):
            self._add_shape_to_display(child)
        elif isinstance(child, OccDimension):
            self._add_dimension_to_display(child)
        else:
            super().child_added(child)

    def child_removed(self, child):
        if isinstance(child, OccShape):
            self._remove_shape_from_display(child)
        elif isinstance(child, OccDimension):
            self._remove_dimension_from_display(child)
        else:
            super().child_removed(child)

    def _add_shape_to_display(self, occ_shape):
        """ Add an OccShape to the display

        """
        d = occ_shape.declaration
        if not d.display:
            return
        displayed_shapes = self._displayed_shapes
        display = self.ais_context.Display
        qt_app = self._qt_app
        occ_shape.displayed = True
        for s in occ_shape.walk_shapes():
            s.observe('ais_shape', self.on_ais_shape_changed)
            ais_shape = s.ais_shape
            if ais_shape is not None:
                try:
                    display(ais_shape, False)
                    s.displayed = True
                    displayed_shapes[s.shape] = s
                except RuntimeError as e:
                    log.exception(e)

                # Displaying can take a lot of time
                qt_app.processEvents()

        if isinstance(occ_shape, OccPart):
            for d in occ_shape.declaration.traverse():
                proxy = getattr(d, 'proxy', None)
                if proxy is None:
                    continue
                if isinstance(proxy, OccDimension):
                    self._add_dimension_to_display(proxy)
                elif isinstance(proxy, OccDisplayItem):
                    self._add_item_to_display(proxy)

        self._redisplay_timer.start()

    def _remove_shape_from_display(self, occ_shape):
        displayed_shapes = self._displayed_shapes
        remove = self.ais_context.Remove
        occ_shape.displayed = False
        for s in occ_shape.walk_shapes():
            s.unobserve('ais_shape', self.on_ais_shape_changed)
            if s.get_member('ais_shape').get_slot(s) is None:
                continue
            ais_shape = s.ais_shape
            if ais_shape is not None:
                s.displayed = False
                displayed_shapes.pop(ais_shape, None)
                remove(ais_shape, False)

        if isinstance(occ_shape, OccPart):
            for d in occ_shape.declaration.traverse():
                proxy = getattr(d, 'proxy', None)
                if proxy is None:
                    continue
                if isinstance(proxy, OccDimension):
                    self._remove_dimension_from_display(proxy)
                elif isinstance(proxy, OccDisplayItem):
                    self._remove_item_from_display(proxy)

        self._redisplay_timer.start()

    def on_ais_shape_changed(self, change):
        ais_context = self.ais_context
        displayed_shapes = self._displayed_shapes
        occ_shape = change['object']
        if change['type'] == 'update':
            old_ais_shape = change['oldvalue']
            if old_ais_shape is not None:
                old_shape = old_ais_shape.Shape()
                displayed_shapes.pop(old_shape, None)
                ais_context.Remove(old_ais_shape, False)
                occ_shape.displayed = False
            new_ais_shape = change['value']
            if new_ais_shape is not None:
                displayed_shapes[occ_shape.shape] = occ_shape
                ais_context.Display(new_ais_shape, False)
                occ_shape.displayed = True
        self._redisplay_timer.start()

    def _add_dimension_to_display(self, occ_dim):
        ais_dimension = occ_dim.dimension
        if ais_dimension is not None:
            self.ais_context.Display(ais_dimension, False)
            self._displayed_dimensions[ais_dimension] = occ_dim
        self._redisplay_timer.start()

    def _remove_dimension_from_display(self, occ_dim):
        ais_dimension = occ_dim.dimension
        if ais_dimension is not None:
            self.ais_context.Remove(ais_dimension, False)
            self._displayed_dimensions.pop(ais_dimension, None)
        self._redisplay_timer.start()

    def _add_item_to_display(self, occ_disp_item):
        ais_object = occ_disp_item.item
        if ais_object is not None:
            self.ais_context.Display(ais_object, False)
            self._displayed_graphics[ais_object] = occ_disp_item
        self._redisplay_timer.start()

    def _remove_item_from_display(self, occ_disp_item):
        ais_object = occ_disp_item.item
        if ais_object is not None:
            self.ais_context.Remove(ais_object, False)
            self._displayed_graphics.pop(ais_object, None)
        self._redisplay_timer.start()

    def on_redisplay_requested(self):
        self.ais_context.UpdateCurrentViewer()

        # Recompute bounding box
        bbox = self.get_bounding_box(self._displayed_shapes.keys())
        self.declaration.bbox = BBox(*bbox)

    # -------------------------------------------------------------------------
    # Viewer API
    # -------------------------------------------------------------------------
    def get_bounding_box(self, shapes):
        """ Compute the bounding box for the given list of shapes.
        Return values are in 3d coordinate space.

        Parameters
        ----------
        shapes: List
            A list of TopoDS_Shape to compute a bbox for

        Returns
        -------
        bbox: Tuple
            A tuple of (xmin, ymin, zmin, xmax, ymax, zmax).

        """
        bbox = Bnd_Box()
        for shape in shapes:
            BRepBndLib.Add_(shape, bbox)
        try:
            pmin = bbox.CornerMin()
            pmax = bbox.CornerMax()
        except RuntimeError:
            return (0, 0, 0, 0, 0, 0)
        return (pmin.X(), pmin.Y(), pmin.Z(), pmax.X(), pmax.Y(), pmax.Z())

    def get_screen_coordinate(self, point):
        """ Convert a 3d coordinate to a 2d screen coordinate

        Parameters
        ----------
        (x, y, z): Tuple
            A 3d coordinate
        """
        return self.v3d_view.Convert(point[0], point[1], point[2], 0, 0)

    # -------------------------------------------------------------------------
    # Rendering parameters
    # -------------------------------------------------------------------------
    def set_chordial_deviation(self, deviation):
        # Turn up tesselation defaults
        self.prs3d_drawer.SetMaximalChordialDeviation(deviation)

    def set_lights(self, lights):
        viewer = self.v3d_viewer
        new_lights = []

        for d in lights:
            color, _ = color_to_quantity_color(d.color)
            if d.type == "directional":
                if '_' in d.orientation:
                    attr = 'V3d_TypeOfOrientation_{}'.format(d.orientation)
                else:
                    attr = 'V3d_{}'.format(d.orientation)
                orientation = getattr(V3d.V3d_TypeOfOrientation, attr,
                                      V3d.V3d_Zneg)
                light = V3d.V3d_DirectionalLight(orientation, color,
                                                 d.headlight)
            elif d.type == "spot":
                light = V3d.V3d_SpotLight(d.position, d.direction, color)
                light.SetAngle(d.angle)
            else:
                light = V3d.V3d_AmbientLight(color)
            light.SetIntensity(d.intensity)

            if d.range:
                light.SetRange(d.range)

            viewer.AddLight(light)
            if d.enabled:
                viewer.SetLightOn(light)
            new_lights.append(light)

        for light in self.lights:
            viewer.DelLight(self.light)

        self.lights = new_lights

    def set_draw_boundaries(self, enabled):
        self.prs3d_drawer.SetFaceBoundaryDraw(enabled)

    def set_hidden_line_removal(self, enabled):
        view = self.v3d_view
        view.SetComputedMode(enabled)
        self.redraw()

    def set_antialiasing(self, enabled):
        self._update_rendering_params()

    def set_shadows(self, enabled):
        self._update_rendering_params()

    def set_reflections(self, enabled):
        self._update_rendering_params()

    def set_raytracing(self, enabled):
        self._update_rendering_params()

    def set_raytracing_depth(self, depth):
        self._update_rendering_params()

    def _update_rendering_params(self, **params):
        """ Set the rendering parameters of the view

        Parameters
        ----------
        **params:
            See Graphic3d_RenderingParams members

        """
        d = self.declaration
        view = self.v3d_view
        rendering_params = view.ChangeRenderingParams()
        if d.raytracing:
            method = Graphic3d_RM_RAYTRACING
        else:
            method = Graphic3d_RM_RASTERIZATION

        defaults = dict(
            Method=method,
            RaytracingDepth=d.raytracing_depth,
            # IsGlobalIlluminationEnabled=d.raytracing,
            IsShadowEnabled=d.shadows,
            IsReflectionEnabled=d.reflections,
            IsAntialiasingEnabled=d.antialiasing,
            IsTransparentShadowEnabled=d.shadows,
            NbMsaaSamples=4,
            StereoMode=Graphic3d_StereoMode_QuadBuffer,
            AnaglyphFilter=Graphic3d_RenderingParams.
            Anaglyph_RedCyan_Optimized,
            ToReverseStereo=False)
        defaults.update(**params)
        for attr, v in defaults.items():
            setattr(rendering_params, attr, v)
        self.redraw()

    def set_background_gradient(self, gradient):
        """ Set the background gradient

        Parameters
        ----------
        gradient: Tuple
            Gradient parameters Color 1, Color 2, and optionally th fill method

        """
        c1, _ = color_to_quantity_color(gradient[0])
        c2, _ = color_to_quantity_color(gradient[1])
        fill_method = Aspect_GFM_VER
        if len(gradient) == 3:
            attr = 'Aspect_GFM_{}'.format(gradient[2].upper())
            fill_method = getattr(Aspect, attr, Aspect_GFM_VER)
        self.v3d_view.SetBgGradientColors(c1, c2, fill_method, True)

    def set_shape_color(self, color):
        self.shape_color = color_to_quantity_color(color)

    def set_trihedron_mode(self, mode):
        attr = 'Aspect_TOTP_{}'.format(mode.upper().replace("-", "_"))
        position = getattr(Aspect, attr)
        self.v3d_view.TriedronDisplay(position, BLACK, 0.1, V3d.V3d_ZBUFFER)
        self.redraw()

    def set_grid_mode(self, mode):
        if not mode:
            self.v3d_viewer.DeactivateGrid()
        else:
            a, b = mode.title().split("-")
            grid_type = getattr(Aspect_GridType, f'Aspect_GT_{a}')
            grid_mode = getattr(Aspect_GridDrawMode, f'Aspect_GDM_{b}')
            self.v3d_viewer.ActivateGrid(grid_type, grid_mode)

    def set_grid_colors(self, colors):
        c1, _ = color_to_quantity_color(colors[0])
        c2, _ = color_to_quantity_color(colors[1])
        grid = self.v3d_viewer.Grid()
        grid.SetColors(c1, c2)

    # -------------------------------------------------------------------------
    # Viewer interaction
    # -------------------------------------------------------------------------
    def set_selection_mode(self, mode):
        """ Set the selection mode.

        Parameters
        ----------
        mode: String
            The mode to use (Face, Edge, Vertex, Shell, or Solid)

        """
        ais_context = self.ais_context
        ais_context.Deactivate()
        if mode == 'any':
            for mode in (TopAbs.TopAbs_SHAPE, TopAbs.TopAbs_SHELL,
                         TopAbs.TopAbs_FACE, TopAbs.TopAbs_EDGE,
                         TopAbs.TopAbs_WIRE, TopAbs.TopAbs_VERTEX):
                ais_context.Activate(AIS_Shape.SelectionMode_(mode))
            return
        attr = 'TopAbs_%s' % mode.upper()
        mode = getattr(TopAbs, attr, TopAbs.TopAbs_SHAPE)
        ais_context.Activate(AIS_Shape.SelectionMode_(mode))

    def set_display_mode(self, mode):
        mode = V3D_DISPLAY_MODES.get(mode)
        if mode is None:
            return
        self.ais_context.SetDisplayMode(mode, True)
        self.redraw()

    def set_view_mode(self, mode):
        """ Set the view mode or (or direction)

        Parameters
        ----------
        mode: String
            The mode to or direction to view.

        """
        mode = V3D_VIEW_MODES.get(mode.lower())
        if mode is None:
            return
        self.v3d_view.SetProj(mode)

    def set_view_projection(self, mode):
        mode = getattr(Graphic3d_Camera, 'Projection_%s' % mode.title())
        self.camera.SetProjectionType(mode)
        self.redraw()

    def set_lock_rotation(self, locked):
        self.widget._lock_rotation = locked

    def set_lock_zoom(self, locked):
        self.widget._lock_zoom = locked

    def zoom_factor(self, factor):
        self.v3d_view.SetZoom(factor)

    def rotate_view(self, x=0, y=0, z=0):
        self.v3d_view.Rotate(x, y, z, True)

    def turn_view(self, x=0, y=0, z=0):
        self.v3d_view.Turn(x, y, z, True)

    def fit_all(self):
        view = self.v3d_view
        view.FitAll()
        view.ZFitAll()
        self.redraw()

    def fit_selection(self):
        if not self._selected_shapes:
            return

        # Compute bounding box of the selection
        view = self.v3d_view
        pad = 20
        bbox = self.get_bounding_box(self._selected_shapes)
        xmin, ymin = self.get_screen_coordinate(bbox[0:3])
        xmax, ymax = self.get_screen_coordinate(bbox[3:6])
        cx, cy = int(xmin + (xmax - xmin) / 2), int(ymin + (ymax - ymin) / 2)
        self.ais_context.MoveTo(cx, cy, view, True)
        view.WindowFit(xmin - pad, ymin - pad, xmax + pad, ymax + pad)

    def take_screenshot(self, filename):
        return self.v3d_view.Dump(filename)

    # -------------------------------------------------------------------------
    # Display Handling
    # -------------------------------------------------------------------------

    def update_selection(self, pos, area, shift):
        """ Update the selection state

        """
        widget = self.widget
        view = self.v3d_view
        ais_context = self.ais_context

        if area:
            xmin, ymin, dx, dy = area
            ais_context.Select(xmin, ymin, xmin + dx, ymin + dy, view, True)
        elif shift:
            # multiple select if shift is pressed
            ais_context.ShiftSelect(True)
        else:
            ais_context.Select(True)
        ais_context.InitSelected()

        # Lookup the shape declrations based on the selection context
        selection = {}
        shapes = []
        displayed_shapes = self._displayed_shapes
        occ_shapes = set(self._displayed_shapes.values())
        while ais_context.MoreSelected():
            if ais_context.HasSelectedShape():
                i = None
                found = False
                topods_shape = Topology.cast_shape(ais_context.SelectedShape())
                shape_type = topods_shape.ShapeType()
                attr = str(shape_type).split("_")[-1].lower() + 's'

                # Try long lookup based on topology
                for occ_shape in occ_shapes:
                    shape_list = getattr(occ_shape.topology, attr, None)
                    if not shape_list:
                        continue
                    for i, s in enumerate(shape_list):
                        if topods_shape.IsPartner(s):
                            found = True
                            break
                    if found:
                        d = occ_shape.declaration
                        shapes.append(topods_shape)
                        # Insert what was selected into the options
                        info = selection.get(d)
                        if info is None:
                            info = selection[d] = {}
                        selection_info = info.get(attr)
                        if selection_info is None:
                            selection_info = info[attr] = {}
                        selection_info[i] = topods_shape
                        break

                # Mark it as found we don't know what shape it's from
                if not found:
                    if None not in selection:
                        selection[None] = {}
                    if attr not in selection[None]:
                        selection[None][attr] = {}
                    info = selection[None][attr]
                    # Just keep incrementing the index
                    info[len(info)] = topods_shape

            ais_context.NextSelected()

        if shift:
            ais_context.UpdateSelected(True)
        # Set selection
        self._selected_shapes = shapes
        self.declaration.selection = ViewerSelection(selection=selection,
                                                     position=pos,
                                                     area=area)

    def update_display(self, change=None):
        """ Queue an update request """
        self._redisplay_timer.start()

    def clear_display(self):
        """ Remove all shapes and dimensions drawn """
        # Erase all just hides them
        remove = self.ais_context.Remove
        for occ_shape in self._displayed_shapes.values():
            remove(occ_shape.ais_shape, False)
        for ais_dim in self._displayed_dimensions.keys():
            remove(ais_dim, False)
        for ais_item in self._displayed_graphics.keys():
            remove(ais_item, False)
        self.gfx_structure.Clear()
        self.ais_context.UpdateCurrentViewer()

    def reset_view(self):
        """ Reset to default zoom and orientation """
        self.v3d_view.Reset()

    @contextmanager
    def redraw_blocked(self):
        """ Temporarily stop redraw during """
        self._redraw_blocked = True
        yield
        self._redraw_blocked = False

    def redraw(self):
        if not self._redraw_blocked:
            self.v3d_view.Redraw()

    def update(self):
        """ Redisplay """
        self.ais_context.UpdateCurrentViewer()
Ejemplo n.º 16
0
class TaskDatabase(Atom):
    """ A database for inter tasks communication.

    The database has two modes:

    - an edition mode in which the number of entries and their hierarchy
      can change. In this mode the database is represented by a nested dict.

    - a running mode in which the entries are fixed (only their values can
      change). In this mode the database is represented as a flat list.
      In running mode the database is thread safe but the object it contains
      may not be so (dict, list, etc)

    """
    #: Signal used to notify a value changed in the database.
    #: In edition mode the update is passed as a tuple ('added', path, value)
    #: for creation, as ('renamed', old, new, value) in case of renaming,
    #: ('removed', old) in case of deletion or as a list of such tuples.
    #: In running mode, a 2-tuple (path, value) is sent as entries cannot be
    #: renamed or removed.
    notifier = Signal()

    #: Signal emitted to notify that access exceptions has changed. The update
    #: is passed as a tuple ('added', path, relative, entry) for creation or as
    #: ('renamed', path, relative, old, new) in case of renaming of the related
    #: entry, ('removed', path, relative, old) in case of deletion (if old is
    #: None all  exceptions have been removed) or as a list of such tuples.
    #: Path indicate the node where the exception is located, relative the
    #: relative path from the 'path' node to the real location of the entry.
    access_notifier = Signal()

    #: Signal emitted to notify that the nodes were modified. The update
    #: is passed as a tuple ('added', path, name, node) for creation or as
    #: ('renamed', path, old, new) in case of renaming of the related node,
    #: ('removed', path, old) in case of deletion or as a list of such tuples.
    nodes_notifier = Signal()

    #: List of root entries which should not be listed.
    excluded = List(default=['threads', 'instrs'])

    #: Flag indicating whether or not the database entered the running mode. In
    #: running mode the database is flattened into a list for faster acces.
    running = Bool(False)

    def set_value(self, node_path, value_name, value):
        """Method used to set the value of the entry at the specified path

        This method can be used both in edition and running mode.

        Parameters
        ----------
        node_path : unicode
            Path to the node holding the value to be set

        value_name : unicode
            Public key associated with the value to be set, internally
            converted so that we do not mix value and nodes

        value : any
            Actual value to be stored

        Returns
        -------
        new_val : bool
            Boolean indicating whether or not a new entry has been created in
            the database

        """
        new_val = False
        if self.running:
            full_path = node_path + '/' + value_name
            index = self._entry_index_map[full_path]
            with self._lock:
                self._flat_database[index] = value
                self.notifier((node_path + '/' + value_name, value))
        else:
            node = self.go_to_path(node_path)
            if value_name not in node.data:
                new_val = True
            node.data[value_name] = value
            if new_val:
                self.notifier(('added', node_path + '/' + value_name, value))

        return new_val

    def get_value(self, assumed_path, value_name):
        """Method to get a value from the database from its name and a path

        This method returns the value stored under the specified name. It
        starts looking at the specified path and if necessary goes up in the
        hierarchy.

        Parameters
        ----------
        assumed_path : unicode
            Path where we start looking for the entry

        value_name : unicode
            Name of the value we are looking for

        Returns
        -------
        value : object
            Value stored under the entry value_name

        """
        if self.running:
            index = self._find_index(assumed_path, value_name)
            return self._flat_database[index]

        else:
            node = self.go_to_path(assumed_path)

            # First check if the entry is in the current node.
            if value_name in node.data:
                value = node.data[value_name]
                return value

            # Second check if there is a special rule about this entry.
            elif 'access' in node.meta and value_name in node.meta['access']:
                path = assumed_path + '/' + node.meta['access'][value_name]
                return self.get_value(path, value_name)

            # Finally go one step up in the node hierarchy.
            else:
                new_assumed_path = assumed_path.rpartition('/')[0]
                if assumed_path == new_assumed_path:
                    mes = "Can't find database entry : {}".format(value_name)
                    raise KeyError(mes)
                return self.get_value(new_assumed_path, value_name)

    def rename_values(self, node_path, old, new, access_exs=None):
        """Rename database entries.

        This method can update the access exceptions attached to them.
        This method cannot be used in running mode.

        Parameters
        ----------
        node_path : unicode
            Path to the node holding the value.

        old : iterable
            Old names of the values.

        new : iterable
            New names of the values.

        access_exs : iterable, optional
            Dict mapping old entries names to how far the access exception is
            located.

        """
        if self.running:
            raise RuntimeError('Cannot delete an entry in running mode')

        node = self.go_to_path(node_path)
        notif = []
        acc_notif = []
        access_exs = access_exs if access_exs else {}

        for i, old_name in enumerate(old):
            if old_name in node.data:
                val = node.data.pop(old_name)
                node.data[new[i]] = val
                notif.append(('renamed', node_path + '/' + old_name,
                              node_path + '/' + new[i], val))
                if old_name in access_exs:
                    count = access_exs[old_name]
                    n = node
                    p = node_path
                    while count:
                        n = n.parent if n.parent else n
                        p, _ = p.rsplit('/', 1)
                        count -= 1
                    path = n.meta['access'].pop(old_name)
                    n.meta['access'][new[i]] = path
                    acc_notif.append(('renamed', p, path, old_name, new[i]))
            else:
                err_str = 'No entry {} in node {}'.format(old_name, node_path)
                raise KeyError(err_str)

        # Avoid sending spurious notifications
        if notif:
            self.notifier(notif)
        if acc_notif:
            self.access_notifier(acc_notif)

    def delete_value(self, node_path, value_name):
        """Remove an entry from the specified node

        This method remove the specified entry from the specified node. It does
        not handle removing the access exceptions attached to it. This
        method cannot be used in running mode.

        Parameters
        ----------
        assumed_path : unicode
            Path where we start looking for the entry

        value_name : unicode
            Name of the value we are looking for

        """
        if self.running:
            raise RuntimeError('Cannot delete an entry in running mode')

        else:
            node = self.go_to_path(node_path)

            if value_name in node.data:
                del node.data[value_name]
                self.notifier(('removed', node_path + '/' + value_name))
            else:
                err_str = 'No entry {} in node {}'.format(
                    value_name, node_path)
                raise KeyError(err_str)

    def get_values_by_index(self, indexes, prefix=None):
        """Access to a list of values using the flat database.

        Parameters
        ----------
        indexes : list(int)
            List of index for which values should be returned.

        prefix : unicode, optional
            If provided return the values in dict with key of the form :
            prefix + index.

        Returns
        -------
        values : list or dict
            List of requested values in the same order as indexes or dict if
            prefix was not None.

        """
        if prefix is None:
            return [self._flat_database[i] for i in indexes]
        else:
            return {prefix + str(i): self._flat_database[i] for i in indexes}

    def get_entries_indexes(self, assumed_path, entries):
        """ Access to the index in the flattened database for some entries.

        Parameters
        ----------
        assumed_path : unicode
            Path to the node in which the values are assumed to be stored.

        entries : iterable(unicode)
            Names of the entries for which the indexes should be returned.

        Returns
        -------
        indexes : dict
            Dict mapping the entries names to their index in the flattened
            database.

        """
        return {name: self._find_index(assumed_path, name) for name in entries}

    def list_accessible_entries(self, node_path):
        """Method used to get a list of all entries accessible from a node.

        DO NOT USE THIS METHOD IN RUNNING MODE (ie never in the check method
        of a task, use a try except clause instead and get_value or
        get_entries_indexes).

        Parameters
        ----------
        node_path : unicode
            Path to the node from which accessible entries should be listed.

        Returns
        -------
        entries_list : list(unicode)
            List of entries accessible from the specified node

        """
        entries = []
        while True:
            node = self.go_to_path(node_path)
            keys = node.data.keys()
            # Looking for the entries in the node.
            for key in keys:
                if not isinstance(node.data[key], DatabaseNode):
                    entries.append(key)

            # Adding the special access if they are not already in the list.
            for entry in node.meta.get('access', []):
                if entry not in entries:
                    entries.append(entry)

            if node_path != 'root':
                # Going to the next node.
                node_path = node_path.rpartition('/')[0]
            else:
                break

        for entry in self.excluded:
            if entry in entries:
                entries.remove(entry)

        return sorted(entries)

    def list_all_entries(self, path='root', values=False):
        """List all entries in the database.

        Parameters
        ----------
        path : unicode, optional
            Starting node. This parameters is for internal use only.

        values : bool, optional
            Whether or not to return the values associated with the entries.

        Returns
        -------
        paths : list(unicode) or dict if values
            List of all accessible entries with their full path.

        """
        entries = [] if not values else {}
        node = self.go_to_path(path)
        for entry in node.data.keys():
            if isinstance(node.data[entry], DatabaseNode):
                aux = self.list_all_entries(path=path + '/' + entry,
                                            values=values)
                if not values:
                    entries.extend(aux)
                else:
                    entries.update(aux)
            else:
                if not values:
                    entries.append(path + '/' + entry)
                else:
                    entries[path + '/' + entry] = node.data[entry]

        if path == 'root':
            for entry in self.excluded:
                aux = path + '/' + entry
                if aux in entries:
                    if not values:
                        entries.remove(aux)
                    else:
                        del entries[aux]

        return sorted(entries) if not values else entries

    def add_access_exception(self, node_path, entry_node, entry):
        """Add an access exception in a node for an entry located in a node
        below.

        Parameters
        ----------
        node_path : unicode
            Path to the node which should hold the exception.

        entry_node : unicode
            Absolute path to the node holding the entry.

        entry : unicode
            Name of the entry for which to create an exception.

        """
        node = self.go_to_path(node_path)
        rel_path = entry_node[len(node_path) + 1:]
        if 'access' in node.meta:
            access_exceptions = node.meta['access']
            access_exceptions[entry] = rel_path
        else:
            node.meta['access'] = {entry: rel_path}
        self.access_notifier(('added', node_path, rel_path, entry))

    def remove_access_exception(self, node_path, entry=None):
        """Remove an access exception from a node for a given entry.

        Parameters
        ----------
        node_path : unicode
            Path to the node holding the exception.

        entry : unicode, optional
            Name of the entry for which to remove the exception, if not
            provided all access exceptions will be removed.

        """
        node = self.go_to_path(node_path)
        if entry:
            access_exceptions = node.meta['access']
            relative_path = access_exceptions[entry]
            del access_exceptions[entry]
        else:
            relative_path = ''
            del node.meta['access']
        self.access_notifier(('removed', node_path, relative_path, entry))

    def create_node(self, parent_path, node_name):
        """Method used to create a new node in the database

        This method creates a new node in the database at the specified path.
        This method is not thread safe safe as the hierarchy of the tasks'
        database is not supposed to change during a measurement but only during
        the configuration phase

        Parameters
        ----------
        parent_path : unicode
            Path to the node parent of the new one

        node_name : unicode
            Name of the new node to create

        """
        if self.running:
            raise RuntimeError('Cannot create a node in running mode')

        parent_node = self.go_to_path(parent_path)
        node = DatabaseNode(parent=parent_node)
        parent_node.data[node_name] = node
        self.nodes_notifier(('added', parent_path, node_name, node))

    def rename_node(self, parent_path, old_name, new_name):
        """Method used to rename a node in the database

        Parameters
        ----------
        parent_path : unicode
            Path to the parent of the node being renamed

        old_name : unicode
            Old name of the node.

        node_name : unicode
            New name of node

        """
        if self.running:
            raise RuntimeError('Cannot rename a node in running mode')

        parent_node = self.go_to_path(parent_path)
        parent_node.data[new_name] = parent_node.data[old_name]
        del parent_node.data[old_name]

        while parent_node:
            if 'access' not in parent_node.meta:
                parent_node = parent_node.parent
                continue
            access = parent_node.meta['access'].copy()
            for k, v in access.items():
                if old_name in v:
                    new_path = v.replace(old_name, new_name)
                    parent_node.meta['access'][k] = new_path

            parent_node = parent_node.parent

        self.nodes_notifier(('renamed', parent_path, old_name, new_name))

    def delete_node(self, parent_path, node_name):
        """Method used to delete an existing node from the database

        Parameters
        ----------
        parent_path : unicode
            Path to the node parent of the new one

        node_name : unicode
            Name of the new node to create

        """
        if self.running:
            raise RuntimeError('Cannot delete a node in running mode')

        parent_node = self.go_to_path(parent_path)
        if node_name in parent_node.data:
            del parent_node.data[node_name]
        else:
            err_str = 'No node {} at the path {}'.format(
                node_name, parent_path)
            raise KeyError(err_str)

        self.nodes_notifier(('removed', parent_path, node_name))

    def copy_node_values(self, node='root'):
        """Copy the values (ie not subnodes) found in a node.

        Parameters
        ----------
        node : unicode, optional
            Path to the node to copy.

        Returns
        -------
        copy : dict
            Copy of the node values.

        """
        node = self.go_to_path(node)
        return {
            k: v
            for k, v in node.data.items() if not isinstance(v, DatabaseNode)
        }

    def prepare_to_run(self):
        """Enter a thread safe, flat database state.

        This is used when tasks are executed.

        """
        self._lock = Lock()
        self.running = True

        # Flattening the database by walking all the nodes.
        index = 0
        nodes = [('root', self._database)]
        mapping = {}
        datas = []
        for (node_path, node) in nodes:
            for key, val in node.data.items():
                path = node_path + '/' + key
                if isinstance(val, DatabaseNode):
                    nodes.append((path, val))
                else:
                    mapping[path] = index
                    index += 1
                    datas.append(val)

        # Walking a second time to add the exception to the _entry_index_map,
        # in reverse order in case an entry has multiple exceptions.
        for (node_path, node) in nodes[::-1]:
            access = node.meta.get('access', [])
            for entry in access:
                short_path = node_path + '/' + entry
                full_path = node_path + '/' + access[entry] + '/' + entry
                mapping[short_path] = mapping[full_path]

        self._flat_database = datas
        self._entry_index_map = mapping

        self._database = None

    def list_nodes(self):
        """List all the nodes present in the database.

        Returns
        -------
        nodes : dict
            Dictionary storing the nodes by path

        """
        nodes = [('root', self._database)]
        for (node_path, node) in nodes:
            for key, val in node.data.items():
                if isinstance(val, DatabaseNode):
                    path = node_path + '/' + key
                    nodes.append((path, val))

        return dict(nodes)

    def go_to_path(self, path):
        """Method used to reach a node specified by a path.

        """
        node = self._database
        if path == 'root':
            return node

        # Decompose the path in database keys
        keys = path.split('/')
        # Remove first key (ie 'root' as we are not trying to access it)
        del keys[0]

        for key in keys:
            if key in node.data:
                node = node.data[key]
            else:
                ind = keys.index(key)
                if ind == 0:
                    err_str = \
                        'Path {} is invalid, no node {} in root'.format(path,
                                                                        key)
                else:
                    err_str = 'Path {} is invalid, no node {} in node\
                        {}'.format(path, key, keys[ind - 1])
                raise KeyError(err_str)

        return node

    # =========================================================================
    # --- Private API ---------------------------------------------------------
    # =========================================================================

    #: Main container for the database.
    _database = Typed(DatabaseNode, ())

    #: Flat version of the database only used in running mode for perfomances
    #: issues.
    _flat_database = List()

    #: Dict mapping full paths to flat database indexes.
    _entry_index_map = Dict()

    #: Lock to make the database thread safe in running mode.
    _lock = Value()

    def _find_index(self, assumed_path, entry):
        """Find the index associated with a path.

        Only to be used in running mode.

        """
        path = assumed_path
        while path != 'root':
            full_path = path + '/' + entry
            if full_path in self._entry_index_map:
                return self._entry_index_map[full_path]
            path = path.rpartition('/')[0]

        full_path = path + '/' + entry
        if full_path in self._entry_index_map:
            return self._entry_index_map[full_path]

        raise KeyError("Can't find entry matching {}, {}".format(
            assumed_path, entry))
Ejemplo n.º 17
0
class AnnealerProcess(Atom):
    """An annealing process described by a series of steps.

    """
    #: User specified description of the process.
    description = Str()

    #: Path under which this process is saved.
    path = Str()

    #: Steps describing the annealing process.
    steps = List(BaseStep, [])

    #: Current status of the process.
    status = Enum('Inactive', 'Started', 'Running', 'Completed', 'Stopping',
                  'Stopped', 'Failed')

    def save(self, path=None):
        """Save the process to a json file by serializing the steps.

        """
        path = path or self.path
        config = dict(steps=[], description=self.description)
        for s in self.steps:
            s_config = s.get_preferences_from_members()
            s_config['type'] = s.__class__.__name__
            config['steps'].append(s_config)

        with open(path, 'w') as f:
            json.dump(config, f)

        self.path = path

    @classmethod
    def load(cls, path):
        """Load a process stored in a JSON file.

        """
        with open(path) as f:
            config = json.load(f)

        steps = []
        for c in config["steps"]:
            step_cls = STEPS[c.pop('type')]
            steps.append(step_cls(**c))

        return cls(description=config['description'], path=path, steps=steps)

    def add_step(self, index, step):
        """Add a step at a given index in the process.

        If index is None the step is appended instead.

        """
        steps = self.steps[:]
        if index is None or index >= len(self.steps):
            steps.append(step)
        else:
            steps.insert(index, step)
        self.steps = steps

    def move_step(self, old_index, new_index):
        """Move a step between two positions.

        """
        steps = self.steps[:]
        step = steps.pop(old_index)
        steps.insert(new_index, step)

        # Force a UI notification
        self.steps = []
        self.steps = steps

    def remove_step(self, index):
        """Remove a step at a given index.

        """
        steps = self.steps[:]
        del steps[index]
        self.steps = steps

    def start(self, app_state):
        """Start the process execution.

        """
        queue = Queue()
        stop_event = Event()
        crashed_event = Event()

        #: Reset the plots data
        app_state.temperature.current_index = 0
        app_state.heater_switch.current_index = 0
        app_state.heater_regulation.current_index = 0

        self._actuator = ActuatorSubprocess(self.path,
                                            app_state.get_daq_config(), queue,
                                            stop_event, crashed_event)
        self._monitoring_thread = MonitoringThread(self)
        self._polling_thread = PollingThread(app_state, queue)

        self._actuator.start()
        self.status = 'Started'
        self._monitoring_thread.start()
        self._polling_thread.start()

        app_state.start_plot_timer()

    def stop(self, force=False):
        """Stop the process.

        """
        self.status = 'Stopping'
        if force:
            self._actuator.terminate()
        else:
            self._actuator.stop_event.set()

    # --- Private API ---------------------------------------------------------

    #: Subprocess actuator repsonible for the process execution.
    _actuator = Typed(ActuatorSubprocess)

    #: Thread responsible for updating the app about the state of progress of
    #: the process.
    _polling_thread = Typed(PollingThread)

    #: Thread updating the state of the process by monitoring teh actuator.
    _monitoring_thread = Typed(MonitoringThread)
Ejemplo n.º 18
0
class QtOccViewer(QtControl, ProxyOccViewer):

    #: Viewer widget
    widget = Typed(QtViewer3d)

    #: Update count
    _update_count = Int(0)

    #: Displayed Shapes
    _displayed_shapes = Dict()
    _ais_shapes = List()

    #: Shapes
    shapes = Property(lambda self: self.get_shapes(), cached=True)

    def _get_display(self):
        return self.widget._display

    #: Display
    display = Property(_get_display, cached=True)

    def get_shapes(self):
        return [
            c for c in self.children() if not isinstance(c, QtToolkitObject)
        ]

    def create_widget(self):
        self.widget = QtViewer3d(parent=self.parent_widget())

    def init_widget(self):
        super(QtOccViewer, self).init_widget()
        d = self.declaration
        widget = self.widget

        #: Create viewer
        widget._display = Display(widget.GetHandle())
        display = widget._display
        display.Create()

        # background gradient
        self.set_background_gradient(d.background_gradient)
        self.set_trihedron_mode(d.trihedron_mode)
        self.set_display_mode(d.display_mode)
        self.set_selection_mode(d.selection_mode)
        self.set_view_mode(d.view_mode)
        self.set_antialiasing(d.antialiasing)
        self.set_lock_rotation(d.lock_rotation)
        self.set_lock_zoom(d.lock_zoom)
        self._update_raytracing_mode()

        #: Setup callbacks
        display.register_select_callback(self.on_selection)

        widget._inited = True  # dict mapping keys to functions
        widget._SetupKeyMap()  #
        display.thisown = False
        self.init_signals()

    def init_signals(self):
        d = self.declaration
        widget = self.widget
        for name in widget._callbacks.keys():
            if hasattr(d, name):
                cb = getattr(d, name)
                widget._callbacks[name].append(cb)

    def init_layout(self):
        super(QtOccViewer, self).init_layout()
        for child in self.children():
            self.child_added(child)
        display = self.display
        display.OnResize()

    def child_added(self, child):
        if not isinstance(child, QtToolkitObject):
            self.get_member('shapes').reset(self)
            child.observe('shape', self.update_display)
            self.update_display()
        else:
            super(QtOccViewer, self).child_added(child)

    def child_removed(self, child):
        if not isinstance(child, QtToolkitObject):
            self.get_member('shapes').reset(self)
            child.unobserve('shape', self.update_display)
        else:
            super(QtOccViewer, self).child_removed(child)

    # -------------------------------------------------------------------------
    # Viewer API
    # -------------------------------------------------------------------------
    def get_bounding_box(self, shapes):
        """ Compute the bounding box for the given list of shapes. Return values
        are in 3d coordinate space.
        
        Parameters
        ----------
        shapes: List
            A list of TopoDS_Shape to compute a bbox for
        
        Returns
        -------
        bbox: Tuple
            A tuple of (xmin, ymin, zmin, xmax, ymax, zmax).
        
        """
        bbox = Bnd_Box()
        for shape in shapes:
            brepbndlib_Add(shape, bbox)
        return bbox.Get()

    def get_screen_coordinate(self, point):
        """ Convert a 3d coordinate to a 2d screen coordinate
        
        Parameters
        ----------
        (x, y, z): Tuple
            A 3d coordinate
        """
        return self.display.View.Convert(*point)

    def set_antialiasing(self, enabled):
        if enabled:
            self.display.EnableAntiAliasing()
        else:
            self.display.DisableAntiAliasing()

    def set_shadows(self, enabled):
        self._update_raytracing_mode()

    def set_reflections(self, enabled):
        self._update_raytracing_mode()

    def _update_raytracing_mode(self):
        d = self.declaration
        display = self.display
        if not hasattr(display.View, 'SetRaytracingMode'):
            return
        if d.shadows or d.reflections:
            display.View.SetRaytracingMode()
            if d.shadows:
                display.View.EnableRaytracedShadows()
            if d.reflections:
                display.View.EnableRaytracedReflections()
            if d.antialiasing:
                display.View.EnableRaytracedAntialiasing()
        else:
            display.View.DisableRaytracingMode()

    def set_background_gradient(self, gradient):
        self.display.set_bg_gradient_color(*gradient)

    def set_trihedron_mode(self, mode):
        attr = 'Aspect_TOTP_{}'.format(mode.upper().replace("-", "_"))
        position = getattr(Aspect, attr)
        self.display.View.TriedronDisplay(position, Quantity_NOC_BLACK, 0.1,
                                          V3d.V3d_ZBUFFER)
        if self.widget._inited:
            self.display.Context.UpdateCurrentViewer()

    def set_selection_mode(self, mode):
        """ Call SetSelectionMode<mode> on the display. """
        attr = 'SetSelectionMode{}'.format(mode.title())
        handler = getattr(self.display, attr, None)
        if handler is not None:
            handler()

    def set_display_mode(self, mode):
        if mode == 'shaded':
            self.display.SetModeShaded()
        elif mode == 'hlr':
            self.display.SetModeHLR()
        elif mode == 'wireframe':
            self.display.SetModeWireFrame()

    def set_view_mode(self, mode):
        """ Call View_<mode> on the display and refit as needed. """
        attr = 'View_{}'.format(mode.title())
        handler = getattr(self.display, attr, None)
        if handler is not None:
            handler()
        self.display.FitAll()

    def set_lock_rotation(self, locked):
        self.widget._lock_rotation = locked

    def set_lock_zoom(self, locked):
        self.widget._lock_zoom = locked

    def zoom_factor(self, factor):
        self.display.ZoomFactor(factor)

    def fit_all(self):
        self.display.FitAll()

    def fit_selection(self):
        if not self.display.selected_shapes:
            return

        # Compute bounding box of the selection
        bbox = self.get_bounding_box(self.display.selected_shapes)
        xmin, ymin = self.get_screen_coordinate(bbox[0:3])
        xmax, ymax = self.get_screen_coordinate(bbox[3:6])
        cx, cy = int(xmin + (xmax - xmin) / 2), int(ymin + (ymax - ymin) / 2)
        self.display.MoveTo(cx, cy)
        pad = 20
        self.display.ZoomArea(xmin - pad, ymin - pad, xmax + pad, ymax + pad)

    def take_screenshot(self, filename):
        return self.display.View.Dump(filename)

    # -------------------------------------------------------------------------
    # Display Handling
    # -------------------------------------------------------------------------
    def on_selection(self, selection, *args, **kwargs):
        d = self.declaration
        selection = []
        for shape in self.display.selected_shapes:
            if shape in self._displayed_shapes:
                selection.append(self._displayed_shapes[shape].declaration)
            else:
                log.warn("shape {} not in {}".format(shape,
                                                     self._displayed_shapes))
        #d.selection = selection
        d.selection(
            ViewerSelectionEvent(selection=selection,
                                 parameters=args,
                                 options=kwargs))


#     def _queue_update(self,change):
#         self._update_count +=1
#         timed_call(0,self._check_update,change)
#
#     def _dequeue_update(self,change):
#         # Only update when all changes are done
#         self._update_count -=1
#         if self._update_count !=0:
#             return
#         self.update_shape(change)

    def update_display(self, change=None):
        """ Queue an update request """
        self._update_count += 1
        timed_call(10, self._do_update)

    def clear_display(self):
        display = self.display
        # Erase all just hides them
        display.Context.PurgeDisplay()
        display.Context.RemoveAll()

    def _expand_shapes(self, shapes):
        expansion = []
        for s in shapes:
            for c in s.children():
                if isinstance(c, OccPart):
                    expansion.extend(self._expand_shapes(c.shapes))
            if hasattr(s, 'shapes'):
                expansion.extend(self._expand_shapes(s.shapes))
            else:
                expansion.append(s)
        return expansion

    def _do_update(self):
        # Only update when all changes are done
        self._update_count -= 1
        if self._update_count != 0:
            return
        #: TO
        try:
            display = self.display

            self.clear_display()
            displayed_shapes = {}
            ais_shapes = []
            #log.debug("_do_update {}")

            #: Expand all parts otherwise we lose the material information
            shapes = self._expand_shapes(self.shapes[:])

            for shape in shapes:
                d = shape.declaration
                if not shape.shape:
                    log.error("{} has no shape property!".format(shape))
                    continue

                try:
                    if isinstance(shape.shape, BRepBuilderAPI_MakeShape):
                        s = shape.shape.Shape()
                    else:
                        s = shape.shape
                except:
                    log.error("{} failed to create shape: {}".format(
                        shape, traceback.format_exc()))
                    continue

                displayed_shapes[s] = shape

                #: If a material is given
                material = getattr(
                    Graphic3d, 'Graphic3d_NOM_{}'.format(
                        d.material.upper())) if d.material else None

                #: If last shape
                update = shape == shapes[-1]

                ais_shapes.append(
                    display.DisplayShape(s,
                                         color=d.color,
                                         material=material,
                                         transparency=d.transparency,
                                         update=update,
                                         fit=not self._displayed_shapes))

            self._ais_shapes = ais_shapes
            self._displayed_shapes = displayed_shapes

            # Update bounding box
            # TODO: Is there an API for this?
            bbox = self.get_bounding_box(displayed_shapes.keys())
            self.declaration.bbox = BBox(*bbox)
        except:
            log.error("Failed to display shapes: {}".format(
                traceback.format_exc()))
Ejemplo n.º 19
0
class MuxerModel(Atom):
    """Class that defines the Model for the data muxer

    Attributes
    ----------
    data_muxer : dataportal.muxer.api.DataMuxer
        The data_muxer holds the non-time-aligned data.  Upon asking the data_muxer
        to reformat its data into time-aligned bins, a dataframe is returned
    run_header: metadatastore.api.Document
        The bucket of information from the data broker that contains all
        non-data information

    column_models : atom.dict.Dict
        Dictionary that is analogous to the col_info property of the
        dataportal.muxer.data.DataMuxer object
    scalar_columns : atom.list.List
        The list of columns names whose cells contain 0-D arrays (single values)
    line_columns : atom.list.List
        The list of column names whose cells contain 1-D arrays
    image_columns : atom.list.List
        The list of column names whose cells contain 2-D arrays
    volume_columns : atom.list.List
        The list of column names whos cells contain 3-D arrays

    scalar_columns_visible : atom.scalars.Bool
        Instructs the GUI to show/hide the scalar info
    line_columns_visible : atom.scalars.Bool
        Instructs the GUI to show/hide the line info
    image_columns_visible : atom.scalars.Bool
        Instructs the GUI to show/hide the image info
    volume_columns_visible : atom.scalars.Bool
        Instructs the GUI to show/hide the volume info

    info : atom.scalars.Str
        A short string describing the `data_muxer` attribute of the Atom
        MuxerModel

    new_data_callbacks : atom.list.List
        List of callbacks that care when the data_muxer gets new data.
        Callback functions should expect no information to be passed.

    auto_updating : atom.Bool
        Is the databroker going to be regularly asked for data?
        True -> yes. False -> no

    update_rate : atom.Int
        The rate at which the databroker will be asked for new data
    """
    column_models = Dict()
    scalar_columns = List(item=ColumnModel)
    line_columns = List(item=ColumnModel)
    image_columns = List(item=ColumnModel)
    volume_columns = List(item=ColumnModel)

    scalar_columns_visible = Bool(False)
    line_columns_visible = Bool(False)
    image_columns_visible = Bool(False)
    volume_columns_visible = Bool(False)

    data_muxer = Typed(DataMuxer)
    header = Typed(Document)
    info = Str()

    new_data_callbacks = List()

    auto_updating = Bool(False)

    update_rate = Int(1000)  # in ms

    def __init__(self):
        # initialize everything to be the equivalent of None. It would seem
        # that the first accessing of an Atom instance attribute causes the
        # creation of that attribute, thus triggering the @observe decorator.
        with self.suppress_notifications():
            self.column_models = {}
            self.scalar_columns = []
            self.line_columns = []
            self.image_columns = []
            self.volume_columns = []
            self.data_muxer = None
            self.header = None
            self.info = 'No run header received yet'
            self.new_data_callbacks = []

    @observe('header')
    def run_header_changed(self, changed):
        print('Run header has been changed, creating a new data_muxer')
        self.info = 'Run {}'.format(self.header.scan_id)
        with self.suppress_notifications():
            self.data_muxer = None
        self.get_new_data()

    def new_run_header(self, changed):
        """Observer function for the `header` attribute of the SearchModels
        """
        self.header = changed['value']

    def get_new_data(self):
        """Hit the dataportal to first see if there is new data and, if so,
        grab it
        """
        print('getting new data from the data broker')
        events = get_events(self.header)
        if self.data_muxer is None:
            # this will automatically trigger the key updating
            self.data_muxer = DataMuxer.from_events(events)
        else:
            self.data_muxer.append_events(events)
            for data_cb in self.new_data_callbacks:
                data_cb()
            # update the column information
            self._verify_column_info()
            for data_cb in self.new_data_callbacks:
                data_cb()

    @observe('data_muxer')
    def new_muxer(self, changed):
        # data_muxer object has been changed. Remake the columns
        print('new data muxer received')
        self._verify_column_info()

    def _verify_column_info(self):
        print('verifying column information')
        updated_cols = []
        for col_name, col_model in self.column_models.items():
            muxer_col_info = self.data_muxer.col_info.get(col_name, None)
            if muxer_col_info:
                # if the values are the same, no magic updates happen, otherwise
                # the UI gets magically updated
                col_model.dim = muxer_col_info.ndim
                col_model.name = muxer_col_info.name
                col_model.upsample = muxer_col_info.upsample
                col_model.downsample = muxer_col_info.downsample
                col_model.shape = muxer_col_info.shape
                col_model.data_muxer = self.data_muxer
                updated_cols.append(col_name)
            else:
                # remove the column model
                self.column_models.pop(col_name)
        for col_name, col_info in self.data_muxer.col_info.items():
            if col_name in updated_cols:
                # column has already been accounted for, move on to the next one
                continue
            # insert a new column model
            print(col_info)
            self.column_models[col_name] = ColumnModel(
                data_muxer=self.data_muxer,
                dim=col_info.ndim,
                name=col_name,
                shape=col_info.shape)
        self._update_column_sortings()

    def _update_column_sortings(self):
        print('updating column sortings')
        mapping = {0: set(), 1: set(), 2: set(), 3: set()}
        for col_name, col_model in self.column_models.items():
            mapping[col_model.dim].add(col_model)

        # update the column key lists, if necessary
        self.scalar_columns = []
        self.line_columns = []
        self.image_columns = []
        self.volume_columns = []

        self.scalar_columns = list(mapping[0])
        self.line_columns = list(mapping[1])
        self.image_columns = list(mapping[2])
        self.volume_columns = list(mapping[3])

        # set the GUI elements to be visible/hidden if there are/aren't any
        # column_models
        self.scalar_columns_visible = len(self.scalar_columns) != 0
        self.line_columns_visible = len(self.line_columns) != 0
        self.image_columns_visible = len(self.image_columns) != 0
        self.volume_columns_visible = len(self.volume_columns) != 0
Ejemplo n.º 20
0
class ScalarCollection(Atom):
    """

    ScalarCollection is a bundle of ScalarModels. The ScalarCollection has an
    instance of a DataMuxer which notifies it of new data which then updates
    its ScalarModels. When instantiated, the data_muxer instance is asked
    for the names of its columns.  All columns which represent scalar values
    are then shoved into ScalarModels and the ScalarCollection manages the
    ScalarModels.

    Attributes
    ----------
    data_muxer : replay.pipeline.pipeline.DataMuxer
        The data manager backing the ScalarModel. The DataMuxer's new_data
        signal is connected to the notify_new_data function of the ScalarModel
        so that the ScalarModel can decide what to do when the DataMuxer
        receives new data.
    scalar_models : atom.Dict
        The collection of scalar_models that the ScalarCollection knows about
    data_cols : atom.List
        The names of the data sets that are in the DataMuxer
    redraw_every : atom.Float
        The frequency with which to redraw the plot. The meaning of this
        parameter changes based on `redraw_type`
    redraw_type : {'max rate', 's'}
        Gives meaning to the float stored in `redraw_every`. Should be read as
        'Update the plot at a rate of `redraw_every` per `redraw_type`'. Since
        there are only the two options in `ScalarCollection`, it should be
        understood that the previous statement is only relevant when 's' is
        selected as the `redraw_type`. If `max_rate` is selected, then the plot
        will attempt to update itself as fast as data is coming in. Beware that
        this may cause significant performance issues if your data rate is
        > 20 Hz
    update_rate : atom.Str
        Formatted rate that new data is coming in.
    x : atom.Str
        The name of the x-axis that the `scalar_models` should be plotted
        against
    """
    # dictionary of lines that can be toggled on and off
    scalar_models = Dict(key=Str(), value=ScalarModel)
    # the thing that holds all the data
    data_muxer = Typed(
        DataMuxer
    )  # the Document that holds all non-data associated with the data_muxer
    header = Typed(Document)
    # The scan id of this data set
    scan_id = Int()
    # name of the x axis
    x = Str()
    # index of x in data_cols
    # x_index = data_cols.index(x)
    x_index = Int()

    # name of the column to align against
    bin_on = Str()
    x_is_time = Bool(False)
    # name of all columns that the data muxer knows about
    data_cols = List()
    derived_cols = List()

    # should the pandas dataframe plotting use subplots for each column
    single_plot = Bool(False)
    # shape of the subplots
    ncols = Range(low=0)
    nrows = Range(low=0)  # 0 here

    # ESTIMATING
    # the current set of data to perform peak estimates for
    estimate_target = Str()
    # the result of the estimates, stored as a dictionary
    # The list of peak parameters to plot
    estimate_stats = Typed(OrderedDict)
    estimate_plot = List()
    # the index of the data set to perform estimates for
    # estimate_index = data_cols.index(estimate_target)
    estimate_index = Int()

    # NORMALIZING
    normalize_target = Str()
    # should the data be normalized?
    normalize = Bool(False)

    # MPL PLOTTING STUFF
    _fig = Typed(Figure)
    _ax = Typed(Axes)
    # configuration properties for the 1-D plot
    _conf = Typed(ScalarConfig)

    # CONTROL OF THE PLOT UPDATE SPEED
    redraw_every = Float(default=1)
    redraw_type = Enum('max rate', 's')
    update_rate = Str()
    # the last time that the plot was updated
    _last_update_time = Typed(datetime)
    # the last frame that the plot was updated
    _last_update_frame = Int()
    # the number of times that `notify_new_data` has been called since the last
    # update
    _num_updates = Int()

    def __init__(self):
        with self.suppress_notifications():
            super(ScalarCollection, self).__init__()
            # plotting initialization
            self._fig = Figure(figsize=(1, 1))
            self._fig.set_tight_layout(True)
            self._ax = self._fig.add_subplot(111)
            self._conf = ScalarConfig(self._ax)
            self.redraw_type = 's'
            self.estimate_plot = ['cen', 'x_at_max']
            self.estimate_stats = OrderedDict()

    def init_scalar_models(self):
        self.scalar_models.clear()
        line_artist, = self._ax.plot([], [], label=nodata_str)
        self.scalar_models[nodata_str] = ScalarModel(line_artist=line_artist,
                                                     name=nodata_str,
                                                     is_plotting=True)

    def new_data_muxer(self, changed):
        """Function to be registered with a MugglerModel on its `muxer`
        attribute

        Parameters
        ----------
        changed : dict
            Changed is emitted by Atom and has the following keys:
            {'value', 'object', 'type', 'name', 'oldvalue'}
        """
        print('new_data_muxer callback function triggered in '
              'ScalarCollection')
        self.data_muxer = changed['value']

    @observe('data_muxer')
    def update_datamuxer(self, changed):
        print('data muxer update triggered')
        with self.suppress_notifications():
            if self.data_muxer is None:
                self.data_cols = [nodata_str]
                self.x = nodata_str
                self.estimate_target = self.x
                self.estimate_index = self.data_cols.index(self.x)
                self.normalize_target = self.x
                self.bin_on = self.x
                self.init_scalar_models()
                return
            # get the column names with dimensionality equal to zero
            # print('data muxer col info by ndim: {}'.format(self.data_muxer.col_info_by_ndim))
            data_cols = [
                col_info.name
                for col_info in self.data_muxer.col_info_by_ndim[0]
            ]
            derived_cols = []
            # init the x axis to be the first column name
            x = data_cols[0]
            # default to time
            x_is_time = True
            estimate_target = x
            estimate_index = data_cols.index(estimate_target)
            # don't bin by any of the columns by default and plot by time
            bin_on = x
            # blow away scalar models
            self.scalar_models.clear()
            self._ax.cla()
            for name in data_cols:
                # create a new line artist and scalar model
                line_artist, = self._ax.plot([], [], label=name, marker='D')
                self.scalar_models[name] = ScalarModel(line_artist=line_artist,
                                                       name=name,
                                                       is_plotting=True)
            # add the estimate
            name = 'peak stats'
            line_artist, = self._ax.plot([], [],
                                         'ro',
                                         label=name,
                                         markersize=15)
            self.scalar_models[name] = ScalarModel(line_artist=line_artist,
                                                   name=name,
                                                   is_plotting=True)

            for model in self.scalar_models.values():
                model.observe('is_plotting', self.reformat_view)
            derived_cols.append(name)

        # trigger the updates
        # print('data_cols', data_cols)
        # for model_name, model in six.iteritems(self.scalar_models):
        #     print(model.state)
        self.derived_cols = []
        self.derived_cols = derived_cols
        self.data_cols = []
        self.data_cols = data_cols
        self.x_is_time = x_is_time
        self._last_update_time = datetime.utcnow()
        self.bin_on = bin_on
        self.x = x
        self.estimate_target = x
        self.estimate_index = estimate_index
        self.normalize_target = x
        self.set_state()
        self.get_new_data_and_plot()

    @observe('data_cols')
    def update_col_names(self, changed):
        print('data_cols changed: {}'.format(self.data_cols))

    @observe('x_is_time')
    def update_x_axis(self, changed):
        lbl = 'Time (s)'
        if not changed['value']:
            lbl = self.x
        self._conf.xlabel = lbl
        self.get_new_data_and_plot()

    @observe('x')
    def update_x(self, changed):
        print('x updated: {}'.format(self.x))
        self._conf.xlabel = self.x
        self.get_new_data_and_plot()
        self.x_index = self.data_cols.index(self.x)

    @observe('alignment_col')
    def update_alignment_col(self, changed):
        # check with the muxer for the columns that can be plotted against
        sliceable = self.data_muxer.align_against(self.bin_on)
        for name, scalar_model in six.iteritems(self.scalar_models):
            if name == 'fit' or name == 'peak stats':
                continue
            if not sliceable[name]:
                # turn off the plotting and disable the check box
                scalar_model.is_plotting = False
                scalar_model.can_plot = False
            else:
                # enable the check box but don't turn on the plotting
                scalar_model.can_plot = True
        self.get_new_data_and_plot()

    @observe('estimate_plot')
    def update_estimate(self, changed):
        self.reformat_view()

    @observe('estimate_target')
    def update_estimate_target(self, changed):
        self.estimate_index = self.data_cols.index(self.estimate_target)

    @observe('normalize')
    def update_normalize(self, changed):
        self.get_new_data_and_plot()

    def set_state(self):
        plotx = getattr(self.header, 'plotx', None)
        ploty = getattr(self.header, 'ploty', None)

        if plotx:
            self.x = plotx

        print('plotx: {}'.format(plotx))
        print('ploty: {}'.format(ploty))

        for name, model in self.scalar_models.items():
            print(name, model, 'name in ploty: ', name in ploty)
            self.scalar_models[name].is_plotting = name in ploty

        if ploty:
            self.x_is_time = False

    def header_changed(self, changed):
        """Callback that should be connected to whatever Atom instance has a
        reference to a header. This callback will parse the header for any
        interesting bits of information that modifies the state of replay

        Parameters
        ----------
        changed : dict
            The dict that gets emitted when the header attribute of an Atom
            class is changed

        Notes
        -----
        Known Keys
        plotx : str
            The column that should be on the x-axis
        ploty : list
            The columns that should be shown on the y-axis
        """
        value = changed['value']
        print('header: {}'.format(value))
        print('vars(header): {}'.format(vars(value)))
        self.header = value

    def print_state(self):
        """Print the, uh, state
        """
        for model_name, model in six.iteritems(self.scalar_models):
            print(model.get_state())

    def notify_new_column(self, new_columns):
        """Function to call when there is a new column in the data muxer

        Parameters
        ----------
        new_columns: list
            The new column name that the data muxer knows about
        """
        scalar_cols = self.data_muxer.keys(dim=0)
        alignable = self.data_muxer.align_against(self.bin_on, self.data_cols)
        for name, is_plottable in six.iteritems(alignable):
            if name in new_columns and not self.data_muxer.col_dims[name]:
                line_artist, = self._ax.plot([], [], label=name)
                self.scalar_models[name] = ScalarModel(line_artist=line_artist,
                                                       name=name)
                self.scalar_models[name].can_plot = is_plottable

    def format_number(self, number):
        return '{:.5f}'.format(number)

    def estimate(self):
        """Return a dictionary of the vital stats of a 'peak'"""
        stats = OrderedDict()
        print('self.fit_target: {}'.format(self.fit_target))
        print('self.alignment_col: {}'.format(self.bin_on))
        print('self.x: {}'.format(self.x))
        other_cols = [self.x, self.estimate_target]
        if self.normalize:
            other_cols.append(self.normalize_target)
        print('other_cols: {}'.format(other_cols))
        time, data = self.data_muxer.get_values(ref_col=self.bin_on,
                                                other_cols=other_cols)
        x = np.asarray(data[self.x])

        y = np.asarray(data[self.estimate_target])

        if self.normalize:
            y = y / np.asarray(data[self.normalize_target])

        # print('x, len(x): {}, {}'.format(x, len(x)))
        # print('y, len(y): {}, {}'.format(y, len(y)))

        fn = lambda num: '{:.5}'.format(num)
        # Center of peak
        stats['ymin'] = y.min()
        stats['ymax'] = y.max()
        stats['avg_y'] = fn(np.average(y))
        stats['x_at_ymin'] = x[y.argmin()]
        stats['x_at_ymax'] = x[y.argmax()]

        # Calculate CEN from derivative
        zero_cross = np.where(
            np.diff(np.sign(y - (stats['ymax'] + stats['ymin']) / 2)))[0]
        if zero_cross.size == 2:
            stats['cen'] = (fn(x[zero_cross].sum() / 2),
                            fn((stats['ymax'] + stats['ymin']) / 2))
        elif zero_cross.size == 1:
            stats['cen'] = x[zero_cross[0]]
        if zero_cross.size == 2:
            fwhm = x[zero_cross]
            stats['width'] = fwhm[1] - fwhm[0]
            stats['fwhm_left'] = (fn(fwhm[0]), fn(y[zero_cross[0]]))
            stats['fwhm_right'] = (fn(fwhm[1]), fn(y[zero_cross[1]]))

        # Center of mass
        stats['center_of_mass'] = fn((x * y).sum() / y.sum())
        #
        #
        # extra_models = []
        # for model_name, model in six.iteritems(self.scalar_models):
        #     if model_name in self.estimate_stats:
        #         line_artist = self.scalar_models[model_name].line_artist
        #         self._ax.lines.remove(line_artist)
        #         line_artist.remove()
        #         del line_artist
        #         print(line_artist)
        #         model = self.scalar_models.pop(model_name)
        #         model = None
        #
        # # create new line artists
        # for name, val in six.iteritems(stats):
        #     line_artist, = self._ax.axvline(label=name, color = 'r',
        #                                     linewidth=2,
        #                                     x=val)
        #     self.scalar_models[name] = ScalarModel(line_artist=line_artist,
        #                                            name=name,
        #                                            can_plot=True,
        #                                            is_plotting=False)
        # self.data_cols = []
        # self.data_cols = list(six.iterkeys(self.scalar_models))

        # trigger the automatic update of the GUI
        self.estimate_stats = OrderedDict()
        self.estimate_stats = stats

    def notify_new_data(self):
        """ Function to call when there is new data in the data muxer

        Parameters
        ----------
        new_data : list
            List of names of updated columns from the data muxer
        """

        # self._num_updates += 1
        # redraw = False
        # if self.redraw_type == 's':
        #     if ((datetime.utcnow() - self._last_update_time).total_seconds()
        #             >= self.redraw_every):
        #         redraw = True
        #     else:
        #         # do nothing
        #         pass
        # elif self.redraw_type == 'max rate':
        #     redraw = True
        # if self.bin_on in new_data:
        #     # update all the data in the line plot
        #     y_names = list(self.scalar_models)
        # else:
        #     # find out which new_data keys overlap with the data that is
        #     # supposed to be shown on the plot
        #     y_names = []
        #     for model_name, model in six.iteritems(self.scalar_models):
        #         if model.is_plotting and model.name in new_data:
        #             y_names.append(model.name)
        # if redraw:
        self.get_new_data_and_plot()
        # self.estimate()

    def get_new_data_and_plot(self, y_names=None):
        """
        Get the data from the data muxer for column `data_name` sampled
        at the time_stamps of `VariableModel.x`

        Parameters
        ----------
        data_name : list, optional
            List of the names of columns in the data muxer. If None, get all
            data from the data muxer
        """
        if self.data_muxer is None:
            return
        if self.x_is_time:
            self.plot_by_time()
        else:
            self.plot_by_x()

    def plot_by_time(self):
        df = self.data_muxer._dataframe
        data_dict = {
            data_name: {
                'x': df[data_name].index.tolist(),
                'y': df[data_name].tolist()
            }
            for data_name in df.columns if data_name in self.data_cols
        }
        self._plot(data_dict)

    def plot_by_x(self):
        interpolation = {name: 'linear' for name in self.data_cols}
        agg = {name: np.mean for name in self.data_cols}
        df = self.data_muxer.bin_on(self.x,
                                    interpolation=interpolation,
                                    agg=agg,
                                    col_names=self.data_cols)
        x_axis = df[self.x].val.values
        data_dict = {
            data_name[0]: {
                'x': x_axis,
                'y': df[data_name].tolist()
            }
            for data_name in df
        }
        self._plot(data_dict)

    def _plot(self, data_dict):
        for dname, dvals in data_dict.items():
            if dname in self.data_cols:
                self.scalar_models[dname].set_data(dvals['x'], dvals['y'])
                # self.scalar_models[dname].is_plotting = True
        self.reformat_view()

    def plot_by_x_old(self, y_names):
        # interpolation = {name: 'linear' for name in self.data_muxer.col_info.keys()}
        # agg = {name: np.mean for name in self.data_muxer.col_info.keys()}
        df = self.data_muxer.bin_on(
            self.x)  #, interpolation=interpolation, agg=agg)
        self._fig.clf()
        print('fig before df.plot call', self._fig)
        self._ax = self._fig.add_subplot(111)
        df.plot(subplots=False, ax=self._ax)
        print('fig after df.plot call', self._fig)
        return

        time, data = self.data_muxer.get_values(ref_col=self.bin_on,
                                                other_cols=other_cols)
        ref_data = data.pop(self.x)
        if self.normalize:
            norm_data = data.pop(self.normalize_target)
        # switch between x axis as data and x axis as time
        if self.x_is_data:
            ref_data_vals = ref_data.values
        else:
            ref_data_vals = time
        # print('ref_data_vals: {}'.format(ref_data_vals))

        if self.scalar_models[self.x].is_plotting:
            self.scalar_models[self.x].set_data(x=ref_data_vals, y=ref_data)
        for dname, dvals in six.iteritems(data):
            # print('{}: {}'.format(dname, id(self.scalar_models[dname])))
            if self.normalize:
                dvals = dvals / norm_data
            self.scalar_models[dname].set_data(x=ref_data_vals, y=dvals)

        # manage the fitting
        if self.fit_target is not '':
            target_data = ref_data
            if self.fit_target != self.x:
                target_data = data[self.fit_target]
            self.multi_fit_controller.set_xy(x=ref_data_vals,
                                             y=target_data.values)
        if self.multi_fit_controller.guess:
            self.multi_fit_controller.do_guess()
        if self.multi_fit_controller.autofit:
            self.multi_fit_controller.fit()
        try:
            self.scalar_models['fit'].set_data(
                x=ref_data_vals, y=self.multi_fit_controller.best_fit)
        except RuntimeError:
            # thrown when x and y are not the same length
            pass
        self.reformat_view()
        self.update_rate = "{0:.2f} s<sup>-1</sup>".format(
            float(self._num_updates) /
            (datetime.utcnow() - self._last_update_time).total_seconds())
        self._num_updates = 0
        self._last_update_time = datetime.utcnow()
        # except KeyError:
        #     pass
        # self._ax.axvline(x=self.estimate_stats['cen'], linewidth=2, color='r')
        # self._ax.axvline(x=self.estimate_stats['x_at_max'], linewidth=4,
        #                  color='k')

    def reformat_view(self, *args, **kwargs):
        """
        Recompute the limits, rescale the view, reformat the legend and redraw
        the canvas
        """
        # ignore the args and kwargs. They are here so that any function can be
        # connected to this one

        x_data = []
        y_data = []
        try:
            y_val = self.estimate_stats['avg_y']
        except KeyError:
            y_val = 1
        for plot in self.estimate_plot:
            try:
                stats = self.estimate_stats[plot]
            except KeyError:
                continue
            try:
                stats_len = len(stats)
            except TypeError:
                stats_len = 1
            if stats_len == 2:
                x_data.append(stats[0])
                y_data.append(stats[1])
            else:
                x_data.append(stats)
                y_data.append(y_val)
        try:
            self.scalar_models['peak stats'].set_data(x=x_data, y=y_data)
        except KeyError:
            # data muxer hasn't been created yet
            pass
        try:
            legend_pairs = [(v.line_artist, k)
                            for k, v in six.iteritems(self.scalar_models)
                            if v.line_artist.get_visible()]
            if legend_pairs:
                arts, labs = zip(*legend_pairs)
                self._ax.legend(arts, labs).draggable()
            else:
                self._ax.legend(legend_pairs)
            self._ax.relim(visible_only=True)
            self._ax.autoscale_view(tight=True)
            self._ax.grid(self._conf.grid)
            self._ax.set_ylabel(self._conf.ylabel)
            self._ax.set_xlabel(self._conf.xlabel)
            self._ax.set_title(self._conf.title)
            self._fig.canvas.draw()
        # self._ax.figure.canvas.draw()
        # print('current figure id: {}'.format(id(self._fig)))
        except AttributeError as ae:
            # should only happen once
            pass
Ejemplo n.º 21
0
class JDF_Assign(Atom):
    """describes a jdf assign statement. defaults to a single pattern P(1) at position 1,1"""
    assign_type=List(default=['P(1)']).tag(desc="list of assigns")
    pos_assign=List(default=[(1,1),]).tag(desc="list of tuples of position")
    shot_assign=Unicode().tag(desc="shot mod table name")
    comment=Unicode().tag(desc="comment on assign")
    short_name=Unicode().tag(desc="short name used for display in html table")

    def dup_assign(self):
        """duplicates assign as separate object using string parsing functionality"""
        dup_str, dup_comment=parse_comment(self.jdf_output)
        return JDF_Assign(tempstr=dup_str, comment=dup_comment)

    @property
    def A_nums(self):
        """all A numbers in assign_type"""
        return find_A_nums(self.assign_str)

    @property
    def P_nums(self):
        """all P numbers in assign_type"""
        return find_P_nums(self.assign_str)

    def _default_pos_assign(self):
        return [(1,1)]

    def _default_short_name(self):
        return self.comment.split(" ")[0]

    def xy_offset(self, x_start, x_step, y_start, y_step):
        return [(x_start+(p[0]-1)*x_step, y_start+(p[1]-1)*y_step) for p in self.pos_assign]

    @property
    def assign_str(self):
        """utility combination of assign_type list as string"""
        return "+".join(self.assign_type)

    @property
    def jdf_output(self):
        """produces output string for jdf"""
        pos_asgn=""
        for pos in self.pos_assign:
            pos_asgn+="({x},{y}),".format(x=pos[0], y=pos[1])
        pos_asgn=pos_asgn[:-1]
        shot_asgn=format_comment(self.shot_assign, sep=",", fmt_str="{0} {1}")
        asgn_comment=format_comment(self.comment)
        return "\tASSIGN {asgn_type} -> ({pos_asgn}{shot_asgn}) {asgn_comment}".format(
                  asgn_type=self.assign_str, pos_asgn=pos_asgn, shot_asgn=shot_asgn, asgn_comment=asgn_comment)

    def __init__(self, **kwargs):
        """Processes kwargs to allow string definition to be passes as well"""
        tempstr=kwargs.pop("tempstr", "ASSIGN P(1) -> ((1,1))")
        kwargs["comment"]=kwargs.get("comment", "")
        assign_type=kwargs.get("assign_type", tempstr.split("ASSIGN")[1].split("->")[0].strip().split("+"))
        kwargs["assign_type"]=[unicode(at) for at in assign_type]
        kwargs["pos_assign"]=kwargs.get("pos_assign", [])
        kwargs["shot_assign"]=kwargs.get("shot_assign", "")
        x_num=kwargs.pop("x_num", 1)
        y_num=kwargs.pop("y_num", 1)
        if kwargs["pos_assign"]==[]:
            for item in tempstr.split("->")[1].partition("(")[2].rpartition(")")[0].split(")"):
                if "(" in item:
                    xcor, ycor=item.split("(")[1].split(",")
                    if "-" in xcor:
                        xcor_start, xcor_end=xcor.split("-")
                        xcor_list=[int(xcor_start)+num for num in range(int(xcor_end))]
                    elif "*" in xcor:
                        xcor_list= [int(1)+num for num in range(int(x_num))]
                    else:
                        xcor_list=[int(xcor)]
                    if "-" in ycor:
                        ycor_start, ycor_end=ycor.split("-")
                        ycor_list=[int(ycor_start)+num for num in range(int(ycor_end))]
                    elif "*" in ycor:
                        ycor_list= [int(1)+num for num in range(int(y_num))]
                    else:
                        ycor_list=[int(ycor)]
                    kwargs["pos_assign"].extend([(x,y) for y in ycor_list for x in xcor_list])
                    #kwargs["pos_assign"].append((int(xcor), int(ycor)))
                elif "," in item:
                    kwargs["shot_assign"]=unicode(item.split(",")[1].strip())
        super(JDF_Assign, self).__init__(**kwargs)
Ejemplo n.º 22
0
class QtContainer(QtConstraintsWidget, ProxyContainer):
    """ A Qt implementation of an Enaml ProxyContainer.

    """
    #: A reference to the toolkit widget created by the proxy.
    widget = Typed(QContainer)

    #: A list of the contents constraints for the widget.
    contents_cns = List()

    #: Whether or not this container owns its layout. A container which
    #: does not own its layout is not responsible for laying out its
    #: children on a resize event, and will proxy the call to its owner.
    _owns_layout = Bool(True)

    #: The object which has taken ownership of the layout for this
    #: container, if any.
    _layout_owner = Value()

    #: The LayoutManager instance to use for solving the layout system
    #: for this container.
    _layout_manager = Value()

    #: The function to use for refreshing the layout on a resize event.
    _refresh = Callable(lambda *args, **kwargs: None)

    #: The table of offsets to use during a layout pass.
    _offset_table = List()

    #: The table of (index, updater) pairs to use during a layout pass.
    _layout_table = List()

    def _default_contents_cns(self):
        """ Create the contents constraints for the container.

        The contents contraints are generated by combining the user
        padding with the margins returned by 'contents_margins' method.

        Returns
        -------
        result : list
            The list of casuarius constraints for the content.

        """
        d = self.declaration
        margins = self.contents_margins()
        top, right, bottom, left = map(sum, zip(d.padding, margins))
        cns = [
            d.contents_top == (d.top + top),
            d.contents_left == (d.left + left),
            d.contents_right == (d.left + d.width - right),
            d.contents_bottom == (d.top + d.height - bottom),
        ]
        return cns

    #--------------------------------------------------------------------------
    # Initialization API
    #--------------------------------------------------------------------------
    def create_widget(self):
        """ Creates the QContainer widget.

        """
        self.widget = QContainer(self.parent_widget())

    def init_widget(self):
        """ Initialize the widget.

        """
        super(QtContainer, self).init_widget()
        self.widget.resized.connect(self.on_resized)

    def init_layout(self):
        """ Initialize the layout of the widget.

        """
        super(QtContainer, self).init_layout()
        self.init_cns_layout()

    def init_cns_layout(self):
        """ Initialize the constraints layout.

        """
        # Layout ownership can only be transferred *after* this init
        # layout method is called, since layout occurs bottom up. So,
        # we only initialize a layout manager if ownership is unlikely
        # to be transferred.
        if not self.will_transfer():
            offset_table, layout_table = self._build_layout_table()
            cns = self._generate_constraints(layout_table)
            manager = LayoutManager()
            manager.initialize(cns)
            self._offset_table = offset_table
            self._layout_table = layout_table
            self._layout_manager = manager
            self._refresh = self._build_refresher(manager)
            self._update_sizes()

    #--------------------------------------------------------------------------
    # Signal Handlers
    #--------------------------------------------------------------------------
    def on_resized(self):
        """ Update the position of the widgets in the layout.

        This makes a layout pass over the descendents if this widget
        owns the responsibility for their layout.

        """
        # The _refresh function is generated on every relayout and has
        # already taken into account whether or not the container owns
        # the layout.
        self._refresh()

    #--------------------------------------------------------------------------
    # Public Layout Handling
    #--------------------------------------------------------------------------
    def relayout(self):
        """ Rebuild the constraints layout for the widget.

        If this object does not own the layout, the call is proxied to
        the layout owner.

        """
        if self._owns_layout:
            item = self.widget_item
            old_hint = item.sizeHint()
            self.init_cns_layout()
            self._refresh()
            new_hint = item.sizeHint()
            # If the size hint constraints are empty, it indicates that
            # they were previously cleared. In this case, the layout
            # system must be notified to rebuild its constraints, even
            # if the numeric size hint hasn't changed.
            if old_hint != new_hint or not self.size_hint_cns:
                self.size_hint_updated()
        else:
            self._layout_owner.relayout()

    def replace_constraints(self, old_cns, new_cns):
        """ Replace constraints in the given layout.

        This method can be used to selectively add/remove/replace
        constraints in the layout system, when it is more efficient
        than performing a full relayout.

        Parameters
        ----------
        old_cns : list
            The list of casuarius constraints to remove from the
            the current layout system.

        new_cns : list
            The list of casuarius constraints to add to the
            current layout system.

        """
        if self._owns_layout:
            manager = self._layout_manager
            if manager is not None:
                with size_hint_guard(self):
                    manager.replace_constraints(old_cns, new_cns)
                    self._update_sizes()
                    self._refresh()
        else:
            self._layout_owner.replace_constraints(old_cns, new_cns)

    def clear_constraints(self, cns):
        """ Clear the given constraints from the current layout.

        Parameters
        ----------
        cns : list
            The list of casuarius constraints to remove from the
            current layout system.

        """
        if self._owns_layout:
            manager = self._layout_manager
            if manager is not None:
                manager.replace_constraints(cns, [])
        else:
            self._layout_owner.clear_constraints(cns)

    def contents_margins(self):
        """ Get the contents margins for the container.

        The contents margins are added to the user provided padding
        to determine the final offset from a layout box boundary to
        the corresponding content line. The default content margins
        are zero. This method can be reimplemented by subclasses to
        supply different margins.

        Returns
        -------
        result : tuple
            A tuple of 'top', 'right', 'bottom', 'left' contents
            margins to use for computing the contents constraints.

        """
        return (0, 0, 0, 0)

    def contents_margins_updated(self):
        """ Notify the system that the contents margins have changed.

        """
        old_cns = self.contents_cns
        del self.contents_cns
        new_cns = self.contents_cns
        self.replace_constraints(old_cns, new_cns)

    #--------------------------------------------------------------------------
    # Private Layout Handling
    #--------------------------------------------------------------------------
    def _layout(self):
        """ The layout callback invoked by the layout manager.

        This iterates over the layout table and calls the geometry
        updater functions.

        """
        # We explicitly don't use enumerate() to generate the running
        # index because this method is on the code path of the resize
        # event and hence called *often*. The entire code path for a
        # resize event is micro optimized and justified with profiling.
        offset_table = self._offset_table
        layout_table = self._layout_table
        running_index = 1
        for offset_index, updater in layout_table:
            dx, dy = offset_table[offset_index]
            new_offset = updater(dx, dy)
            offset_table[running_index] = new_offset
            running_index += 1

    def _update_sizes(self):
        """ Update the min/max/best sizes for the underlying widget.

        This method is called automatically at the proper times. It
        should not normally need to be called by user code.

        """
        widget = self.widget
        widget.setSizeHint(self.compute_best_size())
        widget.setMinimumSize(self.compute_min_size())
        widget.setMaximumSize(self.compute_max_size())

    def _build_refresher(self, manager):
        """ Build the refresh function for the container.

        Parameters
        ----------
        manager : LayoutManager
            The layout manager to use when refreshing the layout.

        """
        # The return function is a hyper optimized (for Python) closure
        # in order minimize the amount of work which is performed on the
        # code path of the resize event. This is explicitly not idiomatic
        # Python code. It exists purely for the sake of efficiency,
        # justified with profiling.
        mgr_layout = manager.layout
        d = self.declaration
        layout = self._layout
        width_var = d.width
        height_var = d.height
        widget = self.widget
        width = widget.width
        height = widget.height
        return lambda: mgr_layout(layout, width_var, height_var,
                                  (width(), height()))

    def _build_layout_table(self):
        """ Build the layout table for this container.

        A layout table is a pair of flat lists which hold the required
        objects for laying out the child widgets of this container.
        The flat table is built in advance (and rebuilt if and when
        the tree structure changes) so that it's not necessary to
        perform an expensive tree traversal to layout the children
        on every resize event.

        Returns
        -------
        result : (list, list)
            The offset table and layout table to use during a resize
            event.

        """
        # The offset table is a list of (dx, dy) tuples which are the
        # x, y offsets of children expressed in the coordinates of the
        # layout owner container. This owner container may be different
        # from the parent of the widget, and so the delta offset must
        # be subtracted from the computed geometry values during layout.
        # The offset table is updated during a layout pass in breadth
        # first order.
        #
        # The layout table is a flat list of (idx, updater) tuples. The
        # idx is an index into the offset table where the given child
        # can find the offset to use for its layout. The updater is a
        # callable provided by the widget which accepts the dx, dy
        # offset and will update the layout geometry of the widget.
        zero_offset = (0, 0)
        offset_table = [zero_offset]
        layout_table = []
        queue = deque((0, child) for child in self.children())

        # Micro-optimization: pre-fetch bound methods and store globals
        # as locals. This method is not on the code path of a resize
        # event, but it is on the code path of a relayout. If there
        # are many children, the queue could potentially grow large.
        push_offset = offset_table.append
        push_item = layout_table.append
        push = queue.append
        pop = queue.popleft
        QtConstraintsWidget_ = QtConstraintsWidget
        QtContainer_ = QtContainer
        isinst = isinstance

        # The queue yields the items in the tree in breadth-first order
        # starting with the immediate children of this container. If a
        # given child is a container that will share its layout, then
        # the children of that container are added to the queue to be
        # added to the layout table.
        running_index = 0
        while queue:
            offset_index, item = pop()
            if isinst(item, QtConstraintsWidget_):
                push_item((offset_index, item.geometry_updater()))
                push_offset(zero_offset)
                running_index += 1
                if isinst(item, QtContainer_):
                    if item.transfer_layout_ownership(self):
                        for child in item.children():
                            push((running_index, child))

        return offset_table, layout_table

    def _generate_constraints(self, layout_table):
        """ Creates the list of casuarius LinearConstraint objects for
        the widgets for which this container owns the layout.

        This method walks over the items in the given layout table and
        aggregates their constraints into a single list of casuarius
        LinearConstraint objects which can be given to the layout
        manager.

        Parameters
        ----------
        layout_table : list
            The layout table created by a call to _build_layout_table.

        Returns
        -------
        result : list
            The list of casuarius LinearConstraints instances to pass to
            the layout manager.

        """
        # The list of raw casuarius constraints which will be returned
        # from this method to be added to the casuarius solver.
        cns = self.contents_cns[:]
        cns.extend(self.declaration._hard_constraints())
        cns.extend(self.declaration._collect_constraints())

        # The first element in a layout table item is its offset index
        # which is not relevant to constraints generation.
        for _, updater in layout_table:
            child = updater.item
            d = child.declaration
            cns.extend(d._hard_constraints())
            if isinstance(child, QtContainer):
                if child.transfer_layout_ownership(self):
                    cns.extend(d._collect_constraints())
                    cns.extend(child.contents_cns)
                else:
                    cns.extend(child.size_hint_cns)
            else:
                cns.extend(d._collect_constraints())
                cns.extend(child.size_hint_cns)

        return cns

    #--------------------------------------------------------------------------
    # Auxiliary Methods
    #--------------------------------------------------------------------------
    def transfer_layout_ownership(self, owner):
        """ A method which can be called by other components in the
        hierarchy to gain ownership responsibility for the layout
        of the children of this container. By default, the transfer
        is allowed and is the mechanism which allows constraints to
        cross widget boundaries. Subclasses should reimplement this
        method if different behavior is desired.

        Parameters
        ----------
        owner : Declarative
            The component which has taken ownership responsibility
            for laying out the children of this component. All
            relayout and refresh requests will be forwarded to this
            component.

        Returns
        -------
        results : bool
            True if the transfer was allowed, False otherwise.

        """
        if not self.declaration.share_layout:
            return False
        self._owns_layout = False
        self._layout_owner = owner
        del self._layout_manager
        del self._refresh
        del self._offset_table
        del self._layout_table
        return True

    def will_transfer(self):
        """ Whether or not the container expects to transfer its layout
        ownership to its parent.

        This method is predictive in nature and exists so that layout
        managers are not senslessly created during the bottom-up layout
        initialization pass. It is declared public so that subclasses
        can override the behavior if necessary.

        """
        d = self.declaration
        return d.share_layout and isinstance(self.parent(), QtContainer)

    def compute_min_size(self):
        """ Calculates the minimum size of the container which would
        allow all constraints to be satisfied.

        If the container's resist properties have a strength less than
        'medium', the returned size will be zero. If the container does
        not own its layout, the returned size will be invalid.

        Returns
        -------
        result : QSize
            A (potentially invalid) QSize which is the minimum size
            required to satisfy all constraints.

        """
        d = self.declaration
        shrink = ('ignore', 'weak')
        if d.resist_width in shrink and d.resist_height in shrink:
            return QSize(0, 0)
        if self._owns_layout and self._layout_manager is not None:
            w, h = self._layout_manager.get_min_size(d.width, d.height)
            if d.resist_width in shrink:
                w = 0
            if d.resist_height in shrink:
                h = 0
            return QSize(w, h)
        return QSize()

    def compute_best_size(self):
        """ Calculates the best size of the container.

        The best size of the container is obtained by computing the min
        size of the layout using a strength which is much weaker than a
        normal resize. This takes into account the size of any widgets
        which have their resist clip property set to 'weak' while still
        allowing the window to be resized smaller by the user. If the
        container does not own its layout, the returned size will be
        invalid.

        Returns
        -------
        result : QSize
            A (potentially invalid) QSize which is the best size that
            will satisfy all constraints.

        """
        if self._owns_layout and self._layout_manager is not None:
            d = self.declaration
            w, h = self._layout_manager.get_min_size(d.width, d.height, weak)
            return QSize(w, h)
        return QSize()

    def compute_max_size(self):
        """ Calculates the maximum size of the container which would
        allow all constraints to be satisfied.

        If the container's hug properties have a strength less than
        'medium', or if the container does not own its layout, the
        returned size will be the Qt maximum.

        Returns
        -------
        result : QSize
            A (potentially invalid) QSize which is the maximum size
            allowable while still satisfying all constraints.

        """
        d = self.declaration
        expanding = ('ignore', 'weak')
        if d.hug_width in expanding and d.hug_height in expanding:
            return QSize(16777215, 16777215)
        if self._owns_layout and self._layout_manager is not None:
            w, h = self._layout_manager.get_max_size(d.width, d.height)
            if w < 0 or d.hug_width in expanding:
                w = 16777215
            if h < 0 or d.hug_height in expanding:
                h = 16777215
            return QSize(w, h)
        return QSize(16777215, 16777215)
Ejemplo n.º 23
0
class QDT(IDT, Sat_Qubit):
    base_name = "QDT"

    @private_property
    def view_window(self):
        return QDTView(agent=self)

    fq0 = SProperty().tag(desc="center frequency of oscillator")

    @fq0.getter
    def _get_fq0(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W):
        ls = self._get_Lamb_shift(f=f,
                                  f0=f0,
                                  ft_mult=ft_mult,
                                  eta=eta,
                                  epsinf=epsinf,
                                  W=W,
                                  Dvv=Dvv,
                                  Np=Np,
                                  Ct_mult=Ct_mult)
        return sqrt(f * (f - 2.0 * ls))

    @fq0.setter
    def _get_Ej_get_fq0(self, fq0, Ec):
        return (h * fq0) ^ 2 / (8.0 * Ec)

    fFWHM = SProperty().tag(
        desc="center frequency of oscillator plus half width")

    @fFWHM.getter
    def _get_fFWHM(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W,
                   dephasing, dephasing_slope):
        ls = self._get_Lamb_shift(f=f,
                                  f0=f0,
                                  ft_mult=ft_mult,
                                  eta=eta,
                                  epsinf=epsinf,
                                  W=W,
                                  Dvv=Dvv,
                                  Np=Np,
                                  Ct_mult=Ct_mult)
        gamma = self._get_coupling(f=f,
                                   f0=f0,
                                   ft_mult=ft_mult,
                                   eta=eta,
                                   epsinf=epsinf,
                                   W=W,
                                   Dvv=Dvv,
                                   Np=Np,
                                   Ct_mult=Ct_mult)
        fplus = sqrt(f * (f - 2.0 * ls + 2.0 * gamma))
        fminus = sqrt(f * (f - 2.0 * ls - 2.0 * gamma))
        return fplus, fminus, fplus - fminus + dephasing + dephasing_slope * f

    fluxfq0 = SProperty().tag(desc="center frequency of oscillator as voltage")

    @fluxfq0.getter
    def _get_fluxfq0(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W,
                     Ct, Ejmax):
        fq0 = self._get_fq0(f=f,
                            f0=f0,
                            ft_mult=ft_mult,
                            eta=eta,
                            epsinf=epsinf,
                            Ct_mult=Ct_mult,
                            Dvv=Dvv,
                            Np=Np,
                            W=W)
        return self._get_flux_from_fq(fq=fq0, Ct=Ct, Ejmax=Ejmax)

    fluxfFWHM = SProperty().tag(desc="FWHM of oscillator")

    @fluxfFWHM.getter
    def _get_fluxfFWHM(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W,
                       Ct, Ejmax):
        fplus, fminus, fwhm = self._get_fFWHM(f=f,
                                              f0=f0,
                                              ft_mult=ft_mult,
                                              eta=eta,
                                              epsinf=epsinf,
                                              Ct_mult=Ct_mult,
                                              Dvv=Dvv,
                                              Np=Np,
                                              W=W)
        Vminus = self._get_flux_from_fq(fq=fplus, Ct=Ct, Ejmax=Ejmax)
        Vplus = self._get_flux_from_fq(fq=fminus, Ct=Ct, Ejmax=Ejmax)
        return Vplus, Vminus, Vplus - Vminus

    Vfq0 = SProperty().tag(desc="center frequency of oscillator as voltage")

    @Vfq0.getter
    def _get_Vfq0(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W, Ct,
                  Ejmax, offset, flux_factor):
        fq0 = self._get_fq0(f=f,
                            f0=f0,
                            ft_mult=ft_mult,
                            eta=eta,
                            epsinf=epsinf,
                            Ct_mult=Ct_mult,
                            Dvv=Dvv,
                            Np=Np,
                            W=W)
        return self._get_voltage_from_flux_par(fq=fq0,
                                               Ct=Ct,
                                               Ejmax=Ejmax,
                                               offset=offset,
                                               flux_factor=flux_factor)

    VfFWHM = SProperty().tag(desc="FWHM of oscillator")

    @VfFWHM.getter
    def _get_VfFWHM(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W, Ct,
                    Ejmax, offset, flux_factor):
        fplus, fminus, fwhm = self._get_fFWHM(f=f,
                                              f0=f0,
                                              ft_mult=ft_mult,
                                              eta=eta,
                                              epsinf=epsinf,
                                              Ct_mult=Ct_mult,
                                              Dvv=Dvv,
                                              Np=Np,
                                              W=W)
        Vminus = self._get_voltage_from_flux_par(fq=fplus,
                                                 Ct=Ct,
                                                 Ejmax=Ejmax,
                                                 offset=offset,
                                                 flux_factor=flux_factor)
        Vplus = self._get_voltage_from_flux_par(fq=fminus,
                                                Ct=Ct,
                                                Ejmax=Ejmax,
                                                offset=offset,
                                                flux_factor=flux_factor)
        return Vplus, Vminus, Vplus - Vminus

    Vfq0_many = SProperty().tag(sub=True)

    @Vfq0_many.getter
    def _get_Vfq0_many(self, f, f0, ft_mult, eta, epsinf, Ct_mult, Dvv, Np, W,
                       Ct, Ejmax, offset, flux_factor):
        fq0 = self._get_fq0(f=f,
                            f0=f0,
                            ft_mult=ft_mult,
                            eta=eta,
                            epsinf=epsinf,
                            Ct_mult=Ct_mult,
                            Dvv=Dvv,
                            Np=Np,
                            W=W)
        return self._get_voltage_from_flux_par_many(fq=fq0,
                                                    Ct=Ct,
                                                    Ejmax=Ejmax,
                                                    offset=offset,
                                                    flux_factor=flux_factor)

#    GL=Float(1.0)
#    simple_S_qdt=SProperty().tag(sub=True)
#    @simple_S_qdt.getter
#    def _get_simple_S_qdt(self, f, f0, ft_mult, eta, epsinf, Ct_mult, K2, Np, Ct, L, dL, vf, L_IDT, GL):
#        Ga=self._get_Ga(f=f, f0=f0, ft_mult=ft_mult, eta=eta, epsinf=epsinf, Ct_mult=Ct_mult, K2=K2, Np=Np)
#        Ba=self._get_Ba(f=f, f0=f0, ft_mult=ft_mult, eta=eta, epsinf=epsinf, Ct_mult=Ct_mult, K2=K2, Np=Np)
#        w=2*pi*f
#        YL=-1.0j/(w*L)
#
#        k=2*pi*f/vf
#        jkL=1.0j*k*L_IDT
#        P33plusYL=Ga+1.0j*Ba+1.0j*w*Ct-1.0j/w*dL+YL
#        S11=S22=-Ga/P33plusYL*exp(-jkL)
#        S12=S21=exp(-jkL)+S11
#        S13=S23=S32=S31=1.0j*sqrt(2.0*Ga*GL)/P33plusYL*exp(-jkL/2.0)
#        S33=(YL-Ga+1.0j*Ba+1.0j*w*Ct-1.0j/w*dL)/P33plusYL
#        return (S11, S12, S13,
#                S21, S22, S23,
#                S31, S32, S33)

    @private_property
    def fixed_fq(self):
        return linspace(self.fixed_fq_min, self.fixed_fq_max,
                        self.N_fixed_fq).astype(float64)

    N_fixed_fq = Int(500)
    fixed_fq_max = Float()
    fixed_fq_min = Float(0.01)

    def _default_fixed_freq_max(self):
        return 2.0 * self.f0

    def _default_fixed_fq_max(self):
        return 2.0 * self.f0

    def _default_fixed_fq_min(self):
        return 0.0001  #*self.f0

    def _default_N_fixed(self):
        return 400

    calc_p_guess = Bool(False)
    fitter = Typed(LorentzianFitter, ())  #fit.gamma=0.05

    flux_indices = List()

    def _default_flux_indices(self):
        return [range(len(self.fixed_fq))]

    @private_property
    def flat_flux_indices(self):
        return [n for ind in self.flux_indices for n in ind]

    fit_indices = List()  #.tag(private=True)

    def _default_fit_indices(self):
        return [range(len(self.fixed_freq))]

    @private_property
    def flat_indices(self):
        return [n for ind in self.fit_indices for n in ind]

    @private_property
    def MagAbs(self):
        #(S11, S12, S13,
        # S21, S22, S23,
        # S31, S32, S33)=self.fixed_S

        magind = {
            "S11": 0,
            "S12": 1,
            "S13": 2,
            "S21": 3,
            "S22": 4,
            "S23": 5,
            "S31": 6,
            "S32": 7,
            "S33": 8
        }[self.magabs_type]
        magcom = self.fixed_S[:, magind, :]
        #magcom={"S11" : S11, "S12" : S12, "S13" : S13,
        #        "S21" : S21, "S22" : S22, "S23" : S23,
        #        "S31" : S31, "S32" : S32, "S33" : S33}[self.magabs_type]
        return absolute(magcom)
        #if self.bgsub_type=="dB":
        #    return 10.0**(self.MagdB/10.0)
        #magabs=absolute(self.Magcom)
        #if self.bgsub_type=="Abs":
        #    return self.bgsub(magabs)
        #return magabs

    @private_property
    def fit_params(self):
        MagAbsSq = (self.MagAbs**2).transpose()
        if self.fitter.fit_params is None:
            self.fitter.full_fit(x=self.fixed_fq[self.flat_flux_indices] / 1e9,
                                 y=MagAbsSq,
                                 indices=self.flat_indices,
                                 gamma=self.fitter.gamma)
            if self.calc_p_guess:
                self.fitter.make_p_guess(
                    self.fixed_fq[self.flat_flux_indices] / 1e9,
                    y=MagAbsSq,
                    indices=self.flat_indices,
                    gamma=self.fitter.gamma)
        return self.fitter.fit_params

    @private_property
    def MagAbsFit(self):
        return sqrt(
            self.fitter.reconstruct_fit(
                self.fixed_fq[self.flat_flux_indices] / 1e9,
                self.fit_params)).transpose()

#    YL=SProperty()
#    @YL.getter
#    def _get_YL(self, w, L):
#        if self.YL_type=="constant":
#            return self.YL
#        elif self.YL_type=="inductor":
#            return -1.0j/(w*L)

    @private_property
    def fixed_S(self):
        w = 2 * pi * self.fixed_freq
        L_arr = self._get_L(fq=self.fixed_fq)
        if self.S_type == "simple":
            return array([
                self._get_simple_S(f=self.fixed_freq, YL=-1.0j / (w * L))
                for L in L_arr
            ])
        P = self.fixed_P[0]
        #return self.PtoS(*P, YL=self.YL)
        return array([self.PtoS(*P, YL=-1.0j / (w * L)) for L in L_arr])

    lamb_shifted_transmon_energy = SProperty()

    @lamb_shifted_transmon_energy.getter
    def _get_lamb_shifted_transmon_energy(self, Ej, Ec, m, f0, ft_mult, eta,
                                          epsinf, W, Dvv, Np, Ct_mult):
        Em = -Ej + sqrt(8.0 * Ej * Ec) * (m + 0.5) - (Ec / 12.0) * (
            6.0 * m**2 + 6.0 * m + 3.0)
        if m == 0:
            return Em
        Emm1 = -Ej + sqrt(
            8.0 * Ej * Ec) * (m - 1 + 0.5) - (Ec / 12.0) * (6.0 *
                                                            (m - 1)**2 + 6.0 *
                                                            (m - 1) + 3.0)
        fq = (Em - Emm1) / h
        fls = self._get_Lamb_shift(f=fq,
                                   f0=f0,
                                   ft_mult=ft_mult,
                                   eta=eta,
                                   epsinf=epsinf,
                                   W=W,
                                   Dvv=Dvv,
                                   Np=Np,
                                   Ct_mult=Ct_mult)
        return Em + h * fls

    lamb_shifted_transmon_energy_levels = SProperty()

    @lamb_shifted_transmon_energy_levels.getter
    def _get_lamb_shifted_transmon_energy_levels(self, Ej, f0, ft_mult, eta,
                                                 epsinf, W, Dvv, Np, Ct_mult,
                                                 Ec, n_energy):
        return [
            self._get_lamb_shifted_transmon_energy(Ej=Ej,
                                                   Ec=Ec,
                                                   m=m,
                                                   f0=f0,
                                                   ft_mult=ft_mult,
                                                   eta=eta,
                                                   epsinf=epsinf,
                                                   W=W,
                                                   Dvv=Dvv,
                                                   Np=Np,
                                                   Ct_mult=Ct_mult)
            for m in range(n_energy)
        ]
Ejemplo n.º 24
0
class BridgedApplication(Application):
    """ An abstract implementation of an Enaml application.

    This serves as a base class for both Android and iOS applications and
    provides support for the python event loop, the development server
    and the bridge.

    """
    __id__ = Int(-1)

    #: Keep screen on by setting the WindowManager flag
    keep_screen_on = Bool()

    #: Statusbar color
    statusbar_color = Unicode()

    #: Application lifecycle state must be set by the implementation
    state = Enum('created', 'paused', 'resumed', 'stopped', 'destroyed')

    #: Width of the screen in dp
    width = Float(strict=False)

    #: Height of the screen in dp
    height = Float(strict=False)

    #: Screen orientation
    orientation = Enum('portrait', 'landscape', 'square')

    #: View to display within the activity
    view = Value()

    #: Factory to create and show the view. It takes the app as the first arg
    load_view = Callable()

    #: If true, debug bridge statements
    debug = Bool()

    #: Use dev server
    dev = Unicode()
    _dev_session = Value()

    #: Event loop
    loop = Instance(EventLoop)

    #: Events to send to the bridge
    _bridge_queue = List()

    #: Time last sent
    _bridge_max_delay = Float(0.005)

    #: Time last sent
    _bridge_last_scheduled = Float()

    #: Entry points to load plugins
    plugins = Dict()

    # -------------------------------------------------------------------------
    # Defaults
    # -------------------------------------------------------------------------
    def _default_loop(self):
        """ Get the event loop based on what libraries are available. """
        return EventLoop.default()

    def _default_plugins(self):
        """ Get entry points to load any plugins installed.
        The build process should create an "entry_points.json" file
        with all of the data from the installed entry points.

        """
        plugins = {}
        try:
            with open('entry_points.json') as f:
                entry_points = json.load(f)
            for ep, obj in entry_points.items():
                plugins[ep] = []
                for name, src in obj.items():
                    plugins[ep].append(Plugin(name=name, source=src))
        except Exception as e:
            print("Failed to load entry points {}".format(e))
        return plugins

    # -------------------------------------------------------------------------
    # BridgedApplication Constructor
    # -------------------------------------------------------------------------
    def __init__(self, *args, **kwargs):
        """ Initialize the event loop error handler.  Subclasses must properly
        initialize the proxy resolver.

        """
        super(BridgedApplication, self).__init__(*args, **kwargs)
        if self.dev:
            self.start_dev_session()
        self.init_error_handler()
        self.load_plugin_widgets()
        self.load_plugin_factories()

    # -------------------------------------------------------------------------
    # Abstract API Implementation
    # -------------------------------------------------------------------------
    def start(self):
        """ Start the application's main event loop
        using either twisted or tornado.

        """
        #: Schedule a load view if given and remote debugging is not active
        #: the remote debugging init call this after dev connection is ready
        if self.load_view and self.dev != "remote":
            self.deferred_call(self.load_view, self)

        self.loop.start()

    def stop(self):
        """ Stop the application's main event loop.

        """
        self.loop.stop()

    def deferred_call(self, callback, *args, **kwargs):
        """ Invoke a callable on the next cycle of the main event loop
        thread.

        Parameters
        ----------
        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        return self.loop.deferred_call(callback, *args, **kwargs)

    def timed_call(self, ms, callback, *args, **kwargs):
        """ Invoke a callable on the main event loop thread at a
        specified time in the future.

        Parameters
        ----------
        ms : int
            The time to delay, in milliseconds, before executing the
            callable.

        callback : callable
            The callable object to execute at some point in the future.

        *args, **kwargs
            Any additional positional and keyword arguments to pass to
            the callback.

        """
        return self.loop.timed_call(ms, callback, *args, **kwargs)

    def is_main_thread(self):
        """ Indicates whether the caller is on the main gui thread.

        Returns
        -------
        result : bool
            True if called from the main gui thread. False otherwise.

        """
        return False

    # -------------------------------------------------------------------------
    # App API Implementation
    # -------------------------------------------------------------------------
    def has_permission(self, permission):
        """ Return a future that resolves with the result of the permission """
        raise NotImplementedError

    def request_permissions(self, permissions):
        """ Return a future that resolves with the result of the
        permission request

        """
        raise NotImplementedError

    # -------------------------------------------------------------------------
    # EventLoop API Implementation
    # -------------------------------------------------------------------------
    def init_error_handler(self):
        """ When an error occurs, set the error view in the App

        """
        self.loop.set_error_handler(self.handle_error)

    def create_future(self):
        """ Create a future object using the EventLoop implementation """
        return self.loop.create_future()

    def run_iteration(self):
        """ Run an iteration of the event loop  """
        return self.loop.run_iteration()

    def add_done_callback(self, future, callback):
        """ Add a callback on a future object put here so it can be
        implemented with different event loops.

        Parameters
        -----------
            future: Future or Deferred
                Future implementation for the current EventLoop

            callback: callable
                Callback to invoke when the future is done

        """
        if future is None:
            raise bridge.BridgeReferenceError(
                "Tried to add a callback to a nonexistent Future. "
                "Make sure you pass the `returns` argument to your JavaMethod")
        return self.loop.add_done_callback(future, callback)

    def set_future_result(self, future, result):
        """ Set the result of the future

        Parameters
        -----------
            future: Future or Deferred
                Future implementation for the current EventLoop

            result: object
                Result to set
        """
        return self.loop.set_future_result(future, result)

    # -------------------------------------------------------------------------
    # Bridge API Implementation
    # -------------------------------------------------------------------------
    def show_view(self):
        """ Show the current `app.view`. This will fade out the previous
        with the new view.

        """
        raise NotImplementedError

    def get_view(self):
        """ Get the root view to display. Make sure it is
        properly initialized.

        """
        view = self.view
        if not view.is_initialized:
            view.initialize()
        if not view.proxy_is_active:
            view.activate_proxy()
        return view.proxy.widget

    def show_error(self, msg):
        """ Show the error view with the given message on the UI.

        """
        self.send_event(bridge.Command.ERROR, msg)

    def send_event(self, name, *args, **kwargs):
        """ Send an event to the native handler. This call is queued and
        batched.

        Parameters
        ----------
        name : str
            The event name to be processed by MainActivity.processMessages.
        *args: args
            The arguments required by the event.
        **kwargs: kwargs
            Options for sending. These are:

            now: boolean
                Send the event now

        """
        n = len(self._bridge_queue)

        # Add to queue
        self._bridge_queue.append((name, args))

        if n == 0:
            # First event, send at next available time
            self._bridge_last_scheduled = time()
            self.deferred_call(self._bridge_send)
            return
        elif kwargs.get('now'):
            self._bridge_send(now=True)
            return

        # If it's been over 5 ms since we last scheduled, run now
        dt = time() - self._bridge_last_scheduled
        if dt > self._bridge_max_delay:
            self._bridge_send(now=True)

    def force_update(self):
        """ Force an update now. """
        #: So we don't get out of order
        self._bridge_send(now=True)

    def _bridge_send(self, now=False):
        """  Send the events over the bridge to be processed by the native
        handler.

        Parameters
        ----------
        now: boolean
            Send all pending events now instead of waiting for deferred calls
            to finish. Use this when you want to update the screen

        """
        if len(self._bridge_queue):
            if self.debug:
                print("======== Py --> Native ======")
                for event in self._bridge_queue:
                    print(event)
                print("===========================")
            self.dispatch_events(bridge.dumps(self._bridge_queue))
            self._bridge_queue = []

    def dispatch_events(self, data):
        """ Send events to the bridge using the system specific implementation.

        """
        raise NotImplementedError

    def process_events(self, data):
        """ The native implementation must use this call to """
        events = bridge.loads(data)
        if self.debug:
            print("======== Py <-- Native ======")
            for event in events:
                print(event)
            print("===========================")
        for event in events:
            if event[0] == 'event':
                self.handle_event(event)

    def handle_event(self, event):
        """ When we get an 'event' type from the bridge
        handle it by invoking the handler and if needed
        sending back the result.

        """
        result_id, ptr, method, args = event[1]
        obj = None
        result = None
        try:
            obj, handler = bridge.get_handler(ptr, method)
            result = handler(*[v for t, v in args])
        except bridge.BridgeReferenceError as e:
            #: Log the event, don't blow up here
            msg = "Error processing event: {} - {}".format(event,
                                                           e).encode("utf-8")
            print(msg)
            #self.show_error(msg)
        except:
            #: Log the event, blow up in user's face
            msg = "Error processing event: {} - {}".format(
                event, traceback.format_exc()).encode("utf-8")
            print(msg)
            self.show_error(msg)
            raise
        finally:
            if result_id:
                if hasattr(obj, '__nativeclass__'):
                    sig = getattr(type(obj), method).__returns__
                else:
                    sig = type(result).__name__

                self.send_event(
                    bridge.Command.RESULT,  #: method
                    result_id,
                    bridge.msgpack_encoder(sig, result)  #: args
                )

    def handle_error(self, callback):
        """ Called when an error occurs in an event loop callback.
        By default, sets the error view.

        """
        self.loop.log_error(callback)
        msg = "\n".join(
            ["Exception in callback %r" % callback,
             traceback.format_exc()])
        self.show_error(msg.encode('utf-8'))

    # -------------------------------------------------------------------------
    # AppEventListener API Implementation
    # -------------------------------------------------------------------------
    def on_events(self, data):
        """ Called when the bridge sends an event. For instance the return
        result of a method call or a callback from a widget event.

        """
        #: Pass to event loop thread
        self.deferred_call(self.process_events, data)

    def on_pause(self):
        """ Called when the app is paused.
        """
        pass

    def on_resume(self):
        """ Called when the app is resumed.
        """
        pass

    def on_stop(self):
        """ Called when the app is stopped.
        """
        #: Called from thread, make sure the correct thread detaches
        pass

    def on_destroy(self):
        """ Called when the app is destroyed.
        """
        self.deferred_call(self.stop)

    # -------------------------------------------------------------------------
    # Dev Session Implementation
    # -------------------------------------------------------------------------
    def start_dev_session(self):
        """ Start a client that attempts to connect to the dev server
        running on the host `app.dev`

        """
        try:
            from .dev import DevServerSession
            session = DevServerSession.initialize(host=self.dev)
            session.start()

            #: Save a reference
            self._dev_session = session
        except:
            self.show_error(traceback.format_exc())

    # -------------------------------------------------------------------------
    # Plugin implementation
    # -------------------------------------------------------------------------
    def get_plugins(self, group):
        """ Was going to use entry points but that requires a ton of stuff
        which will be extremely slow.

        """
        return self.plugins.get(group, [])

    def load_plugin_widgets(self):
        """ Pull widgets added via plugins using the `enaml_native_widgets`
        entry point. The entry point function must return a dictionary of
        Widget declarations to add to the core api.

        def install():
            from charts.widgets.chart_view import BarChart, LineChart
            return {
                'BarChart': BarChart,
                'LineCart': LineChart,
            }

        """
        from enamlnative.widgets import api
        for plugin in self.get_plugins(group='enaml_native_widgets'):
            get_widgets = plugin.load()
            for name, widget in iter(get_widgets()):
                #: Update the core api with these widgets
                setattr(api, name, widget)

    def load_plugin_factories(self):
        """ Pull widgets added via plugins using the
        `enaml_native_ios_factories` or `enaml_native_android_factories`
        entry points. The entry point function must return a dictionary of
        Widget declarations to add to the factories for this platform.

        def install():
            return {
                'MyWidget':my_widget_factory,
                # etc...
            }

        """
        raise NotImplementedError
Ejemplo n.º 25
0
class LogPlugin(Plugin):
    """Plugin managing the application logging.

    """
    #: List of installed handlers.
    handler_ids = List(Unicode())

    #: List of installed filters.
    filter_ids = List(Unicode())

    #: Model which can be used to display the log in the GUI. It is associated
    #: to a handler attached to the root logger.
    gui_model = Typed(LogModel)

    def add_handler(self, id, handler=None, logger='', mode=None):
        """Add a handler to the specified logger.

        Parameters
        ----------
        id : unicode
            Id of the new handler. This id should be unique.

        handler : logging.Handler, optional
            Handler to add.

        logger : unicode, optional
            Name of the logger to which the handler should be added. By default
            the handler is added to the root logger.

        mode : {'ui', }, optional
            Conveninence to add a simple logger. If this argument is specified,
            handler will be ignored and the command will return useful
            references (the model to which can be connected a ui for the 'ui'
            mode).

        Returns
        -------
        refs : list
            List of useful reference, empty if no mode is selected.

        """
        refs = []
        if not handler:
            if mode and mode == 'ui':
                model = LogModel()
                handler = GuiHandler(model=model)
                refs.append(model)
            else:
                logger = logging.getLogger(__name__)
                msg = ('Missing handler or recognised mode when adding '
                       'log handler under id %s to logger %s')
                logger.info(msg, id, logger)
                return []

        name = logger
        logger = logging.getLogger(name)

        logger.addHandler(handler)

        self._handlers[id] = (handler, name)
        self.handler_ids = list(self._handlers.keys())

        if refs:
            return refs

    def remove_handler(self, id):
        """Remove the specified handler.

        Parameters
        ----------
        id : unicode
            Id of the handler to remove.

        """
        handlers = self._handlers
        if id in handlers:
            handler, logger_name = handlers.pop(id)
            logger = logging.getLogger(logger_name)
            logger.removeHandler(handler)
            for filter_id in self.filter_ids:
                infos = self._filters[filter_id]
                if infos[1] == id:
                    del self._filters[filter_id]

            self.filter_ids = list(self._filters.keys())
            self.handler_ids = list(self._handlers.keys())

    def add_filter(self, id, filter, handler_id):
        """Add a filter to the specified handler.

        Parameters
        ----------
        id : unicode
            Id of the filter to add.

        filter : object
            Filter to add to the specified handler (object implemeting a filter
            method).

        handler_id : unicode
            Id of the handler to which this filter should be added

        """
        if not hasattr(filter, 'filter'):
            logger = logging.getLogger(__name__)
            logger.warn('Filter does not implemet a filter method')
            return

        handlers = self._handlers
        if handler_id in handlers:
            handler, _ = handlers[handler_id]
            handler.addFilter(filter)
            self._filters[id] = (filter, handler_id)

            self.filter_ids = list(self._filters.keys())

        else:
            logger = logging.getLogger(__name__)
            logger.warn('Handler {} does not exist')

    def remove_filter(self, id):
        """Remove the specified filter.

        Parameters
        ----------
        id : unicode
            Id of the filter to remove.

        """
        filters = self._filters
        if id in filters:
            filter, handler_id = filters.pop(id)
            handler, _ = self._handlers[handler_id]
            handler.removeFilter(filter)
            self.filter_ids = list(self._filters.keys())

    def set_formatter(self, handler_id, formatter):
        """Set the formatter of the specified handler.

        Parameters
        ----------
        handler_id : unicode
            Id of the handler whose formatter shoudl be set.

        formatter : Formatter
            Formatter for the handler.

        """
        handlers = self._handlers
        handler_id = str(handler_id)
        if handler_id in handlers:
            handler, _ = handlers[handler_id]
            handler.setFormatter(formatter)

        else:
            logger = logging.getLogger(__name__)
            logger.warn('Handler {} does not exist')

    # ---- Private API --------------------------------------------------------

    # Mapping between handler ids and handler, logger name pairs.
    _handlers = Dict(Unicode(), Tuple())

    # Mapping between filter_id and filter, handler_id pairs.
    _filters = Dict(Unicode(), Tuple())
Ejemplo n.º 26
0
class FirstPassTemplateCompiler(block.FirstPassBlockCompiler):
    """ The first pass template compiler.

    This compiler generates the code which builds the compiler nodes
    for the template definition. The main entry point is the 'compile'
    class method.

    """
    #: The const names collected during traversal.
    const_names = List()

    @classmethod
    def compile(cls, node, args, local_names, filename):
        """ Invoke the compiler for the given node.

        The generated code object expects the SCOPE_KEY to be passed as
        one of the arguments. The code object will return a 2-tuple of
        compiler node list and const expression value tuple.

        Parameters
        ----------
        node : Template
            The enaml ast Template node of interest.

        args : list
            The list of argument names which will be passed to the
            code object when it is invoked.

        local_names : set
            The set of local names which are available to the code
            object. This should be the combination of const expression
            names and template parameter names.

        filename : str
            The filename of the node being compiled.

        Returns
        -------
        result : tuple
            A 2-tuple of (code, index_map) which is the generated code
            object for the first compiler pass, and a mapping of ast
            node to relevant compiler node index.

        """
        compiler = cls()
        compiler.filename = filename
        compiler.local_names = local_names
        cg = compiler.code_generator

        # Setup the block for execution.
        cmn.fetch_helpers(cg)
        cmn.make_node_list(cg, cmn.count_nodes(node))

        # Dispatch the visitors.
        compiler.visit(node)

        # Setup the parameters and generate the code object.
        cg.name = node.name
        cg.firstlineno = node.lineno
        cg.newlocals = True
        cg.args = args
        code = cg.to_code()

        # Union the two index maps for use by the second compiler pass.
        final_index_map = dict(compiler.index_map)
        final_index_map.update(compiler.aux_index_map)

        return (code, final_index_map)

    def visit_Template(self, node):
        # No pragmas are supported yet for template nodes.
        cmn.warn_pragmas(node, self.filename)

        # Claim the index for the compiler node
        index = len(self.index_map)
        self.index_map[node] = index

        # Setup the line number for the template.
        cg = self.code_generator
        cg.set_lineno(node.lineno)

        # Create the template compiler node and store in the node list.
        cmn.load_helper(cg, 'template_node')
        cg.load_fast(cmn.SCOPE_KEY)
        cg.call_function(1)
        cmn.store_node(cg, index)

        # Visit the body of the template.
        for item in node.body:
            self.visit(item)

        # Update the internal node ids for the hierarchy.
        cmn.load_node(cg, 0)
        cg.load_attr('update_id_nodes')
        cg.call_function()
        cg.pop_top()

        # Load the compiler node list for returning.
        cg.load_fast(cmn.NODE_LIST)

        # Load the const names for returning.
        for name in self.const_names:
            cg.load_fast(name)
        cg.build_tuple(len(self.const_names))

        # Create and return the return value tuple.
        cg.build_tuple(2)
        cg.return_value()

    def visit_ConstExpr(self, node):
        # Keep track of the const name for loading for return.
        self.const_names.append(node.name)

        # Setup the line number for the const expr.
        cg = self.code_generator
        cg.set_lineno(node.lineno)

        # Generate the code for the expression.
        names = self.local_names
        cmn.safe_eval_ast(cg, node.expr.ast, node.name, node.lineno, names)

        # Validate the type of the expression value.
        if node.typename:
            with cg.try_squash_raise():
                cg.dup_top()
                cmn.load_helper(cg, 'type_check_expr')
                cg.rot_two()
                cmn.load_typename(cg, node.typename, names)
                cg.call_function(2)
                cg.pop_top()

        # Store the result in the fast locals.
        cg.store_fast(node.name)
Ejemplo n.º 27
0
class MenuNode(PathNode):
    """ A path node representing a menu item.

    This class is an implementation detail and should not be consumed
    by code outside of this module.

    """
    #: The child objects defined for this menu node.
    children = List(PathNode)

    def group_data(self):
        """ The group map and list of group items for the node.

        Returns
        -------
        result : tuple
            A tuple of (dict, list) which holds the mapping of group
            id to ItemGroup object, and the flat list of ordered groups.

        """
        group_map = {}
        item_groups = self.item.item_groups

        for group in item_groups:
            if group.id in group_map:
                msg = "menu item '%s' has duplicate group '%s'"
                raise ValueError(msg % (self.path, group.id))
            group_map[group.id] = group

        if u'' not in group_map:
            group = ItemGroup()
            group_map[u''] = group
            item_groups.append(group)

        return group_map, item_groups

    def collect_child_groups(self):
        """ Yield the ordered and grouped children.

        """
        group_map, item_groups = self.group_data()

        grouped = defaultdict(list)
        for child in self.children:
            target_group = child.item.group
            if target_group not in group_map:
                msg = "item '%s' has invalid group '%s'"
                raise ValueError(msg % (child.path, target_group))
            grouped[target_group].append(child)

        for group in item_groups:
            if group.id in grouped:
                nodes = grouped.pop(group.id)
                yield group, solve_ordering(nodes)

    def create_children(self, group, nodes):
        """ Create the child widgets for the given group of nodes.

        This will assemble the nodes and setup the action groups.

        """
        result = []
        actions = []
        children = [node.assemble() for node in nodes]

        def process_actions():
            if actions:
                wag = WorkbenchActionGroup(group=group)
                wag.insert_children(None, actions)
                result.append(wag)
                del actions[:]

        for child in children:
            if isinstance(child, WorkbenchAction):
                actions.append(child)
            else:
                process_actions()
                child.group = group
                result.append(child)

        process_actions()

        return result

    def assemble_children(self):
        """ Assemble the list of child objects for the menu.

        """
        children = []
        for group, nodes in self.collect_child_groups():
            children.extend(self.create_children(group, nodes))
            children.append(Action(separator=True))
        if children:
            children.pop()
        return children

    def assemble(self):
        """ Assemble and return a WorkbenchMenu for the node.

        """
        menu = WorkbenchMenu(item=self.item)
        menu.insert_children(None, self.assemble_children())
        return menu
Ejemplo n.º 28
0
 class ListTest(Atom):
     no_default = List()
     default = List(default=['a'])
Ejemplo n.º 29
0
class DevicePlugin(Plugin):
    """ Plugin for configuring, using, and communicating with 
    a device.
    
    """

    #: Protocols registered in the system
    protocols = List(extensions.DeviceProtocol)

    #: Transports
    transports = List(extensions.DeviceTransport)

    #: Drivers registered in the system
    drivers = List(extensions.DeviceDriver)

    #: Devices configured
    devices = List(Device).tag(config=True)

    #: Current device
    device = Instance(Device).tag(config=True)

    # -------------------------------------------------------------------------
    # Plugin API
    # -------------------------------------------------------------------------
    def start(self):
        """ Load all the plugins the device is dependent on """
        w = self.workbench
        plugins = []
        with enaml.imports():
            from .transports.serialport.manifest import SerialManifest
            from .transports.printer.manifest import PrinterManifest
            from .transports.disk.manifest import FileManifest
            from inkcut.device.protocols.manifest import ProtocolManifest
            from inkcut.device.drivers.manifest import DriversManifest
            from inkcut.device.pi.manifest import PiManifest

            plugins.append(SerialManifest)
            plugins.append(PrinterManifest)
            plugins.append(FileManifest)
            plugins.append(ProtocolManifest)
            plugins.append(DriversManifest)
            plugins.append(PiManifest)

        for Manifest in plugins:
            w.register(Manifest())

        #: This refreshes everything else
        self._refresh_extensions()

        #: Restore state after plugins are loaded
        super(DevicePlugin, self).start()

    def submit(self, job):
        """ Send the given job to the device and restart all stats 
        
        """
        job.info.reset()
        job.info.started = datetime.now()
        return self.device.submit(job)

    def _default_device(self):
        """ If no device is loaded from the previous state, get the device
        from the first driver loaded.
        
        """
        self._refresh_extensions()

        #: If a device is configured, use that
        if self.devices:
            return self.devices[0]

        #: Otherwise create one using the first registered driver
        if not self.drivers:
            raise RuntimeError("No device drivers were registered. "
                               "This indicates a missing plugin.")
        return self.get_device_from_driver(self.drivers[0])

    def _observe_device(self, change):
        """ Whenever the device changes, redraw """
        #: Redraw
        plugin = self.workbench.get_plugin('inkcut.job')
        plugin.refresh_preview()

    # -------------------------------------------------------------------------
    # Device Driver API
    # -------------------------------------------------------------------------

    def get_device_from_driver(self, driver):
        """ Load the device driver. This generates the device using
        the factory function the DeviceDriver specifies and assigns
        the protocols and transports based on the filters given by
        the driver.
        
        Parameters
        ----------
            driver: inkcut.device.extensions.DeviceDriver
                The DeviceDriver declaration to use to create a device.
        Returns
        -------
            device: inkcut.device.plugin.Device
                The actual device object that will be used for communication
                and processing the jobs.
        
        """
        #: Generate the device
        device = driver.factory(driver.default_config)

        #: Now set the declaration
        device.declaration = driver

        #: Set the protocols based on the declaration
        if driver.protocols:
            device.protocols = [
                p for p in self.protocols if p.id in driver.protocols
            ]
        else:
            device.protocols = self.protocols[:]

        #: Set the protocols based on the declaration
        if driver.connections:
            device.transports = [
                t for t in self.transports
                if t.id == 'disk' or t.id in driver.connections
            ]
        else:
            device.transports = self.transports[:]

        return device

    # -------------------------------------------------------------------------
    # Device Extensions API
    # -------------------------------------------------------------------------
    def _refresh_extensions(self):
        """ Refresh all extensions provided by the DevicePlugin """
        self._refresh_protocols()
        self._refresh_transports()
        self._refresh_drivers()

    def _refresh_protocols(self):
        """ Reload all DeviceProtocols registered by any Plugins 
        
        Any plugin can add to this list by providing a DeviceProtocol 
        extension in the PluginManifest.
        
        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DEVICE_PROTOCOL_POINT)

        protocols = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for p in extension.get_children(extensions.DeviceProtocol):
                protocols.append(p)

        #: Update
        self.protocols = protocols

    def _refresh_transports(self):
        """ Reload all DeviceTransports registered by any Plugins 
        
        Any plugin can add to this list by providing a DeviceTransport 
        extension in the PluginManifest.
        
        """
        workbench = self.workbench
        point = workbench.get_extension_point(
            extensions.DEVICE_TRANSPORT_POINT)

        transports = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for t in extension.get_children(extensions.DeviceTransport):
                transports.append(t)

        #: Update
        self.transports = transports

    def _refresh_drivers(self):
        """ Reload all DeviceDrivers registered by any Plugins 
        
        Any plugin can add to this list by providing a DeviceDriver 
        extension in the PluginManifest.
        
        """
        workbench = self.workbench
        point = workbench.get_extension_point(extensions.DEVICE_DRIVER_POINT)
        drivers = []
        for extension in sorted(point.extensions, key=lambda ext: ext.rank):
            for driver in extension.get_children(extensions.DeviceDriver):
                if not driver.id:
                    driver.id = "{} {}".format(driver.manufacturer,
                                               driver.model)
                drivers.append(driver)

        #: Update
        self.drivers = drivers

    # -------------------------------------------------------------------------
    # Live progress API
    # -------------------------------------------------------------------------
    def reset_preview(self):
        """ Clear the preview """
        self._reset_preview(None)

    @observe('device', 'device.job')
    def _reset_preview(self, change):
        """ Redraw the preview on the screen 
        
        """
        view_items = []

        #: Transform used by the view
        preview_plugin = self.workbench.get_plugin('inkcut.preview')
        plot = preview_plugin.live_preview
        t = preview_plugin.transform

        #: Draw the device
        device = self.device
        job = device.job
        if device and device.area:
            area = device.area
            view_items.append(
                dict(path=device.transform(device.area.path * t),
                     pen=plot.pen_device,
                     skip_autorange=True))

        if job and job.material:
            # Also observe any change to job.media and job.device
            view_items.extend([
                dict(path=device.transform(job.material.path * t),
                     pen=plot.pen_media,
                     skip_autorange=True),
                dict(path=device.transform(job.material.padding_path * t),
                     pen=plot.pen_media_padding,
                     skip_autorange=True)
            ])

        #: Update the plot
        preview_plugin.set_live_preview(*view_items)

    @observe('device.position')
    def _update_preview(self, change):
        """ Watch the position of the device as it changes. """
        if change['type'] == 'update' and self.device.job:
            x, y, z = change['value']
            preview_plugin = self.workbench.get_plugin('inkcut.preview')
            preview_plugin.live_preview.update(change['value'])
Ejemplo n.º 30
0
class EventsResult(Model):
    Locations = List(Location)
    Labels = List(Label)
    Starred = Int()