コード例 #1
0
ファイル: qt.py プロジェクト: CSRAccTeam/madgui
class Property:
    """Internal class for cached properties. Should be simplified and
    rewritten. Currently only used as base class for ``SingleWindow``. Do not
    use for new code."""
    def __init__(self, construct):
        self.construct = construct
        self.holds_value = Bool(False)

    # porcelain

    @classmethod
    def factory(cls, func):
        return cachedproperty(
            functools.wraps(func)(lambda self: cls(func.__get__(self))))

    def create(self):
        if self._has:
            self._update()
        else:
            self._new()
        return self.val

    def destroy(self):
        if self._has:
            self._del()

    def toggle(self):
        if self._has:
            self._del()
        else:
            self._new()

    def _new(self):
        val = self.construct()
        self._set(val)
        return val

    def _update(self):
        pass

    @property
    def _has(self):
        return hasattr(self, '_val')

    def _get(self):
        return self._val

    def _set(self, val):
        self._val = val
        self.holds_value.set(True)

    def _del(self):
        del self._val
        self.holds_value.set(False)

    # use lambdas to enable overriding the _get/_set/_del methods
    # without having to redefine the 'val' property
    val = property(lambda self: self._get(), lambda self, val: self._set(val),
                   lambda self: self._del())
コード例 #2
0
ファイル: control.py プロジェクト: CSRAccTeam/madgui
 def __init__(self, session):
     self.session = session
     self.backend = None
     self.model = session.model
     self.sampler = BeamSampler(self)
     # menu conditions
     self.is_connected = Bool(False)
     self.has_backend = Bool(False)
     self.can_connect = ~self.is_connected & self.has_backend
     self.has_sequence = self.is_connected & self.model
     self._config = config = session.config.online_control
     self._settings = config['settings']
     self._on_model_changed()
     self.set_backend(config.backend)
コード例 #3
0
ファイル: control.py プロジェクト: hibtc/madgui
 def __init__(self, session):
     self.session = session
     self.backend = None
     self.model = session.model
     self.sampler = BeamSampler(self)
     # menu conditions
     self.is_connected = Bool(False)
     self.has_backend = Bool(False)
     self.can_connect = ~self.is_connected & self.has_backend
     self.has_sequence = self.is_connected & self.model
     self._config = config = session.config.online_control
     self._settings = config['settings']
     self._on_model_changed()
     self.set_backend(config.backend)
コード例 #4
0
ファイル: plugin.py プロジェクト: hibtc/hit_acs
 def __init__(self,
              lib,
              params,
              model=None,
              offsets=None,
              settings=None,
              control=None):
     self._lib = lib
     self._params = dicti({
         'beam_energy':
         dict(name='beam_energy',
              ui_name='beam_energy',
              ui_hint='',
              ui_prec=3,
              unit='MeV/u',
              ui_unit='MeV/u',
              ui_conv=1),
         'beam_focus':
         dict(name='beam_focus',
              ui_name='beam_focus',
              ui_hint='',
              ui_prec=3,
              unit='m',
              ui_unit='mm',
              ui_conv=1000),
         'beam_intensity':
         dict(name='beam_intensity',
              ui_name='beam_intensity',
              ui_hint='',
              ui_prec=3,
              unit='',
              ui_unit='',
              ui_conv=1),
         'gantry_angle':
         dict(name='gantry_angle',
              ui_name='gantry_angle',
              ui_hint='',
              ui_prec=3,
              unit='°',
              ui_unit='°',
              ui_conv=1),
     })
     self._params.update(params)
     self._model = model
     self._offsets = {} if offsets is None else offsets
     self.connected = Bool(False)
     self.settings = settings
     self.control = control
     self.vAcc = -1
コード例 #5
0
ファイル: plugin.py プロジェクト: hibtc/hit_acs
 def set_window(self, window):
     self.window = window
     self.menu = window and window.acs_settings_menu
     if window is None:
         return
     from madgui.util.menu import extend, Item, Separator
     self.jitter = Bool(self._lib.jitter)
     self.auto_sd = Bool(self._lib.auto_sd)
     self.menu.clear()
     extend(window, self.menu, [
         Item('&Vary readouts',
              None,
              'Emulate continuous readouts using gaussian jitter',
              self._toggle_jitter,
              checked=self.jitter),
         Item('Add &magnet aberrations', None,
              'Add small deltas to all magnet strengths',
              self._lib._aberrate_strengths),
         Separator,
         Item('Autoset readouts from model',
              None,
              'Autoset monitor readout values from model twiss table',
              self._toggle_auto_sd,
              checked=self.auto_sd),
         Separator,
         Item('Load readouts from file', None,
              'Load monitor readout values from monitor export',
              self._open_sd_values),
         Item('Load strengths from file', None,
              'Load magnet strengths from strength export',
              self._open_float_values),
         Separator,
         Item(
             'Edit backend simulation model', None,
             'Edit initial conditions for the model used to simulate '
             'the backend behaviour',
             self._edit_model_initial_conditions.create),
     ])
コード例 #6
0
ファイル: qt.py プロジェクト: hibtc/madgui
 def __init__(self, construct):
     self.construct = construct
     self.holds_value = Bool(False)
コード例 #7
0
ファイル: qt.py プロジェクト: hibtc/madgui
class Property:

    """Internal class for cached properties. Should be simplified and
    rewritten. Currently only used as base class for ``SingleWindow``. Do not
    use for new code."""

    def __init__(self, construct):
        self.construct = construct
        self.holds_value = Bool(False)

    # porcelain

    @classmethod
    def factory(cls, func):
        return cachedproperty(functools.wraps(func)(
            lambda self: cls(func.__get__(self))))

    def create(self):
        if self._has:
            self._update()
        else:
            self._new()
        return self.val

    def destroy(self):
        if self._has:
            self._del()

    def toggle(self):
        if self._has:
            self._del()
        else:
            self._new()

    def _new(self):
        val = self.construct()
        self._set(val)
        return val

    def _update(self):
        pass

    @property
    def _has(self):
        return hasattr(self, '_val')

    def _get(self):
        return self._val

    def _set(self, val):
        self._val = val
        self.holds_value.set(True)

    def _del(self):
        del self._val
        self.holds_value.set(False)

    # use lambdas to enable overriding the _get/_set/_del methods
    # without having to redefine the 'val' property
    val = property(lambda self:      self._get(),
                   lambda self, val: self._set(val),
                   lambda self:      self._del())
コード例 #8
0
ファイル: control.py プロジェクト: CSRAccTeam/madgui
class Control:
    """
    Plugin class for MadGUI.

    When connected, the plugin can be used to access parameters in the online
    database. This works only if the corresponding parameters were named
    exactly as in the database and are assigned with the ":=" operator.
    """
    def __init__(self, session):
        self.session = session
        self.backend = None
        self.model = session.model
        self.sampler = BeamSampler(self)
        # menu conditions
        self.is_connected = Bool(False)
        self.has_backend = Bool(False)
        self.can_connect = ~self.is_connected & self.has_backend
        self.has_sequence = self.is_connected & self.model
        self._config = config = session.config.online_control
        self._settings = config['settings']
        self._on_model_changed()
        self.set_backend(config.backend)

    def set_backend(self, qualname):
        self.backend_spec = qualname
        self.has_backend.set(bool(qualname))

    # menu handlers

    def connect(self):
        qualname = self.backend_spec
        logging.info('Connecting online control: {}'.format(qualname))
        modname, clsname = qualname.split(':')
        mod = import_module(modname)
        cls = getattr(mod, clsname)
        self.backend = cls(self.session, self._settings)
        try:
            self.backend.connect()
            self.is_connected.set(True)
            self.session.user_ns.acs = self.backend
            self.model.changed.connect(self._on_model_changed)
            self._on_model_changed()
        except RuntimeError:
            logging.error('No connection to backend was possible')
            logging.error('Try to connect again')
            # Implemented since there has been reports of madgui
            # crashing after connecting and disconnecting from
            # beamOptikDLL. Should we try here to disconnect again
            # and implement a loop to try multiple times to connect?
            self.disconnect()

    def disconnect(self):
        self._settings = self.export_settings()
        self.session.user_ns.acs = None
        self.backend.disconnect()
        self.backend = None
        self.is_connected.set(False)
        self.model.changed.disconnect(self._on_model_changed)
        self._on_model_changed()

    def _on_model_changed(self, model=None):
        model = model or self.model()
        elems = self.is_connected() and model and model.elements or ()
        self.sampler.monitors = [
            elem.name for elem in elems
            if elem.base_name.lower().endswith('monitor')
            or elem.base_name.lower() == 'instrument'
        ]

    def export_settings(self):
        if hasattr(self.backend, 'export_settings'):
            return self.backend.export_settings()
        return self._settings

    def get_knobs(self):
        """Get dict of lowercase name → :class:`ParamInfo`."""
        if not self.model():
            return {}
        return {
            knob: info
            for knob in self.model().export_globals()
            for info in [self.backend.param_info(knob)] if info
        }

    # TODO: unify export/import dialog -> "show knobs"
    # TODO: can we drop the read-all button in favor of automatic reads?
    # (SetNewValueCallback?)
    def on_read_all(self):
        """Read all parameters from the online database."""
        from madgui.online.dialogs import ImportParamWidget
        self._show_sync_dialog(ImportParamWidget(), self.read_all)

    def on_write_all(self):
        """Write all parameters to the online database."""
        from madgui.online.dialogs import ExportParamWidget
        self._show_sync_dialog(ExportParamWidget(), self.write_all)

    def _show_sync_dialog(self, widget, apply):
        from madgui.online.dialogs import SyncParamItem
        from madgui.widget.dialog import Dialog
        model, live = self.model(), self.backend
        widget.data = [
            SyncParamItem(info, live.read_param(name), model.read_param(name))
            for name, info in self.get_knobs().items()
        ]
        widget.data_key = 'acs_parameters'
        dialog = Dialog(self.session.window())
        dialog.setExportWidget(widget, self.session.folder)
        dialog.serious.addCustomButton('Sync Model',
                                       self.on_sync_model(self, dialog))
        dialog.serious.updateButtons()
        dialog.accepted.connect(apply)
        dialog.show()
        return dialog

    class on_sync_model:
        def __init__(self, parent, widget):
            self.parent = parent
            self.widget = widget

        def __call__(self):
            self.onSyncModel()

        def onSyncModel(self):
            model = self.parent.backend.vAcc_to_model()
            self.parent.session.load_model(model)
            self.parent._on_model_changed()
            self.parent.on_read_all()
            self.widget.close()

    def read_all(self, knobs=None):
        live = self.backend
        self.model().write_params([(knob, live.read_param(knob))
                                   for knob in knobs or self.get_knobs()],
                                  "Read params from online control")

    def write_all(self, knobs=None):
        model = self.model()
        self.write_params([(knob, model.read_param(knob))
                           for knob in knobs or self.get_knobs()])

    def on_read_beam(self):
        # TODO: add confirmation dialog
        self.read_beam()

    def read_beam(self):
        self.model().update_beam(self.backend.get_beam())

    def read_monitor(self, name):
        return self.backend.read_monitor(name)

    @SingleWindow.factory
    def monitor_widget(self):
        """Read out SD values (beam position/envelope)."""
        from madgui.online.diagnostic import MonitorWidget
        return MonitorWidget(self.session)

    @SingleWindow.factory
    def orm_measure_widget(self):
        """Measure ORM for later analysis."""
        from madgui.widget.dialog import Dialog
        from madgui.online.orm_measure import MeasureWidget
        widget = MeasureWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_multi_grid_method(self):
        from madgui.widget.correct.multi_grid import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_optic_variation_method(self):
        from madgui.widget.correct.optic_variation import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_measured_response_method(self):
        from madgui.widget.correct.mor_dialog import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    # helper functions

    def write_params(self, params):
        write = self.backend.write_param
        for param, value in params:
            write(param, value)
        self.backend.execute()

    def read_param(self, name):
        return self.backend.read_param(name)
コード例 #9
0
ファイル: qt.py プロジェクト: CSRAccTeam/madgui
 def __init__(self, construct):
     self.construct = construct
     self.holds_value = Bool(False)
コード例 #10
0
ファイル: control.py プロジェクト: hibtc/madgui
class Control:

    """
    Plugin class for MadGUI.

    When connected, the plugin can be used to access parameters in the online
    database. This works only if the corresponding parameters were named
    exactly as in the database and are assigned with the ":=" operator.
    """

    def __init__(self, session):
        self.session = session
        self.backend = None
        self.model = session.model
        self.sampler = BeamSampler(self)
        # menu conditions
        self.is_connected = Bool(False)
        self.has_backend = Bool(False)
        self.can_connect = ~self.is_connected & self.has_backend
        self.has_sequence = self.is_connected & self.model
        self._config = config = session.config.online_control
        self._settings = config['settings']
        self._on_model_changed()
        self.set_backend(config.backend)

    def set_backend(self, qualname):
        self.backend_spec = qualname
        self.has_backend.set(bool(qualname))

    # menu handlers

    def connect(self):
        qualname = self.backend_spec
        logging.info('Connecting online control: {}'.format(qualname))
        modname, clsname = qualname.split(':')
        mod = import_module(modname)
        cls = getattr(mod, clsname)
        self.backend = cls(self.session, self._settings)
        self.backend.connect()
        self.session.user_ns.acs = self.backend
        self.is_connected.set(True)
        self.model.changed.connect(self._on_model_changed)
        self._on_model_changed()

    def disconnect(self):
        self._settings = self.export_settings()
        self.session.user_ns.acs = None
        self.backend.disconnect()
        self.backend = None
        self.is_connected.set(False)
        self.model.changed.disconnect(self._on_model_changed)
        self._on_model_changed()

    def _on_model_changed(self, model=None):
        model = model or self.model()
        elems = self.is_connected() and model and model.elements or ()
        self.sampler.monitors = [
            elem.name
            for elem in elems
            if elem.base_name.lower().endswith('monitor')
            or elem.base_name.lower() == 'instrument'
        ]

    def export_settings(self):
        if hasattr(self.backend, 'export_settings'):
            return self.backend.export_settings()
        return self._settings

    def get_knobs(self):
        """Get dict of lowercase name → :class:`ParamInfo`."""
        if not self.model():
            return {}
        return {
            knob: info
            for knob in self.model().export_globals()
            for info in [self.backend.param_info(knob)]
            if info
        }

    # TODO: unify export/import dialog -> "show knobs"
    # TODO: can we drop the read-all button in favor of automatic reads?
    # (SetNewValueCallback?)
    def on_read_all(self):
        """Read all parameters from the online database."""
        from madgui.online.dialogs import ImportParamWidget
        self._show_sync_dialog(ImportParamWidget(), self.read_all)

    def on_write_all(self):
        """Write all parameters to the online database."""
        from madgui.online.dialogs import ExportParamWidget
        self._show_sync_dialog(ExportParamWidget(), self.write_all)

    def _show_sync_dialog(self, widget, apply):
        from madgui.online.dialogs import SyncParamItem
        from madgui.widget.dialog import Dialog
        model, live = self.model(), self.backend
        widget.data = [
            SyncParamItem(info, live.read_param(name), model.read_param(name))
            for name, info in self.get_knobs().items()
        ]
        widget.data_key = 'acs_parameters'
        dialog = Dialog(self.session.window())
        dialog.setExportWidget(widget, self.session.folder)
        dialog.serious.updateButtons()
        dialog.accepted.connect(apply)
        dialog.show()
        return dialog

    def read_all(self, knobs=None):
        live = self.backend
        self.model().write_params([
            (knob, live.read_param(knob))
            for knob in knobs or self.get_knobs()
        ], "Read params from online control")

    def write_all(self, knobs=None):
        model = self.model()
        self.write_params([
            (knob, model.read_param(knob))
            for knob in knobs or self.get_knobs()
        ])

    def on_read_beam(self):
        # TODO: add confirmation dialog
        self.read_beam()

    def read_beam(self):
        self.model().update_beam(self.backend.get_beam())

    def read_monitor(self, name):
        return self.backend.read_monitor(name)

    @SingleWindow.factory
    def monitor_widget(self):
        """Read out SD values (beam position/envelope)."""
        from madgui.online.diagnostic import MonitorWidget
        return MonitorWidget(self.session)

    @SingleWindow.factory
    def orm_measure_widget(self):
        """Measure ORM for later analysis."""
        from madgui.widget.dialog import Dialog
        from madgui.online.orm_measure import MeasureWidget
        widget = MeasureWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_multi_grid_method(self):
        from madgui.widget.correct.multi_grid import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_optic_variation_method(self):
        from madgui.widget.correct.optic_variation import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    def on_correct_measured_response_method(self):
        from madgui.widget.correct.mor_dialog import CorrectorWidget
        from madgui.widget.dialog import Dialog
        self.read_all()
        widget = CorrectorWidget(self.session)
        return Dialog(self.session.window(), widget)

    # helper functions

    def write_params(self, params):
        write = self.backend.write_param
        for param, value in params:
            write(param, value)
        self.backend.execute()

    def read_param(self, name):
        return self.backend.read_param(name)
コード例 #11
0
ファイル: plugin.py プロジェクト: hibtc/hit_acs
class _HitACS(api.Backend):
    def __init__(self,
                 lib,
                 params,
                 model=None,
                 offsets=None,
                 settings=None,
                 control=None):
        self._lib = lib
        self._params = dicti({
            'beam_energy':
            dict(name='beam_energy',
                 ui_name='beam_energy',
                 ui_hint='',
                 ui_prec=3,
                 unit='MeV/u',
                 ui_unit='MeV/u',
                 ui_conv=1),
            'beam_focus':
            dict(name='beam_focus',
                 ui_name='beam_focus',
                 ui_hint='',
                 ui_prec=3,
                 unit='m',
                 ui_unit='mm',
                 ui_conv=1000),
            'beam_intensity':
            dict(name='beam_intensity',
                 ui_name='beam_intensity',
                 ui_hint='',
                 ui_prec=3,
                 unit='',
                 ui_unit='',
                 ui_conv=1),
            'gantry_angle':
            dict(name='gantry_angle',
                 ui_name='gantry_angle',
                 ui_hint='',
                 ui_prec=3,
                 unit='°',
                 ui_unit='°',
                 ui_conv=1),
        })
        self._params.update(params)
        self._model = model
        self._offsets = {} if offsets is None else offsets
        self.connected = Bool(False)
        self.settings = settings
        self.control = control
        self.vAcc = -1

    @property
    def beamoptikdll(self):
        """Python wrapper for the BeamOptikDLL exposed methods."""
        return self._lib

    # Backend API

    def connect(self):
        """Connect to online database (must be loaded)."""
        status = self._lib.GetInterfaceInstance()
        logging.debug('Conection status: {}'.format(status))
        self.connected.set(True)

    def disconnect(self):
        """Disconnect from online database."""
        (self.settings or {}).update(self.export_settings())
        self._lib.FreeInterfaceInstance()
        self.connected.set(False)

    def export_settings(self):
        """Updates the settings yaml file for future loggins"""
        mefi = self._lib.GetMEFIValue()[1]
        settings = {
            'variant': self._lib._variant,
            'vacc': self._lib.GetSelectedVAcc(),
            'mefi': mefi and tuple(mefi),
        }
        if hasattr(self._lib, 'export_settings'):
            settings.update(self._lib.export_settings())
        return settings

    def execute(self, options=ExecOptions.CalcDif):
        """Execute changes (commits prior set_value operations)."""
        self._lib.ExecuteChanges(options)

    def param_info(self, knob):
        """Get parameter info for backend key."""
        data = self._params.get(knob)
        return data and api.ParamInfo(**data)

    def read_monitor(self, name):
        """
        Read out one monitor, return values as dict with keys
        posx/posy/envx/envy.
        """
        # TODO: Handle usability of parameters individually
        try:
            GetFloatValueSD = self._lib.GetFloatValueSD
            posx = GetFloatValueSD('posx_' + name)
            posy = GetFloatValueSD('posy_' + name)
            envx = GetFloatValueSD('widthx_' + name)
            envy = GetFloatValueSD('widthy_' + name)
        except RuntimeError:
            return {}
        # TODO: move sanity check to later, so values will simply be
        # unchecked/grayed out, instead of removed completely
        # The magic number -9999.0 signals corrupt values.
        # FIXME: Sometimes width=0 is returned. ~ Meaning?
        if posx == -9999 or posy == -9999 or envx <= 0 or envy <= 0:
            return {}
        xoffs, yoffs = self._offsets.get(name, (0, 0))
        return {
            'posx': -(posx / 1000 + xoffs),
            'posy': +(posy / 1000 + yoffs),
            'envx': envx / 1000,
            'envy': envy / 1000,
        }

    def read_params(self, param_names=None, warn=True):
        """Read all specified params (by default all). Return dict."""
        if param_names is None:
            param_names = self._params
            warn = False
        return {
            param: value
            for param in param_names
            for value in [self.read_param(param, warn=warn)]
            if value is not None
        }

    def read_param(self, param, warn=True):
        """Read parameter. Return numeric value."""
        param = param.lower()
        if param in MEFI_PARAMS:
            return self._lib.GetMEFIValue()[0][MEFI_PARAMS.index(param)]
        try:
            return self._lib.GetFloatValue(param)
        except RuntimeError as e:
            if warn:
                logging.warning("{} for {!r}".format(e, param))

    def write_param(self, param, value):
        """Update parameter into control system."""
        param = param.lower()
        if param in MEFI_PARAMS:
            cur_value = self.read_param(param)
            if value != cur_value:
                logging.warning(
                    "Unable to set {}={} (is {}). This parameter "
                    "can only be changed by selecting the MEFI combination!".
                    format(param, value, cur_value))
            return
        try:
            self._lib.SetFloatValue(param, value)
        except RuntimeError as e:
            logging.error("{} for {!r} = {}".format(e, param, value))

    def get_beam(self):
        units = unit.units
        e_para = ENERGY_PARAM.get(self._model().seq_name, 'E_HEBT')
        z_num = self._lib.GetFloatValue('Z_POSTSTRIP')
        mass = self._lib.GetFloatValue('A_POSTSTRIP') * units.u
        charge = self._lib.GetFloatValue('Q_POSTSTRIP') * units.e
        e_kin = (self._lib.GetFloatValue(e_para) or 1) * units.MeV / units.u
        return {
            'particle': PERIODIC_TABLE[round(z_num)],
            'charge': unit.from_ui('charge', charge),
            'mass': unit.from_ui('mass', mass),
            'energy': unit.from_ui('energy', mass * (e_kin + 1 * units.c**2)),
        }

    def get_MEFI(self):
        mefi = self._lib.GetMEFIValue()[1]
        return mefi and tuple(mefi)

    def vAcc_to_model(self):
        """User defined vAcc to model"""
        vAcc = self.vAcc = self._lib.GetSelectedVAcc()
        _isStdVacc = False

        if vAcc in np.arange(16):
            _isStdVacc = True
            logging.info('Loading model with vAcc {}'.format(vAcc))
            for bL in VACC_TABLE:
                beamLine = VACC_TABLE[bL]
                if self.vAcc in beamLine[0]:
                    return beamLine[1], _isStdVacc

        if vAcc == -1.:
            logging.warning('Please select a vAcc.')
            return self.model().model_data()['sequence'], _isStdVacc

        logging.warning('vAcc is not standard. Load model manually.')
        return self.model().model_data()['sequence'], _isStdVacc
コード例 #12
0
ファイル: plugin.py プロジェクト: hibtc/hit_acs
class TestACS(_HitACS):
    def __init__(self, session, settings):
        params = load_dvm_parameters()
        offsets = find_offsets(settings.get('runtime_path', '.'))
        # Don't pass `session.model()` to the stub. It should use an
        # independent simulation, which is cloned upon connection in
        # `on_model_changed`:
        lib = session.user_ns.beamoptikdll = BeamOptikStub(
            None, offsets, settings)
        super().__init__(lib,
                         params,
                         session.model,
                         offsets,
                         control=session.control)
        self.menu = None
        self.window = None
        self.set_window(session.window())
        self.connected.changed.connect(self.on_connected_changed)

        self.str_file = settings.get('str_file')
        self.sd_file = settings.get('sd_file')

    def load_float_values(self, filename):
        from madgui.util.export import read_str_file
        self.str_file = filename = os.path.abspath(filename)
        self._lib.set_float_values(read_str_file(filename))

    def load_sd_values(self, filename):
        from madgui.util.yaml import load_file
        self.sd_file = filename = os.path.abspath(filename)
        data = load_file(filename)
        cols = {
            'envx': 'widthx',
            'envy': 'widthy',
            'x': 'posx',
            'y': 'posy',
        }
        self.auto_sd.set(False)
        self._lib.set_sd_values({
            cols[param] + '_' + elem: value
            for elem, values in data['monitor'].items()
            for param, value in values.items()
        })

    def set_window(self, window):
        self.window = window
        self.menu = window and window.acs_settings_menu
        if window is None:
            return
        from madgui.util.menu import extend, Item, Separator
        self.jitter = Bool(self._lib.jitter)
        self.auto_sd = Bool(self._lib.auto_sd)
        self.menu.clear()
        extend(window, self.menu, [
            Item('&Vary readouts',
                 None,
                 'Emulate continuous readouts using gaussian jitter',
                 self._toggle_jitter,
                 checked=self.jitter),
            Item('Add &magnet aberrations', None,
                 'Add small deltas to all magnet strengths',
                 self._lib._aberrate_strengths),
            Separator,
            Item('Autoset readouts from model',
                 None,
                 'Autoset monitor readout values from model twiss table',
                 self._toggle_auto_sd,
                 checked=self.auto_sd),
            Separator,
            Item('Load readouts from file', None,
                 'Load monitor readout values from monitor export',
                 self._open_sd_values),
            Item('Load strengths from file', None,
                 'Load magnet strengths from strength export',
                 self._open_float_values),
            Separator,
            Item(
                'Edit backend simulation model', None,
                'Edit initial conditions for the model used to simulate '
                'the backend behaviour',
                self._edit_model_initial_conditions.create),
        ])

    def export_settings(self):
        return {
            'jitter': self.jitter(),
            'shot_interval': self._lib.sd_cache.timeout,
            'auto_sd': self.auto_sd(),
            'str_file': self.str_file and safe_relpath(self.str_file, None),
            'sd_file': self.str_file and safe_relpath(self.sd_file, None),
        }

    def _toggle_jitter(self):
        self.jitter.set(not self.jitter())
        self._lib.jitter = self.jitter()

    def _toggle_auto_sd(self):
        self.auto_sd.set(not self.auto_sd())
        self._lib.auto_sd = self.auto_sd()
        self._lib.update_sd_values()

    def _open_sd_values(self):
        from madgui.widget.filedialog import getOpenFileName
        filters = [
            ("YAML files", "*.yml", "*.yaml"),
            ("All files", "*"),
        ]
        folder = self.window.str_folder or self.window.folder
        if self.sd_file:
            folder = os.path.dirname(self.sd_file)
        filename = getOpenFileName(self.window, 'Open monitor export', folder,
                                   filters)
        if filename:
            self.load_sd_values(filename)

    def _open_float_values(self):
        from madgui.widget.filedialog import getOpenFileName
        filters = [
            ("STR files", "*.str"),
            ("All files", "*"),
        ]
        folder = self.window.str_folder or self.window.folder
        if self.str_file:
            folder = os.path.dirname(self.str_file)
        filename = getOpenFileName(self.window, 'Open strength export', folder,
                                   filters)
        if filename:
            self.load_float_values(filename)

    def on_connected_changed(self, connected):
        if connected:
            self.model.changed.connect(self.on_model_changed)
            self.on_model_changed(self.model())
        else:
            self.model.changed.disconnect(self.on_model_changed)
        if self.menu:
            self.menu.setEnabled(connected)

    def on_model_changed(self, model):
        clone = model and model.load_file(model.filename, stdout=False)
        self._lib.set_model(clone)
        if clone:
            if self.str_file:
                self.load_float_values(self.str_file)
            if self.sd_file:
                self.load_sd_values(self.sd_file)

    @SingleWindow.factory
    def _edit_model_initial_conditions(self):
        from madgui.widget.params import model_params_dialog
        return model_params_dialog(self._lib.model,
                                   parent=self.window,
                                   folder=self.window.folder)

    @property
    def model(self):
        return self._model