Example #1
0
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
Example #2
0
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
Example #3
0
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)
Example #4
0
    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))
Example #5
0
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
Example #6
0
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