def crop_cells(self): rows = self.chest.root.cell_description.nrows if rows > 0: # remove the table self.chest.removeNode('/cell_description') try: # remove the table of peak characteristics - they are not valid. self.chest.removeNode('/cell_peaks') except: pass # recreate it self.chest.createTable('/', 'cell_description', CellsTable) # remove all existing entries in the data group for node in self.chest.listNodes('/cells'): self.chest.removeNode('/cells/' + node.name) # store the template template_data = self.template_data.get_data('imagedata') self.parent.add_cell_data(template_data, name="template") # TODO: set attribute that tells where the template came from row = self.chest.root.cell_description.row files=[] for idx in xrange(self.numfiles): # filter the peaks that are outside the selected threshold self.set_active_index(idx) active_image = self.get_active_image() peaks=np.ma.compress_rows(self.mask_peaks(self.get_active_name())) files.append(self.get_active_name()) tmp_sz=self.template_size data=np.zeros((peaks.shape[0],tmp_sz,tmp_sz), dtype=active_image.dtype) if data.shape[0] >0: for i in xrange(peaks.shape[0]): # store the peak in the table row['file_idx'] = i row['input_data'] = self.data_path row['filename'] = self.get_active_name() row['x_coordinate'] = peaks[i, 1] row['y_coordinate'] = peaks[i, 0] row.append() # crop the cells from the given locations data[i,:,:]=active_image[peaks[i, 1]:peaks[i, 1] + tmp_sz, peaks[i, 0]:peaks[i, 0] + tmp_sz] self.chest.root.cell_description.flush() self.parent.add_cell_data(data, name=self.get_active_name()) # insert the data (one 3d array per file) self.chest.setNodeAttr('/cell_description', 'threshold', (self.thresh_lower, self.thresh_upper)) self.chest.setNodeAttr('/cell_description', 'template_position', (self.template_left, self.template_top)) self.chest.setNodeAttr('/cell_description', 'template_filename', self.template_filename) self.chest.setNodeAttr('/cell_description', 'template_size', (self.template_size)) self.chest.root.cell_description.flush() self.chest.flush() average_data = np.average(data,axis=0).squeeze() self.parent.add_cell_data(average_data, name="average") self.parent.update_cell_data() self.log_action(action="crop cells", files=files, thresh=self.thresh, template_position=(self.template_left, self.template_top), template_size=self.template_size, template_filename=self.template_filename) Application.instance().end_session(self._session_id)
def activate_proxy(self): """ Activate the proxy object tree. This method should be called by a node to activate the proxy tree by making two initialization passes over the tree, from this node downward. This method is automatically at the proper times and should not normally need to be invoked by user code. """ self.activate_top_down() for child in self.children: # Make sure each is initialized upon activation if not child.is_initialized: child.initialize() if isinstance(child, ToolkitObject): if not child.proxy_is_active: child.activate_proxy() # Generating the model can take a lot of time # so process events inbetween to keep the UI from freezing Application.instance().process_events() self.activate_bottom_up() self.proxy_is_active = True self.activated()
def _create_application(self): """ Create the Application object for the ui. This will load the highest ranking extension to the application factory extension point, and use it to create the instance. If an application object already exists, that application will be used instead of any defined by a factory, since there can be only one application per-process. """ if Application.instance() is not None: self._application = Application.instance() return workbench = self.workbench point = workbench.get_extension_point(APPLICATION_FACTORY_POINT) extensions = point.extensions if not extensions: msg = "no contributions to the '%s' extension point" raise RuntimeError(msg % APPLICATION_FACTORY_POINT) extension = extensions[-1] if extension.factory is None: msg = "extension '%s' does not declare an application factory" raise ValueError(msg % extension.qualified_id) application = extension.factory() if not isinstance(application, Application): msg = "extension '%s' created non-Application type '%s'" args = (extension.qualified_id, type(application).__name__) raise TypeError(msg % args) self._application = application
def open_save_UI(self, plot_id='plot'): save_controller = SaveFileController(plot=self.get_plot(plot_id), parent=self) save_dialog = simple_session('save', 'Save dialog', SavePlotDialog, controller=save_controller) Application.instance().add_factories([save_dialog]) session_id = Application.instance().start_session('save') save_controller._session_id = session_id
def open_crop_UI(self): crop_controller = CellCropController(parent=self, treasure_chest=self.chest) cell_cropper = simple_session('cropper', 'Cell cropper', CellCropperInterface, controller=crop_controller) Application.instance().add_factories([cell_cropper]) session_id = Application.instance().start_session('cropper') crop_controller._session_id = session_id
def open_MDA_UI(self): mda_controller = MDAExecutionController(parent=self, treasure_chest=self.chest) mda_dialog = simple_session('mda', 'MDA dialog', MDAInterface, controller=mda_controller) Application.instance().add_factories([mda_dialog]) session_id = Application.instance().start_session('mda') mda_controller._session_id = session_id
def save_plot(self, filename): self._save_plot(self.plot, filename, self.plot_width, self.plot_height, self.dpi) self.parent.log_action("save plot", controller=self.parent.__class__.__name__, dpi=self.dpi, width=self.plot_width, height=self.plot_height, filename=filename) Application.instance().end_session(self._session_id)
def test_cache_1(): from enaml.application import Application from enaml.styling import StyleCache source = dedent("""\ from enaml.widgets.api import Window, Container, PushButton from enaml.styling import StyleSheet, Style, Setter enamldef Sheet(StyleSheet): Style: element = 'PushButton' Setter: field = 'background' value = 'blue' enamldef Main(Window): alias button Sheet: pass Container: PushButton: button: pass """) _clear_cache() app = Application.instance() if app is not None: app.style_sheet = None main = compile_source(source, 'Main')() styles = StyleCache.styles(main.button) assert len(styles) == 1 assert not _cache_items_empty() assert not _cache_styles_empty() main.destroy() assert _cache_items_empty() assert _cache_styles_empty()
def custom_color(index): """ Get the custom color for the given index. The custom colors are shared among all color dialogs. Parameters ---------- index : int The integer index of the custom color. Returns ------- result : Color The custom color for the index. Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: return proxy_cls.custom_color(index) return Color(255, 255, 255)
def _send_relayout(self): """ Send the 'relayout' action to the client widget. If an Enaml Application instance exists, then multiple `relayout` actions will be collapsed into a single action that will be sent on the next cycle of the event loop. If no application exists, then the action is sent immediately. """ # The relayout action is deferred until the next cycle of the # event loop for two reasons: 1) So that multiple relayout # requests can be collapsed into a single action. 2) So that # all child events (which are fired synchronously) can finish # processing and send their actions to the client before the # relayout request is sent. The action itself is batched so # that it can be sent along with any object tree changes. app = Application.instance() if app is not None: task = self._layout_task if task is None: def notifier(ignored): self._layout_task = None def layout_task(): self.batch_action('relayout', self._layout_info()) task = app.schedule(layout_task) task.notify(notifier) self._layout_task = task
def _app_style_sheet(): app = Application.instance() if app is not None: sheet = app.style_sheet if sheet is not None and not sheet.is_initialized: sheet.initialize() return sheet
def execute(self): method = self.methods[self.selected_method_idx] try: if method == 'PCA': self.PCA(n_components=self.number_to_derive) elif method == 'ICA': self.ICA(n_components=self.number_to_derive) self.chest.set_node_attr('/mda_results/'+self.context, 'dimensionality', self.number_to_derive) # close the dialog window Application.instance().end_session(self._session_id) # add to the log file self.parent.log_action(action=method, n_components = self.number_to_derive, on_peaks = self.on_peaks, backend=mda.name) # show the results windows self.parent.update_mda_data() except RuntimeError: print sys.exc_info()[0] print "MDA not executed."
def execute(self): method = self.methods[self.selected_method_idx] try: if method == 'PCA': self.PCA(n_components=self.number_to_derive) elif method == 'ICA': self.ICA(n_components=self.number_to_derive) self.chest.set_node_attr('/mda_results/' + self.context, 'dimensionality', self.number_to_derive) # close the dialog window Application.instance().end_session(self._session_id) # add to the log file self.parent.log_action(action=method, n_components=self.number_to_derive, on_peaks=self.on_peaks, backend=mda.name) # show the results windows self.parent.update_mda_data() except RuntimeError: print sys.exc_info()[0] print "MDA not executed."
def initialize(self): """ A reimplemented initializer. This initializer will invoke the application to create the proxy if one has not already been provided. """ if not self.proxy: app = Application.instance() if app is None: msg = 'cannot create a proxy without an active Application' raise RuntimeError(msg) self.proxy = app.create_proxy(self) super(ToolkitObject, self).initialize()
def enaml_run(enaml_qtbot, monkeypatch): """ Patches the QtApplication to allow using the qtbot when the enaml application is started. It also patches QApplication.exit as recommended in the pytest-qt docs. Yields ------- handler: object an object with a `run` attribute that can be set to a callback that will be invoked with the application and first window shown. References ---------- 1. https://pytest-qt.readthedocs.io/en/latest/app_exit.html """ from enaml.qt.qt_application import QtApplication, QApplication app = Application.instance() if app: Application._instance = None class Runner: # Set this to a callback run = None runner = Runner() def start(self): for window in Window.windows: wait_for_window_displayed(enaml_qtbot, window) if callable(runner.run): runner.run(self, window) else: close_window_or_popup(enaml_qtbot, window) break try: with monkeypatch.context() as m: m.setattr(QtApplication, 'start', start) m.setattr(QApplication, 'exit', lambda self: None) yield runner finally: Application._instance = app
def enaml_run(qtbot): """ Patch the start method to use the qtbot """ app = Application.instance() if app: Application._instance = None _start = QtApplication.start def start(self): for window in Window.windows: qtbot.wait_for_window_shown(window.proxy.widget) qtbot.wait(1000) window.close() break QtApplication.start = start try: yield finally: QtApplication.start = _start Application._instance = app
def custom_count(): """ Get the number of available custom colors. The custom colors are shared among all color dialogs. Returns ------- result : int The number of available custom colors. Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: return proxy_cls.custom_count() return 0
def set_custom_color(index, color): """ Set the custom color for the given index. The custom colors are shared among all color dialogs. Parameters ---------- index : int The integer index of the custom color. color : Color The custom color to set for the index Notes ----- The Application object must exist before calling this method. """ app = Application.instance() assert app is not None, 'the application object does not exist' proxy_cls = app.resolve_proxy_class(ColorDialog) if proxy_cls is not None: proxy_cls.set_custom_color(index, color)
def _default_app(self): return Application.instance()
def test_cascade(): source = dedent("""\ from enaml.widgets.api import Window, Container, PushButton from enaml.styling import StyleSheet, Style, Setter enamldef Sheet1(StyleSheet): Style: style_class = 'yellow' Setter: field = 'background' value = 'yellow' Style: element = 'PushButton' Setter: field = 'background' value = 'red' enamldef Sheet2(StyleSheet): Style: style_class = 'cyan' Setter: field = 'background' value = 'cyan' Style: element = 'PushButton' Setter: field = 'background' value = 'green' enamldef Sheet3(StyleSheet): Style: style_class = 'magenta' Setter: field = 'background' value = 'magenta' Style: element = 'PushButton' Setter: field = 'background' value = 'blue' enamldef Main(Window): alias one alias two alias three alias four alias five alias six alias seven alias eight alias nine alias ten alias eleven Sheet2: pass Container: PushButton: one: pass PushButton: two: style_class = 'yellow' PushButton: three: style_class = 'cyan' PushButton: four: Sheet3: pass PushButton: five: style_class = 'yellow' Sheet3: pass PushButton: six: style_class = 'cyan' Sheet3: pass PushButton: seven: style_class = 'magenta' Sheet3: pass Container: Sheet3: pass PushButton: eight: pass PushButton: nine: style_class = 'yellow' PushButton: ten: style_class = 'cyan' PushButton: eleven: style_class = 'magenta' def init(): from enaml.application import Application app = Application() app.style_sheet = Sheet1() return Main() """) old_instance = None if Application.instance(): old_instance = Application._instance Application._instance = None try: main = compile_source(source, 'init')() _assert_setters(main.one, ( ('background', 'red'), ('background', 'green'), )) _assert_setters(main.two, ( ('background', 'red'), ('background', 'yellow'), ('background', 'green'), )) _assert_setters(main.three, ( ('background', 'red'), ('background', 'green'), ('background', 'cyan'), )) _assert_setters(main.four, ( ('background', 'red'), ('background', 'green'), ('background', 'blue'), )) _assert_setters(main.five, ( ('background', 'red'), ('background', 'yellow'), ('background', 'green'), ('background', 'blue'), )) _assert_setters(main.six, ( ('background', 'red'), ('background', 'green'), ('background', 'cyan'), ('background', 'blue'), )) _assert_setters(main.seven, ( ('background', 'red'), ('background', 'green'), ('background', 'blue'), ('background', 'magenta'), )) _assert_setters(main.eight, ( ('background', 'red'), ('background', 'green'), ('background', 'blue'), )) _assert_setters(main.nine, ( ('background', 'red'), ('background', 'yellow'), ('background', 'green'), ('background', 'blue'), )) _assert_setters(main.ten, ( ('background', 'red'), ('background', 'green'), ('background', 'cyan'), ('background', 'blue'), )) _assert_setters(main.eleven, ( ('background', 'red'), ('background', 'green'), ('background', 'blue'), ('background', 'magenta'), )) finally: Application._instance = old_instance
def crop_cells(self): rows = self.chest.root.cell_description.nrows if rows > 0: # remove the table self.chest.remove_node('/cell_description') try: # remove the table of peak characteristics - they are not valid. self.chest.remove_node('/cell_peaks') except: pass # recreate it self.chest.create_table('/', 'cell_description', CellsTable) # remove all existing entries in the data group for node in self.chest.list_nodes('/cells'): self.chest.remove_node('/cells/' + node.name) # store the template template_data = self.template_data.get_data('imagedata') self.parent.add_cell_data(template_data, name="template") # TODO: set attribute that tells where the template came from row = self.chest.root.cell_description.row files = [] for idx in xrange(self.numfiles): # filter the peaks that are outside the selected threshold self.set_active_index(idx) active_image = self.get_active_image() peaks = np.ma.compress_rows(self.mask_peaks( self.get_active_name())) files.append(self.get_active_name()) tmp_sz = self.template_size data = np.zeros((peaks.shape[0], tmp_sz, tmp_sz), dtype=active_image.dtype) if data.shape[0] > 0: for i in xrange(peaks.shape[0]): # store the peak in the table row['file_idx'] = i row['input_data'] = self.data_path row['filename'] = self.get_active_name() row['x_coordinate'] = peaks[i, 0] row['y_coordinate'] = peaks[i, 1] row.append() # crop the cells from the given locations data[i, :, :] = active_image[ int(peaks[i, 0]):int(peaks[i, 0] + tmp_sz), int(peaks[i, 1]):int(peaks[i, 1] + tmp_sz)] self.chest.root.cell_description.flush() self.parent.add_cell_data(data, name=self.get_active_name()) # insert the data (one 3d array per file) self.chest.set_node_attr( '/cell_description', 'threshold', (self.thresh_lower, self.thresh_upper)) self.chest.set_node_attr( '/cell_description', 'template_position', (self.template_left, self.template_top)) self.chest.set_node_attr('/cell_description', 'template_filename', self.template_filename) self.chest.set_node_attr('/cell_description', 'template_size', (self.template_size)) self.chest.root.cell_description.flush() self.chest.flush() average_data = np.average(data, axis=0).squeeze() self.parent.add_cell_data(average_data, name="average") row = self.chest.root.cell_description.row row['file_idx'] = 0 row['input_data'] = self.data_path row['filename'] = "average" row['x_coordinate'] = 0 row['y_coordinate'] = 0 row.append() self.chest.root.cell_description.flush() self.parent.update_cell_data() self.parent.add_image_data(average_data, "average") self.log_action(action="crop cells", files=files, thresh=self.thresh, template_position=(self.template_left, self.template_top), template_size=self.template_size, template_filename=self.template_filename) Application.instance().end_session(self._session_id)
def _do_update(self): # Only update when all changes are done self._update_count -= 1 if self._update_count != 0: return qtapp = Application.instance()._qapp start_time = datetime.now() self.declaration.loading = True try: view = self.v3d_view self.clear_display() displayed_shapes = {} ais_shapes = [] log.debug("Rendering...") #: Expand all parts otherwise we lose the material information self.dimensions = set() shapes = self._expand_shapes(self.children(), self.dimensions) if not shapes: log.debug("No shapes to display") return self.set_selection_mode(self.declaration.selection_mode) n = len(shapes) for i, occ_shape in enumerate(shapes): qtapp.processEvents() if self._update_count != 0: log.debug("Aborted!") return # Another update coming abort d = occ_shape.declaration topods_shape = occ_shape.shape if not topods_shape or topods_shape.IsNull(): log.error("{} has no shape!".format(occ_shape)) continue # Translate part locations parent = occ_shape.parent() if parent and isinstance(parent, OccPart) \ and not topods_shape.Locked(): # Build transform for nested parts l = topods_shape.Location() while isinstance(parent, OccPart): l = parent.location.Multiplied(l) parent = parent.parent() topods_shape.Location(l) # HACK: Prevent doing this multiple times when the view is # force updated and the same part is rendered topods_shape.Locked(True) # Save the mapping of topods_shape to declaracad shape displayed_shapes[topods_shape] = occ_shape progress = self.declaration.progress = min( 100, max(0, i * 100 / n)) #log.debug("Displaying {} ({}%)".format( # topods_shape, round(progress, 2))) ais_shape = self.display_shape( topods_shape, d.color, d.transparency, d.material if d.material.name else None, d.texture, update=False) occ_shape.ais_shape = ais_shape if ais_shape: ais_shapes.append(ais_shape) # Display all dimensions log.debug("Adding {} dimensions...".format(len(self.dimensions))) displayed_dimensions = {} for item in self.dimensions: dim = item.dimension if dim is not None and item.declaration.display: displayed_dimensions[dim] = item self.display_ais(dim, update=False) # Update self.ais_context.UpdateCurrentViewer() self._displayed_shapes = displayed_shapes self._displayed_dimensions = displayed_dimensions # Update bounding box bbox = self.get_bounding_box(displayed_shapes.keys()) self.declaration.bbox = BBox(*bbox) log.debug("Took: {}".format(datetime.now() - start_time)) self.declaration.progress = 100 except: log.error("Failed to display shapes: {}".format( traceback.format_exc())) finally: self.declaration.loading = False
def safe_call(func, *args, **kwargs): """utility function for safely calling functions that doesn't return anything""" if Application.instance() is None: return func(*args, **kwargs) deferred_call(func, *args, **kwargs)
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()
def safe_setattr(obj, name, value): if Application.instance() is None: return setattr(obj, name, value) deferred_call(setattr, obj, name, value)
def safe_getattr(obj, name, default=None): if Application.instance() is None: return getattr(obj, name, default) return schedule(getattr, args=(obj, name, default))
def _app_style_sheet(): app = Application.instance() if app is not None: return app.style_sheet