def _make_delay_tab(box_factory, selected_index=0): """ Parameters ---------- box_factory : list of (func, tab_name) Example of box_factory: [(_make_gen_box, 'General'), (_make_repr_box, 'Representation')] """ tab = Tab([Box() for box, _ in box_factory]) [tab.set_title(i, title) for i, (_, title) in enumerate(box_factory)] # trick if not tab.children[selected_index].children: tab.selected_index = -1 def on_update_selected_index(change): index = change['new'] if not tab.children[index].children: # make widget on demand tab.children[index].children = [box_factory[index][0](),] tab.observe(on_update_selected_index, names='selected_index') # trigger tab.selected_index = selected_index return tab
def _make_delay_tab(box_factory, selected_index=0): """ Parameters ---------- box_factory : list of (func, tab_name) Example of box_factory: [(_make_gen_box, 'General'), (_make_repr_box, 'Representation')] """ tab = Tab([Box() for box, _ in box_factory]) [tab.set_title(i, title) for i, (_, title) in enumerate(box_factory)] # trick if not tab.children[selected_index].children: tab.selected_index = 1 def on_update_selected_index(change): index = change['new'] if not tab.children[index].children: # make widget on demand tab.children[index].children = [ box_factory[index][0](), ] tab.observe(on_update_selected_index, names='selected_index') # trigger tab.selected_index = selected_index return tab
class Map(ipyleaflet.Map): def __init__(self, **kwargs): # Change defaults kwargs.setdefault('center', [0, 0]) kwargs.setdefault('zoom', 2) super(Map, self).__init__(**kwargs) # self.added_geometries = {} # self.added_images = {} self.is_shown = False self.EELayers = {} # CREATE TABS self.tabs = Tab() tab_names = ['Inspector', 'Assets', 'Tasks'] ## widgets self.inspectorWid = Accordion() # Inspector Widget self.assetsWid = Accordion() # Assets Widget self.tasksWid = HTML() # Tasks Widget childrenName = ['Inspector', 'Assets', 'Tasks'] childrenWid = [self.inspectorWid, self.assetsWid, self.tasksWid] # Dictonary to hold tab's widgets # (tab's name:widget) self.childrenDict = OrderedDict(zip(childrenName, childrenWid)) # Set tabs children self.tabs.children = self.childrenDict.values() # Set tabs names for i, name in enumerate(tab_names): self.tabs.set_title(i, name) # Handlers self.tabs.observe(self.handle_change_tab) self.handlers = {'Inspector': self.handle_inspector} # First handler: Inspector self.on_interaction(self.handlers['Inspector']) @property def added_images(self): return sum( [1 for val in self.EELayers.values() if val['type'] == 'Image']) @property def added_geometries(self): return sum( [1 for val in self.EELayers.values() if val['type'] == 'Geometry']) def create_assets_tab(self): # ASSETS TAB # Get assets root rootid = ee.data.getAssetRoots()[0]['id'] assets_list = ee.data.getList({'id': rootid}) widlist = [] namelist = [] for asset in assets_list: wid = HTML('') widlist.append(wid) name = asset['id'].split('/')[-1] ty = asset['type'] namelist.append('{} ({})'.format(name, ty)) self.assetsWid.children = widlist for i, name in enumerate(namelist): self.assetsWid.set_title(i, name) def show(self, inspector=True): """ Show the Map on the Notebook """ if not self.is_shown: # Layers Control lc = ipyleaflet.LayersControl() self.add_control(lc) self.is_shown = True if inspector: # Create Assets Tab self.create_assets_tab() # Display display(self, self.tabs) else: display(self) elif inspector: display(self, self.tabs) else: display(self) def addLayer(self, eeObject, visParams=None, name=None, show=True, opacity=None, inspect={ 'data': None, 'reducer': None, 'scale': None }): """ Adds a given EE object to the map as a layer. :param eeObject: Earth Engine object to add to map :type eeObject: ee.Image || ee.Geometry || ee.Feature :param visParams: visualization parameters. For Images can have the following arguments: bands, min, max. :type visParams: dict :param name: name for the layer :type name: str :param inspect: when adding a geometry or a feature you can pop up data from a desired layer. Params are: :data: the EEObject where to get the data from :reducer: the reducer to use :scale: the scale to reduce :return: the added layer :rtype: """ def addImage(image, name): # Check if layer exists if name in self.EELayers.keys(): print("Layer with name {} exists already, please choose" + "another name".format(name)) return params = get_image_tile(image, visParams, show, opacity) layer = ipyleaflet.TileLayer(url=params['url'], attribution=params['attribution'], name=name) self.add_layer(layer) self.EELayers[name] = { 'type': 'Image', 'object': image, 'visParams': visParams, 'layer': layer } return layer def addGeoJson(geometry, name): # Check if layer exists if name in self.EELayers.keys(): print("Layer with name {} exists already, please choose" + "another name".format(name)) return params = get_geojson_tile(geometry, inspect) layer = ipyleaflet.GeoJSON(data=params['geojson'], name=name, popup=HTML(params['pop'])) self.add_layer(layer) self.EELayers[name] = { 'type': 'Geometry', 'object': geometry, 'visParams': None, 'layer': layer } return layer # CASE: ee.Image if isinstance(eeObject, ee.Image): thename = name if name else 'Image {}'.format(self.added_images) addImage(eeObject, thename) elif isinstance(eeObject, ee.Geometry): thename = name if name else 'Geometry {}'.format( self.added_geometries) addGeoJson(eeObject, thename) elif isinstance(eeObject, ee.Feature): geom = eeObject.geometry() addGeoJson(geom) elif isinstance(eeObject, ee.ImageCollection): proxy = eeObject.sort('system:time_start') mosaic = ee.Image(proxy.mosaic()) thename = name if name else 'Mosaic {}'.format(self.added_images) addImage(mosaic, thename) else: print("`addLayer` doesn't support adding the specified object to" "the map") def removeLayer(self, name): """ Remove a layer by its name """ if name in self.EELayers.keys(): layer = self.EELayers[name]['layer'] self.remove_layer(layer) self.EELayers.pop(name) else: print('Layer {} is not present in the map'.format(name)) return def centerObject(self, eeObject, zoom=None, method=1): """ Center an eeObject :param eeObject: :param zoom: :param method: experimetal methods to estimate zoom for fitting bounds Currently: 1 or 2 :type: int """ bounds = get_bounds(eeObject) if isinstance(eeObject, ee.Geometry): centroid = eeObject.centroid().getInfo()['coordinates'] elif isinstance(eeObject, ee.Feature) or isinstance( eeObject, ee.Image): centroid = eeObject.geometry().centroid().getInfo()['coordinates'] elif isinstance(eeObject, list): pol = ee.Geometry.Polygon(inverse_coordinates(list)) centroid = pol.centroid().getInfo()['coordinates'] self.center = inverse_coordinates(centroid) if zoom: self.zoom = zoom else: self.zoom = get_zoom(bounds, method) def getCenter(self): """ Returns the coordinates at the center of the map. No arguments. Returns: Geometry.Point :return: """ center = self.center coords = inverse_coordinates(center) return ee.Geometry.Point(coords) def getBounds(self, asGeoJSON=True): """ Returns the bounds of the current map view, as a list in the format [west, south, east, north] in degrees. Arguments: asGeoJSON (Boolean, optional): If true, returns map bounds as GeoJSON. Returns: GeoJSONGeometry|List<Number>|String """ bounds = inverse_coordinates(self.bounds) if asGeoJSON: return ee.Geometry.Rectangle(bounds) else: return bounds def addTab(self, name, handler, widget=None): """ Add a Tab to the Panel. The handler is for the Map :param name: name for the new tab :type name: str :param handler: handle function for the new tab. Arguments of the function are: :type: the type of the event (click, mouseover, etc..) :coordinates: coordinates where the event occured [lon, lat] :widget: the widget inside the Tab :map: the Map instance :param widget: widget inside the Tab. Defaults to HTML('') :type widget: ipywidgets.Widget """ # Widget wid = widget if widget else HTML('') # Get tab's children as a list tab_children = list(self.tabs.children) # Get a list of tab's titles titles = [ self.tabs.get_title(i) for i, child in enumerate(tab_children) ] # Check if tab already exists if name not in titles: ntabs = len(tab_children) # Add widget as a new children self.childrenDict[name] = wid tab_children.append(wid) # Overwrite tab's children self.tabs.children = tab_children # Set name of the new tab self.tabs.set_title(ntabs, name) # Set the handler for the new tab def proxy_handler(f): def wrap(**kwargs): # Add widget to handler arguments kwargs['widget'] = self.childrenDict[name] coords = kwargs['coordinates'] kwargs['coordinates'] = inverse_coordinates(coords) kwargs['map'] = self return f(**kwargs) return wrap self.handlers[name] = proxy_handler(handler) else: print('Tab {} already exists, please choose another name'.format( name)) def handle_change_tab(self, change): """ Handle function to trigger when tab changes """ # Remove all handlers if change['name'] == 'selected_index': old = change['old'] new = change['new'] old_name = self.tabs.get_title(old) new_name = self.tabs.get_title(new) # Remove all handlers for handl in self.handlers.values(): self.on_interaction(handl, True) # Set new handler if new_name in self.handlers.keys(): self.on_interaction(self.handlers[new_name]) def handle_inspector(self, **change): """ Handle function for the Inspector Widget """ # Get click coordinates coords = inverse_coordinates(change['coordinates']) event = change['type'] # event type if event == 'click': # If the user clicked # Clear children // Loading self.inspectorWid.children = [HTML('wait a second please..')] self.inspectorWid.set_title(0, 'Loading...') # create a point where the user clicked point = ee.Geometry.Point(coords) # First Accordion row text (name) first = 'Point {} at {} zoom'.format(coords, self.zoom) namelist = [first] wids4acc = [HTML('')] # first row has no content for name, obj in self.EELayers.items(): # for every added layer # name = obj['name'] # IMAGES if obj['type'] == 'Image': # Get the image's values image = obj['object'] values = tools.get_value(image, point, 10, 'client') values = tools.sort_dict(values) # Create the content img_html = '' for band, value in values.items(): img_html += '<b>{}</b>: {}</br>'.format(band, value) wid = HTML(img_html) # append widget to list of widgets wids4acc.append(wid) namelist.append(name) # GEOMETRIES elif obj['type'] == 'Geometry': geom = obj['object'] data = str(geom.getInfo()) wid = HTML(data) wids4acc.append(wid) namelist.append(name) # Set children and children's name of inspector widget self.inspectorWid.children = wids4acc for i, n in enumerate(namelist): self.inspectorWid.set_title(i, n)
def show(self): """ """ # display() about = VBox([HTML("<a href=\"https://github.com/bengranett/pypelidcalc\" target=\"_blank\">Pypelid-calc</a> version: %s"%pypelidcalc.__version__)]) tab = Tab([self.galaxy.widget, self.foreground.widget, self.instrument.widget, self.survey.widget, self.analysis.widget, self.config.widget, about]) tab.set_title(0, "Source") tab.set_title(1, "Foreground") tab.set_title(2, "Instrument") tab.set_title(3, "Survey") tab.set_title(4, "Analysis") tab.set_title(5, "Config") tab.set_title(6, "About") tab.layout={'height': '300px'} tab.observe(self.tab_event) display(tab) for group in [self.galaxy, self.foreground, self.instrument, self.survey, self.analysis]: for key, w in group.widgets.items(): w.observe(self.render, names='value') self.figs = {} self.figs['spec'] = go.FigureWidget() self.figs['spec'].update_layout(xaxis_title=u'Wavelength (\u03BCm)', height=200, yaxis_title='Flux density', margin=dict(l=0, r=0, t=0, b=0, pad=0)) self.figs['spec'].add_scatter(x=[], y=[], name='Noise', line_color='grey') self.figs['spec'].add_scatter(x=[], y=[], name='Realization', line_color='dodgerblue') self.figs['spec'].add_scatter(x=[], y=[], name='Signal', line_color='black') self.figs['image'] = go.FigureWidget() self.figs['image'].update_layout(height=200, width=200, margin=dict(l=0, r=0, t=0, b=0, pad=0)) self.figs['image'].add_trace(go.Heatmap(z=[[]], showscale=False)) self.widgets['render_button'] = Button(description="Update realization", layout={'border':'solid 1px black', 'width': '200px'}) self.widgets['render_button'].on_click(self.render) self.widgets['seed_checkbox'].observe(self.seed_checkbox, names='value') self.widgets['signal_on'].observe(self.hideshow_line, names='value') self.widgets['real_on'].observe(self.hideshow_line, names='value') self.widgets['noise_on'].observe(self.hideshow_line, names='value') checkboxes = HBox([self.widgets['signal_on'], self.widgets['noise_on'], self.widgets['real_on']]) display(HTML('<h3>Spectrum</h3>')) display(HBox([self.widgets['seed_checkbox'], self.widgets['seed']])) display(HBox([HTML('SNR:'), self.widgets['snrbox'], self.widgets['render_button'], checkboxes])) display(HBox([self.figs['spec'], self.figs['image']])) self.reset_button(self.widgets['button']) self.widgets['button'].on_click(self.click_start) elements = [HTML("<h3>Redshift measurement</h3>")] elements += [HBox([self.widgets['nreal'], self.widgets['button'], self.widgets['progress'], self.widgets['timer']])] horiz = [HTML('<b>Statistics:</b>')] horiz += [HTML('Mean z:'), self.widgets['zmeas']] horiz += [HTML('Error:'), self.widgets['zerr']] horiz += [HTML('68% limit:'), self.widgets['zerr_68']] horiz += [HTML('Fractional systematic:'), self.widgets['zerr_sys']] horiz += [HTML('Outlier rate:'), self.widgets['zerr_cat']] elements += [HBox(horiz)] display(VBox(elements)) self.figs['pdf'] = go.FigureWidget() self.figs['pdf'].update_layout(xaxis_title='Redshift', height=200, yaxis_title='Distribution',margin=dict(l=0, r=0, t=0, b=0, pad=0)) self.figs['pdf'].add_scatter(x=[], y=[], name='Measured redshift') display(self.figs['pdf']) self.render_lock.acquire() self.param_lock.acquire() self._render((self.render_lock, self.param_lock))
class CadqueryDisplay(object): types = [ "reset", "fit", "isometric", "right", "front", "left", "rear", "top", "bottom", ] directions = { "left": (1, 0, 0), "right": (-1, 0, 0), "front": (0, 1, 0), "rear": (0, -1, 0), "top": (0, 0, 1), "bottom": (0, 0, -1), "isometric": (1, 1, 1), } def __init__(self): super().__init__() self.info = None self.cq_view = None self.assembly = None self.image_path = join(dirname(__file__), "icons") self.image_paths = [ { UNSELECTED: "%s/no_shape.png" % self.image_path, SELECTED: "%s/shape.png" % self.image_path, MIXED: "%s/mix_shape.png" % self.image_path, EMPTY: "%s/empty_shape.png" % self.image_path, }, { UNSELECTED: "%s/no_mesh.png" % self.image_path, SELECTED: "%s/mesh.png" % self.image_path, MIXED: "%s/mix_mesh.png" % self.image_path, EMPTY: "%s/empty_mesh.png" % self.image_path, }, ] self._display = "cell" self._tools = True self.id = uuid4().hex[:10] self.clean = True def _dump_config(self): print("\nCadDisplay:") config = { k: v for k, v in self.__dict__.items() if not k in [ "cq_view", "output", "info", "clipping", "tree_clipping", "image_paths", "image_path", "view_controls", "check_controls", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCadView:") config = { k: v for k, v in self.cq_view.__dict__.items() if not k in [ "pickable_objects", "scene", "controller", "renderer", "key_lights", "picker", "shapes", "camera", "info", "axes", "grid", "cq_renderer", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCamera:") config = { k: v for k, v in self.cq_view.camera.__dict__["_trait_values"].items() if not k in [ "keys", "matrix", "matrixWorldInverse", "modelViewMatrix", "normalMatrix", "matrixWorld", "projectionMatrix", "comm", ] and not k.startswith("_") } for k, v in config.items(): print(f"- {k:30s}: {v}") # Buttons def create_button(self, image_name, handler, tooltip): button = ImageButton( width=36, height=28, image_path="%s/%s.png" % (self.image_path, image_name), tooltip=tooltip, type=image_name, ) button.on_click(handler) button.add_class("view_button") return button def create_checkbox(self, kind, description, value, handler): checkbox = Checkbox(value=value, description=description, indent=False) checkbox.observe(handler, "value") checkbox.add_class("view_%s" % kind) return checkbox # UI Handler def change_view(self, typ, directions): def reset(b): self.cq_view._reset() def refit(b): self.cq_view.camera.zoom = self.cq_view.camera_initial_zoom self.cq_view._update() def change(b): self.cq_view.camera.position = self.cq_view._add( self.cq_view.bb.center, self.cq_view._scale(directions[typ])) self.cq_view._update() if typ == "fit": return refit elif typ == "reset": return reset else: return change def bool_or_new(self, val): return val if isinstance(val, bool) else val["new"] def toggle_axes(self, change): self.axes = self.bool_or_new(change) self.cq_view.set_axes_visibility(self.axes) def toggle_grid(self, change): self.grid = self.bool_or_new(change) self.cq_view.set_grid_visibility(self.grid) def toggle_center(self, change): self.axes0 = self.bool_or_new(change) self.cq_view.set_axes_center(self.axes0) def toggle_ortho(self, change): self.ortho = self.bool_or_new(change) self.cq_view.toggle_ortho(self.ortho) def toggle_transparent(self, change): self.transparent = self.bool_or_new(change) self.cq_view.set_transparent(self.transparent) def toggle_black_edges(self, change): self.black_edges = self.bool_or_new(change) self.cq_view.set_black_edges(self.black_edges) def toggle_clipping(self, change): if change["name"] == "selected_index": self.cq_view.set_clipping(change["new"]) def create( self, render_shapes=None, render_edges=None, height=None, bb_factor=None, tree_width=None, cad_width=None, quality=None, angular_tolerance=None, optimal_bb=None, edge_accuracy=None, axes=None, axes0=None, grid=None, ortho=None, transparent=None, position=None, rotation=None, zoom=None, mac_scrollbar=None, display=None, tools=None, timeit=None, ): def preset(key, value): return get_default(key) if value is None else value self.height = preset("height", height) self.tree_width = preset("tree_width", tree_width) self.cad_width = preset("cad_width", cad_width) self.bb_factor = preset("bb_factor", bb_factor) self.render_shapes = preset("render_shapes", render_shapes) self.render_edges = preset("render_edges", render_edges) self.quality = preset("quality", quality) self.angular_tolerance = preset("angular_tolerance", angular_tolerance) self.optimal_bb = preset("optimal_bb", optimal_bb) self.edge_accuracy = preset("edge_accuracy", edge_accuracy) self.axes = preset("axes", axes) self.axes0 = preset("axes0", axes0) self.grid = preset("grid", grid) self.ortho = preset("ortho", ortho) self.transparent = preset("transparent", transparent) self.position = preset("position", position) self.rotation = preset("rotation", rotation) self.zoom = preset("zoom", zoom) self.mac_scrollbar = (platform.system() == "Darwin") and preset( "mac_scrollbar", mac_scrollbar) self.timeit = preset("timeit", timeit) self._display = preset("display", display) self._tools = preset("tools", tools) self.black_edges = False # Output widget output_height = self.height * 0.4 - 20 + 2 self.info = Info(self.tree_width, output_height - 6) self.info.html.add_class("scroll-area") ## Threejs rendering of Cadquery objects self.cq_view = CadqueryView( width=self.cad_width, height=self.height, bb_factor=self.bb_factor, quality=self.quality, edge_accuracy=self.edge_accuracy, angular_tolerance=self.angular_tolerance, optimal_bb=self.optimal_bb, render_shapes=self.render_shapes, render_edges=self.render_edges, info=self.info, position=self.position, rotation=self.rotation, zoom=self.zoom, timeit=self.timeit, ) renderer = self.cq_view.create() renderer.add_class("view_renderer") renderer.add_class(f"view_renderer_{self.id}") # Prepare the CAD view tools # Output area self.output = Box([self.info.html]) self.output.layout = Layout( height="%dpx" % output_height, width="%dpx" % self.tree_width, overflow_y="scroll", overflow_x="scroll", ) self.output.add_class("view_output") # Clipping tool self.clipping = Clipping(self.image_path, self.output, self.cq_view, self.tree_width) for normal in ((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)): self.clipping.add_slider(1, -1, 1, 0.01, normal) # Empty dummy Tree View self.tree_view = Output() # Tab widget with Tree View and Clipping tools self.tree_clipping = Tab(layout=Layout(height="%dpx" % (self.height * 0.6 + 20), width="%dpx" % self.tree_width)) self.tree_clipping.children = [self.tree_view, self.clipping.create()] for i, c in enumerate(["Tree", "Clipping"]): self.tree_clipping.set_title(i, c) self.tree_clipping.observe(self.toggle_clipping) self.tree_clipping.add_class("tab-content-no-padding") # Check controls to swith orto, grid and axis self.check_controls = [ self.create_checkbox("axes", "Axes", self.axes, self.toggle_axes), self.create_checkbox("grid", "Grid", self.grid, self.toggle_grid), self.create_checkbox("zero", "@ 0", self.axes0, self.toggle_center), self.create_checkbox("ortho", "Ortho", self.ortho, self.toggle_ortho), self.create_checkbox( "transparent", "Transparency", self.transparent, self.toggle_transparent, ), self.create_checkbox("black_edges", "Black Edges", False, self.toggle_black_edges), ] self.check_controls[-2].add_class("indent") # Buttons to switch camera position self.view_controls = [] for typ in CadqueryDisplay.types: if typ == "refit": tooltip = "Fit view" elif typ == "reset": tooltip = "Reset view" else: tooltip = "Change view to %s" % typ button = self.create_button( typ, self.change_view(typ, CadqueryDisplay.directions), tooltip) self.view_controls.append(button) self.info.version_msg() # only show pure renderer if self._tools == False: return renderer else: return HBox([ VBox([ HBox(self.check_controls[:-2]), self.tree_clipping, self.output ]), VBox([ HBox(self.view_controls + self.check_controls[-2:]), renderer ]), ]) def add_shapes(self, shapes, mapping, tree, reset=True): def count_shapes(shapes): count = 0 for shape in shapes["parts"]: if shape.get("parts") is None: count += 1 else: count += count_shapes(shape) return count self.clear() self.states = {k: v["state"] for k, v in mapping.items()} self.paths = {k: v["path"] for k, v in mapping.items()} self.tree_view = Output() self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] self.progress = Progress(count_shapes(shapes) + 3) with self.tree_view: ipy_display(self.progress.progress) add_shapes_timer = Timer(self.timeit, "add shapes") self.cq_view.add_shapes(shapes, self.progress, reset=reset) add_shapes_timer.stop() configure_display_timer = Timer(self.timeit, "configure display") def set_slider(i, s_min, s_max): s_min = -0.02 if abs(s_min) < 1e-4 else s_min * self.bb_factor s_max = 0.02 if abs(s_max) < 1e-4 else s_max * self.bb_factor self.clipping.sliders[ i].max = 2**31 # first increase max to avoid traitlet error that min > max self.clipping.sliders[ i].min = s_min # set min which now is always < max self.clipping.sliders[i].max = s_max # correct max self.clipping.sliders[i].value = s_max bb = self.cq_view.bb set_slider(1, bb.xmin, bb.xmax) set_slider(3, bb.ymin, bb.ymax) set_slider(5, bb.zmin, bb.zmax) # Tree widget to change visibility self.tree_view = TreeView( image_paths=self.image_paths, tree=tree, state=self.states, layout=Layout(height="%dpx" % (self.height * 0.6 - 25), width="%dpx" % (self.tree_width - 20)), ) self.tree_view.add_class("view_tree") self.tree_view.add_class("scroll-area") if self.mac_scrollbar: self.tree_view.add_class("mac-scrollbar") self.tree_view.observe(self.cq_view.change_visibility(self.paths), "state") self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Set initial state for obj, vals in self.states.items(): for i, val in enumerate(vals): self.cq_view.set_visibility(self.paths[obj], i, val) self.toggle_axes(self.axes) self.toggle_center(self.axes0) self.toggle_grid(self.grid) self.toggle_transparent(self.transparent) self.toggle_black_edges(self.black_edges) self.toggle_ortho(self.ortho) self.clean = False configure_display_timer.stop() if SIDECAR is not None: print("Done, using side car '%s'" % SIDECAR.title) def clear(self): if not self.clean: self.cq_view.clear() # clear tree self.tree_clipping.children = [ Output(), self.tree_clipping.children[1] ] self.clean = True def display(self, widget): if self._display == "cell" or SIDECAR is None: ipy_display(widget) else: SIDECAR.clear_output(True) with SIDECAR: ipy_display(widget) @property def root_group(self): return self.cq_view.root_group
class CadqueryDisplay(object): types = [ "reset", "fit", "isometric", "right", "front", "left", "rear", "top", "bottom", ] directions = { "left": (1, 0, 0), "right": (-1, 0, 0), "front": (0, 1, 0), "rear": (0, -1, 0), "top": (0, 0, 1), "bottom": (0, 0, -1), "isometric": (1, 1, 1), } def __init__(self): super().__init__() self.info = None self.cq_view = None self.assembly = None self.image_path = join(dirname(__file__), "icons", get_default("theme")) self.image_paths = [ { UNSELECTED: join(self.image_path, "no_shape.png"), SELECTED: join(self.image_path, "shape.png"), MIXED: join(self.image_path, "mix_shape.png"), EMPTY: join(self.image_path, "empty_shape.png"), }, { UNSELECTED: join(self.image_path, "no_mesh.png"), SELECTED: join(self.image_path, "mesh.png"), MIXED: join(self.image_path, "mix_mesh.png"), EMPTY: join(self.image_path, "empty_mesh.png"), }, ] self._display = "cell" self._tools = True self.id = uuid4().hex[:10] self.clean = True self.splash = False self.tree_clipping = None def _dump_config(self): print("\nCadDisplay:") config = { k: v for k, v in self.__dict__.items() if not k in [ "cq_view", "output", "info", "clipping", "tree_clipping", "image_paths", "image_path", "view_controls", "check_controls", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCadView:") config = { k: v for k, v in self.cq_view.__dict__.items() if not k in [ "pickable_objects", "scene", "controller", "renderer", "key_lights", "picker", "shapes", "camera", "info", "axes", "grid", "cq_renderer", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCamera:") config = { k: v for k, v in self.cq_view.camera.__dict__["_trait_values"].items() if not k in [ "keys", "matrix", "matrixWorldInverse", "modelViewMatrix", "normalMatrix", "matrixWorld", "projectionMatrix", "comm", ] and not k.startswith("_") } for k, v in config.items(): print(f"- {k:30s}: {v}") # Buttons def create_button(self, image_name, handler, tooltip): button = ImageButton( width=36, height=28, image_path=join(self.image_path, f"{image_name}.png"), tooltip=tooltip, type=image_name, ) button.on_click(handler) button.add_class("view_button") return button def create_checkbox(self, kind, description, value, handler): checkbox = Checkbox(value=value, description=description, indent=False) checkbox.observe(handler, "value") checkbox.add_class("view_%s" % kind) return checkbox # UI Handler def change_view(self, typ, directions): def reset(b): self.cq_view._reset_camera() def refit(b): self.cq_view.camera.zoom = self.cq_view.camera_initial_zoom self.cq_view._update() def change(b): self.cq_view.camera.position = self.cq_view._add( self.cq_view.bb.center, self.cq_view._scale(directions[typ])) self.cq_view._update() if typ == "fit": return refit elif typ == "reset": return reset else: return change def bool_or_new(self, val): return val if isinstance(val, bool) else val["new"] def toggle_axes(self, change): self.axes = self.bool_or_new(change) self.cq_view.set_axes_visibility(self.axes) def toggle_grid(self, change): self.grid = self.bool_or_new(change) self.cq_view.set_grid_visibility(self.grid) def toggle_axes0(self, change): self.axes0 = self.bool_or_new(change) self.cq_view.set_axes_center(self.axes0) def toggle_ortho(self, change): self.ortho = self.bool_or_new(change) self.cq_view.toggle_ortho(self.ortho) def toggle_transparent(self, change): self.transparent = self.bool_or_new(change) self.cq_view.set_transparent(self.transparent) def toggle_black_edges(self, change): self.black_edges = self.bool_or_new(change) self.cq_view.set_black_edges(self.black_edges) def toggle_clipping(self, change): if change["name"] == "selected_index": self.cq_view.set_clipping(change["new"]) def init_progress(self, num_shapes): self.progress.progress.value = 0 self.progress.reset(num_shapes) def _set_checkboxes(self): self.checkbox_axes.value = self.axes self.checkbox_grid.value = self.grid self.checkbox_axes0.value = self.axes0 self.checkbox_ortho.value = self.ortho self.checkbox_transparent.value = self.transparent self.checkbox_black_edges.value = self.black_edges def _update_settings(self, **kwargs): preset = lambda key, value: get_default(key ) if value is None else value self.height = preset("height", kwargs.get("height")) self.tree_width = preset("tree_width", kwargs.get("tree_width")) self.cad_width = preset("cad_width", kwargs.get("cad_width")) self.bb_factor = preset("bb_factor", kwargs.get("bb_factor")) self.axes = preset("axes", kwargs.get("axes")) self.axes0 = preset("axes0", kwargs.get("axes0")) self.grid = preset("grid", kwargs.get("grid")) self.ortho = preset("ortho", kwargs.get("ortho")) self.transparent = preset("transparent", kwargs.get("transparent")) self.black_edges = preset("black_edges", kwargs.get("black_edges")) self.mac_scrollbar = preset("mac_scrollbar", kwargs.get("mac_scrollbar")) self.timeit = preset("timeit", kwargs.get("timeit")) self._display = preset("display", kwargs.get("display")) self._tools = preset("tools", kwargs.get("tools")) def _info_height(self, height): return int(height * 0.4) - 20 + 2 def _tree_clipping_height(self, height): return int(height * 0.6) + 17 def _tree_height(self, height): return int(height * 0.6) - 30 def set_size(self, tree_width, width, height): if width is not None: # adapt renderer self.cad_width = width self.cq_view.camera.width = width self.cq_view.renderer.width = width if height is not None: # adapt renderer self.height = height self.cq_view.camera.height = height self.cq_view.renderer.height = height # adapt info box info_height = self._info_height(height) self.info.height = info_height self.info.html.layout.height = px(info_height - 6) self.output.layout.height = px(info_height) # adapt tree and clipping tree_clipping_height = self._tree_clipping_height(height) self.tree_clipping.layout.height = px(tree_clipping_height) tree_height = self._tree_height(height) self.tree_view.layout.height = px(tree_height) if tree_width is not None: # adapt tree and clipping self.tree_clipping.layout.width = px(tree_width) self.tree_view.layout.width = px(tree_width) self.clipping.set_width(tree_width) # adapt info box self.output.layout.width = px(tree_width) self.info.html.layout.width = px(tree_width) # adapt progress bar self.progress.progress.layout.width = px(tree_width) def create( self, height=None, bb_factor=None, tree_width=None, cad_width=None, axes=None, axes0=None, grid=None, ortho=None, transparent=None, mac_scrollbar=None, display=None, tools=None, timeit=None, ): self._update_settings( height=height, bb_factor=bb_factor, tree_width=tree_width, cad_width=cad_width, axes=axes, axes0=axes0, grid=grid, ortho=ortho, transparent=transparent, mac_scrollbar=mac_scrollbar, display=display, tools=tools, timeit=timeit, ) # Output widget output_height = self._info_height(self.height) self.info = Info(self.tree_width, output_height - 6) self.info.html.add_class("scroll-area") if self.mac_scrollbar: self.info.html.add_class("mac-scrollbar") ## Threejs rendering of Cadquery objects self.cq_view = CadqueryView( width=self.cad_width, height=self.height, info=self.info, timeit=self.timeit, ) renderer = self.cq_view.create() renderer.add_class("view_renderer") renderer.add_class(f"view_renderer_{self.id}") # Prepare the CAD view tools # Output area self.output = Box([self.info.html]) self.output.layout = Layout( height=px(output_height), width=px(self.tree_width), overflow_y="hidden", overflow_x="hidden", ) self.output.add_class("view_output") # TODO # if get_default("theme") == "dark": # self.output.add_class("p-Collapse-contents") # Clipping tool self.clipping = Clipping(self.image_path, self.output, self.cq_view, self.tree_width) for normal in ((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)): self.clipping.add_slider(1, -1, 1, 0.01, normal) # Empty dummy Tree View self.tree_view = Output() self.progress = Progress(3, self.tree_width) # Tab widget with Tree View and Clipping tools self.tree_clipping = Tab( layout=Layout(height=px(self._tree_clipping_height(self.height)), width=px(self.tree_width))) self.tree_clipping.children = [self.tree_view, self.clipping.create()] for i, c in enumerate(["Tree", "Clipping"]): self.tree_clipping.set_title(i, c) self.tree_clipping.observe(self.toggle_clipping) self.tree_clipping.add_class("tab-content-no-padding") # Check controls to switch orto, grid and axis self.checkbox_axes = self.create_checkbox("axes", "Axes", self.axes, self.toggle_axes) self.checkbox_grid = self.create_checkbox("grid", "Grid", self.grid, self.toggle_grid) self.checkbox_axes0 = self.create_checkbox("zero", "@ 0", self.axes0, self.toggle_axes0) self.checkbox_ortho = self.create_checkbox("ortho", "Ortho", self.ortho, self.toggle_ortho) self.checkbox_transparent = self.create_checkbox( "transparent", "Transparency", self.transparent, self.toggle_transparent, ) self.checkbox_black_edges = self.create_checkbox( "black_edges", "Black Edges", False, self.toggle_black_edges) self.check_controls = [ self.checkbox_axes, self.checkbox_grid, self.checkbox_axes0, self.checkbox_ortho, self.checkbox_transparent, self.checkbox_black_edges, ] self.check_controls[-2].add_class("indent") # Buttons to switch camera position self.view_controls = [] for typ in CadqueryDisplay.types: if typ == "refit": tooltip = "Fit view" elif typ == "reset": tooltip = "Reset view" else: tooltip = "Change view to %s" % typ button = self.create_button( typ, self.change_view(typ, CadqueryDisplay.directions), tooltip) self.view_controls.append(button) # only show pure renderer if self._tools == False: return renderer else: return HBox([ VBox([ HBox(self.check_controls[:-2]), self.tree_clipping, self.progress.progress, self.output ]), VBox([ HBox(self.view_controls + self.check_controls[-2:]), renderer ]), ]) def add_shapes( self, shapes, mapping, tree, bb, ticks=None, reset_camera=True, bb_factor=None, ambient_intensity=None, direct_intensity=None, default_edgecolor=None, position=None, rotation=None, zoom=None, ): self.clear() self.states = {k: v["state"] for k, v in mapping.items()} self.paths = {k: v["path"] for k, v in mapping.items()} self.tree_view = Output() self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Force reset of camera to inhereit splash settings for first object if self.splash: reset_camera = True self.splash = False with Timer(self.timeit, "", "mesh shapes", 2): self.cq_view.add_shapes( shapes, bb, ticks, self.progress, reset_camera=reset_camera, bb_factor=bb_factor, ambient_intensity=ambient_intensity, direct_intensity=direct_intensity, default_edgecolor=default_edgecolor, position=position, rotation=rotation, zoom=zoom, ) with Timer(self.timeit, "", "configure display", 2): def set_slider(i, s_min, s_max): s_min = -0.02 if abs(s_min) < 1e-4 else s_min * self.bb_factor s_max = 0.02 if abs(s_max) < 1e-4 else s_max * self.bb_factor self.clipping.sliders[ i].max = 2**31 # first increase max to avoid traitlet error that min > max self.clipping.sliders[ i].min = s_min # set min which now is always < max self.clipping.sliders[i].max = s_max # correct max self.clipping.sliders[i].value = s_max bb = self.cq_view.bb set_slider(1, bb.xmin, bb.xmax) set_slider(3, bb.ymin, bb.ymax) set_slider(5, bb.zmin, bb.zmax) # Tree widget to change visibility self.tree_view = TreeView( image_paths=self.image_paths, tree=tree, state=self.states, layout=Layout(height=px(self._tree_height(self.height)), width=px(self.tree_width - 20)), ) self.tree_view.add_class("view_tree") self.tree_view.add_class("scroll-area") if self.mac_scrollbar: self.tree_view.add_class("mac-scrollbar") self.tree_view.observe(self.cq_view.change_visibility(self.paths), "state") self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Set initial state for obj, vals in self.states.items(): for i, val in enumerate(vals): self.cq_view.set_visibility(self.paths[obj], i, val) self._set_checkboxes() self.toggle_axes(self.axes) self.toggle_axes0(self.axes0) self.toggle_grid(self.grid) self.toggle_transparent(self.transparent) self.toggle_black_edges(self.black_edges) self.toggle_ortho(self.ortho) self.clean = False def clear(self): if not self.clean: self.cq_view.clear() self.info.clear() # clear tree self.tree_clipping.children = [ Output(), self.tree_clipping.children[1] ] self.clean = True def display(self, widget): if self._display == "cell" or SIDECAR is None: ipy_display(widget) else: SIDECAR.clear_output(True) with SIDECAR: ipy_display(widget) @property def root_group(self): return self.cq_view.root_group