Example #1
0
    def __init__(self, model, opt, maxiters, verbose=False, current_iteration=0, ipython_notebook=True, clear_after_finish=False):
        self.verbose = verbose
        if self.verbose:
            self.model = model
            self.iteration = current_iteration
            self.p_iter = self.iteration
            self.maxiters = maxiters
            self.len_maxiters = len(str(maxiters))
            self.opt_name = opt.opt_name
            self.model.add_observer(self, self.print_status)
            self.status = 'running'
            self.clear = clear_after_finish

            self.update()

            try:
                from IPython.display import display
                from ipywidgets import IntProgress, HTML, Box, VBox, HBox, FlexBox
                self.text = HTML(width='100%')
                self.progress = IntProgress(min=0, max=maxiters)
                #self.progresstext = Text(width='100%', disabled=True, value='0/{}'.format(maxiters))
                self.model_show = HTML()
                self.ipython_notebook = ipython_notebook
            except:
                # Not in Ipython notebook
                self.ipython_notebook = False

            if self.ipython_notebook:
                left_col = VBox(children=[self.progress, self.text], padding=2, width='40%')
                right_col = Box(children=[self.model_show], padding=2, width='60%')
                self.hor_align = FlexBox(children = [left_col, right_col], width='100%', orientation='horizontal')

                display(self.hor_align)

                try:
                    self.text.set_css('width', '100%')
                    left_col.set_css({
                             'padding': '2px',
                             'width': "100%",
                             })

                    right_col.set_css({
                             'padding': '2px',
                             })

                    self.hor_align.set_css({
                             'width': "100%",
                             })

                    self.hor_align.remove_class('vbox')
                    self.hor_align.add_class('hbox')

                    left_col.add_class("box-flex1")
                    right_col.add_class('box-flex0')

                except:
                    pass

                #self.text.add_class('box-flex2')
                #self.progress.add_class('box-flex1')
            else:
                self.exps = exponents(self.fnow, self.current_gradient)
                print('Running {} Code:'.format(self.opt_name))
                print('  {3:7s}   {0:{mi}s}   {1:11s}    {2:11s}'.format("i", "f", "|g|", "runtime", mi=self.len_maxiters))
    def __init__(self,
                 model,
                 opt,
                 maxiters,
                 verbose=False,
                 current_iteration=0,
                 ipython_notebook=True,
                 clear_after_finish=False):
        self.verbose = verbose
        if self.verbose:
            self.model = model
            self.iteration = current_iteration
            self.p_iter = self.iteration
            self.maxiters = maxiters
            self.len_maxiters = len(str(int(maxiters)))
            self.opt_name = opt.opt_name
            self.opt = opt
            self.model.add_observer(self, self.print_status)
            self.status = 'running'
            self.clear = clear_after_finish

            self.update()

            try:  # pragma: no cover
                from IPython.display import display
                from ipywidgets import IntProgress, HTML, Box, VBox, HBox, FlexBox
                self.text = HTML(width='100%')
                self.progress = IntProgress(min=0, max=maxiters)
                #self.progresstext = Text(width='100%', disabled=True, value='0/{}'.format(maxiters))
                self.model_show = HTML()
                self.ipython_notebook = ipython_notebook
            except:
                # Not in Ipython notebook
                self.ipython_notebook = False

            if self.ipython_notebook:  # pragma: no cover
                left_col = VBox(children=[self.progress, self.text],
                                padding=2,
                                width='40%')
                right_col = Box(children=[self.model_show],
                                padding=2,
                                width='60%')
                self.hor_align = FlexBox(children=[left_col, right_col],
                                         width='100%',
                                         orientation='horizontal')

                display(self.hor_align)

                try:
                    self.text.set_css('width', '100%')
                    left_col.set_css({
                        'padding': '2px',
                        'width': "100%",
                    })

                    right_col.set_css({
                        'padding': '2px',
                    })

                    self.hor_align.set_css({
                        'width': "100%",
                    })

                    self.hor_align.remove_class('vbox')
                    self.hor_align.add_class('hbox')

                    left_col.add_class("box-flex1")
                    right_col.add_class('box-flex0')

                except:
                    pass

                #self.text.add_class('box-flex2')
                #self.progress.add_class('box-flex1')
            else:
                self.exps = exponents(self.fnow, self.current_gradient)
                print('Running {} Code:'.format(self.opt_name))
                print('  {3:7s}   {0:{mi}s}   {1:11s}    {2:11s}'.format(
                    "i", "f", "|g|", "runtime", mi=self.len_maxiters))
Example #3
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, default_mesh_color=None, default_edge_color=None):
        self.default_mesh_color = default_mesh_color
        self.default_edge_color = default_edge_color

        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
        }]

    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="Change view to %s" % image_name,
                             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

    def write(self, *msg):
        try:
            self.info.add_text(" ".join([str(m) for m in msg]))
        except:
            print(msg)

    def display(self,
                states,
                shapes,
                mapping,
                tree,
                height=600,
                tree_width=250,
                cad_width=800,
                quality=0.5,
                axes=True,
                axes0=True,
                grid=True,
                ortho=True,
                transparent=False,
                position=None,
                rotation=None,
                zoom=None,
                mac_scrollbar=True):

        if platform.system() != "Darwin":
            mac_scrollbar = False

        # Output widget
        output_height = height * 0.4 - 20 + 2
        self.info = Info(tree_width, output_height - 6)

        self.info.html.add_class("scroll-area")
        if mac_scrollbar:
            self.info.html.add_class("mac-scrollbar")

        self.output = Box([self.info.html])
        self.output.layout = Layout(height="%dpx" % output_height,
                                    width="%dpx" % tree_width,
                                    overflow_y="scroll",
                                    overflow_x="scroll")

        self.output.add_class("view_output")

        ## Threejs rendering of Cadquery objects
        self.cq_view = CadqueryView(width=cad_width,
                                    height=height,
                                    quality=quality,
                                    default_mesh_color=self.default_mesh_color,
                                    default_edge_color=self.default_edge_color,
                                    info=self.info)

        for shape in shapes:
            self.cq_view.add_shape(shape["name"], shape["shape"],
                                   shape["color"])
        renderer = self.cq_view.render(position, rotation, zoom)
        renderer.add_class("view_renderer")

        bb = self.cq_view.bb
        clipping = Clipping(self.image_path, self.output, self.cq_view,
                            tree_width)
        for normal in ((1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, 1.0)):
            clipping.add_slider(bb.max * 1.2, -bb.max * 1.3, bb.max * 1.2,
                                0.01, normal)

        # Tree widget to change visibility
#       tree_height = height - output_height - 35
        tree_view = TreeView(image_paths=self.image_paths,
                             tree=tree,
                             state=states,
                             layout=Layout(height="%dpx" % (height * 0.6 - 25),
                                           width="%dpx" % (tree_width - 20)))
        tree_view.add_class("view_tree")
        tree_view.add_class("scroll-area")
        if mac_scrollbar:
            tree_view.add_class("mac-scrollbar")

        tree_view.observe(self.cq_view.change_visibility(mapping), "state")

        tab_contents = ['Tree', 'Clipping']
        tree_clipping = Tab(layout=Layout(height="%dpx" % (height * 0.6 + 20),
                                          width="%dpx" % tree_width))
        tree_clipping.children = [tree_view, clipping.create()]
        for i in range(len(tab_contents)):
            tree_clipping.set_title(i, tab_contents[i])
        tree_clipping.add_class("tab-content-no-padding")

        # Check controls to swith orto, grid and axis
        self.check_controls = [
            self.create_checkbox("axes", "Axes", axes,
                                 self.cq_view.toggle_axes),
            self.create_checkbox("grid", "Grid", grid,
                                 self.cq_view.toggle_grid),
            self.create_checkbox("zero", "@ 0", axes0,
                                 self.cq_view.toggle_center),
            self.create_checkbox("ortho", "Ortho", ortho,
                                 self.cq_view.toggle_ortho),
            self.create_checkbox("transparent", "Transparency", transparent,
                                 self.cq_view.toggle_transparent),
            self.create_checkbox("black_edges", "Black Edges", False,
                                 self.cq_view.toggle_black_edges),
        ]
        self.check_controls[-2].add_class("indent")

        # Set initial state
        self.cq_view.toggle_ortho(ortho)
        self.cq_view.toggle_axes(axes)
        self.cq_view.toggle_center(axes0)
        self.cq_view.toggle_grid(grid)
        self.cq_view.toggle_transparent(transparent)

        for obj, vals in states.items():
            for i, val in enumerate(vals):
                self.cq_view.set_visibility(mapping[obj], i, val)

        # 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.cq_view.change_view(typ, CadqueryDisplay.directions),
                tooltip)
            self.view_controls.append(button)

        return HBox([
            VBox([HBox(self.check_controls[:-2]), tree_clipping, self.output]),
            VBox([
                HBox(self.view_controls + self.check_controls[-2:]), renderer
            ])
        ])
Example #4
0
class SageExplorer(VBox):
    """Sage Explorer in Jupyter Notebook"""

    value = Any()

    def __init__(self, obj=None):
        """
        TESTS::

            sage: from sage_explorer.sage_explorer import SageExplorer
            sage: S = StandardTableaux(15)
            sage: t = S.random_element()
            sage: widget = SageExplorer(t)
        """
        super(SageExplorer, self).__init__()
        self.title = Title()
        self.propsbox = VBox() # Will be a VBox full of HBoxes, one for each property
        self.titlebox = VBox()
        self.titlebox.add_class('titlebox')
        self.titlebox.add_class('lightborder')
        self.titlebox.children = [self.title, self.propsbox]
        self.visualbox = Box()
        self.visualtext = Textarea('', rows=8)
        self.visualwidget = None
        self.visualbox.add_class('visualbox')
        self.visualbox.children = [self.visualtext]
        self.top = HBox([self.titlebox, self.visualbox], layout=justified_h_layout)
        self.menus = Accordion(selected_index=None)
        self.menusbox = VBox([Title("Menus", 2), self.menus])
        self.menusbox.add_class('catalog-menus')
        self.menusbox.add_class('lightborder')
        self.inputs = HBox()
        self.gobutton = Button(description='Run!', tooltip='Run the function or method, with specified arguments')
        self.output = HTML()
        self.worktab = VBox((self.inputs, self.gobutton, self.output))
        self.doc = HTML()
        self.doctab = HTML() # For the method docstring
        self.tabs = Tab((self.worktab, self.doctab)) # Will be used when a method is selected
        self.tabs.add_class('tabs')
        self.tabs.set_title(0, 'Call')
        self.tabs.set_title(1, 'Help')
        self.main = Box((self.doc, self.tabs))
        self.main.add_class('lightborder')
        self.tabs.add_class('invisible') # Hide tabs at first display
        self.bottom = HBox((self.menusbox, self.main), layout=main_h_layout)
        self.children = (self.top, self.bottom)
        self.history = []
        self.set_value(obj)

    def init_selected_menu_value(self):
        r"""
        From a menu selection, compute display elements for the widgets.

        TESTS::
            sage: from sage_explorer.sage_explorer import SageExplorer, ExploredMember
            sage: from sage.monoids.string_monoid import AlphabeticStrings
            sage: e = SageExplorer()
            sage: m = ExploredMember('AlphabeticStrings', member=AlphabeticStrings)
            sage: e.selected_menu_value = m
            sage: e.init_selected_menu_value()
            sage: 'string monoid on generators A-Z' in e.doc.value
            True
        """
        if self.value:
            """If we are exploring an object, all menu items are functions"""
            self.init_selected_func()
            return
        """We are on the catalogs page"""
        selected_obj = self.selected_menu_value # An ExplorerMember
        if not hasattr(selected_obj, 'member_type'):
            selected_obj.compute_member_type()
        if not hasattr(selected_obj, 'doc'):
            selected_obj.compute_doc()
        if 'function' in selected_obj.member_type or 'method' in selected_obj.member_type:
            self.doctab.value = to_html(selected_obj.doc)
            if not hasattr(selected_obj, 'args'):
                try:
                    selected_obj.member = selected_obj.member()
                except:
                    pass
            elif hasattr(selected_obj, 'defaults') and len(selected_obj.defaults) == len(selected_obj.args):
                try:
                    selected_obj.member = selected_obj.member(selected_obj.defaults)
                except:
                    pass
            return
        if 'class' in selected_obj.member_type:
            self.doc.value = to_html(selected_obj.doc)
            self.doctab.value = ''
            self.inputs.children = []
            self.tabs.remove_class('visible')
            self.tabs.add_class('invisible')
            self.doc.remove_class('invisible')
            self.doc.add_class('visible')
            return

    def init_selected_func(self):
        r"""
        From a menu selection, compute display elements for the widgets.

        TESTS::
            sage: from sage_explorer.sage_explorer import SageExplorer, ExploredMember
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: m = ExploredMember('conjugate', parent=p)
            sage: e.selected_menu_value = m
            sage: e.init_selected_func()
            sage: str(e.doctab.value[:100]) # For Python3 compatibility
            '<div class="docstring">\n    \n  <blockquote>\n<div><p>Return the conjugate partition of the partition '
        """
        self.output.value = ''
        func = self.selected_menu_value # An ExplorerMember
        if not hasattr(func, 'doc'):
            func.compute_doc()
        if not hasattr(func, 'origin'):
            func.compute_origin()
        self.doctab.value = to_html(func.doc)
        if func.overrides:
            self.doctab.value += to_html("Overrides:")
            self.doctab.value += to_html(', '.join([extract_classname(x, element_ok=True) for x in func.overrides]))
        inputs = []
        if not hasattr(func, 'args'):
            func.compute_argspec()
        try:
            shift = 0
            for i in range(len(func.args)):
                argname = func.args[i]
                if argname in ['self']:
                    shift = 1
                    continue
                default = ''
                if func.defaults and len(func.defaults) > i - shift and func.defaults[i - shift]:
                    default = func.defaults[i - shift]
                inputs.append(Text(description=argname, placeholder=str(default)))
        except:
            print (func, "attr?")
            print (func.args, func.defaults)
        self.inputs.children = inputs
        self.doc.remove_class('visible')
        self.doc.add_class('invisible')
        self.tabs.remove_class('invisible')
        self.tabs.add_class('visible')

    def get_title(self):
        r"""
        Get explorer general title.

        TESTS:
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_title()
            'Exploring: [3, 3, 2, 1]'
        """
        return "Exploring: %s" % repr(self.value)

    def get_members(self):
        r"""
        Get all members for object self.value.

        OUTPUT: List of `Member` named tuples.

        TESTS::
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_members()
            sage: e.members[2].name, e.members[2].privacy
            ('__class__', 'python_special')
            sage: e.members[68].name, e.members[68].origin, e.members[68].privacy
            ('_doccls', <class 'sage.combinat.partition.Partitions_all_with_category.element_class'>, 'private')
            sage: [(e.members[i].name, e.members[i].overrides, e.members[i].prop_label) for i in range(len(e.members)) if e.members[i].name == '_reduction']
            [('_reduction',
             [<class 'sage.categories.infinite_enumerated_sets.InfiniteEnumeratedSets.element_class'>,
              <class 'sage.categories.enumerated_sets.EnumeratedSets.element_class'>,
              <class 'sage.categories.sets_cat.Sets.Infinite.element_class'>,
              <class 'sage.categories.sets_cat.Sets.element_class'>,
              <class 'sage.categories.sets_with_partial_maps.SetsWithPartialMaps.element_class'>,
              <class 'sage.categories.objects.Objects.element_class'>],
             None)]
            sage: e = SageExplorer(Partition)
            sage: e.get_members()
        """
        if isclass(self.value):
            c0 = self.value
        else:
            c0 = self.value.__class__
        self.valueclass = c0
        members = []
        for name, member in getmembers(c0):
            if isabstract(member) or 'deprecated' in str(type(member)).lower():
                continue
            m = ExploredMember(name, member=member, parent=self.value)
            m.compute_member_type()
            m.compute_origin()
            m.compute_privacy()
            m.compute_property_label(CONFIG_PROPERTIES)
            members.append(m)
        self.members = members

    def get_attributes(self):
        r"""
        Get all attributes for object self.value.

        OUTPUT: List of `Attribute` named tuples.

        TESTS::
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_attributes()
            sage: e.attributes[0].privacy
            'python_special'
            sage: [e.attributes[i].origin for i in range(len(e.attributes)) if e.attributes[i].name in ['_doccls', '_dummy_attribute', 'young_subgroup']]
            [<class 'sage.combinat.partition.Partitions_all_with_category.element_class'>,
             <class 'sage.categories.sets_cat.Sets.element_class'>,
             <class 'sage.combinat.partition.Partitions_all_with_category.element_class'>]
        """
        if not hasattr(self, 'members'):
            self.get_members()
        attributes = []
        for m in self.members:
            if m.member_type.startswith('attribute'):
                attributes.append(m)
        self.attributes = attributes

    def get_methods(self):
        r"""
        Get all methods specifications for object self.value.

        TESTS::
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_methods()
            sage: [e.methods[i].privacy for i in range(len(e.methods)) if e.methods[i].name in ['_latex_coeff_repr', '_sage_', 'is_zero']]
            ['private', 'sage_special', None]
            sage: [e.methods[i].args for i in range(len(e.methods)) if e.methods[i].name in ['_unicode_art_', 'dual_equivalence_graph', 'upper_hook']]
            [['self'], ['self', 'directed', 'coloring'], ['self', 'i', 'j', 'alpha']]
            sage: e = SageExplorer(Partition)
            sage: e.get_methods()
        """
        if not hasattr(self, 'members'):
            self.get_members()
        methods = []
        for m in self.members:
            if not 'method' in m.member_type and not 'function' in m.member_type:
                continue
            m.compute_argspec()
            methods.append(m)
        self.methods = methods

    def compute(self):
        """Get some properties, depending on the object
        Create links between menus and output tabs"""
        obj = self.value
        if obj is None:
            self.make_index()
            return
        if isclass(obj):
            c0 = obj
        else:
            c0 = obj.__class__
        if not hasattr(self, 'objclass') or c0 != self.objclass:
            self.objclass = c0
            self.get_members()
            self.get_attributes()
            self.get_methods()
        self.classname = extract_classname(c0, element_ok=False)
        self.title.value = self.get_title()
        replace_widget_w_css(self.tabs, self.doc)
        visualwidget = get_widget(obj)
        if visualwidget:
            # Reset if necessary, then replace with visualbox
            self.visualbox.children = [self.visualtext]
            self.visualwidget = visualwidget
            def graphical_change(change):
                self.set_value(change.new)
            self.visualwidget.observe(graphical_change, names='value')
            replace_widget_hard(self.visualbox, self.visualtext, self.visualwidget)
        else:
            try:
                self.visualtext.value = repr(obj._ascii_art_())
            except:
                self.visualtext.value = repr(obj)
            if self.visualwidget:
                replace_widget_hard(self.visualbox, self.visualwidget, self.visualtext)
                self.visualwidget = None
        attributes_as_properties = [m for m in self.attributes if m.prop_label]
        methods_as_properties = [m for m in self.methods if m.prop_label]
        attributes = [m for m in self.attributes if not m in attributes_as_properties and not m.name in EXCLUDED_MEMBERS and not m.privacy in ['private', 'sage_special']]
        methods = [m for m in self.methods if not m in methods_as_properties and not m.name in EXCLUDED_MEMBERS and not m.privacy in ['private', 'sage_special']]
        props = [Title('Properties', 2)] # a list of HBoxes, to become self.propsbox's children
        # Properties
        for p in attributes_as_properties + methods_as_properties:
            try:
                value = p.member(obj)
            except:
                print ("Warning: Error in finding method %s" % p.name)
                value = None
            button = self.make_new_page_button(value)
            b_label = p.prop_label
            if type(value) is type(True):
                b_label += '?'
            else:
                b_label += ':'
            props.append(HBox([
                Label(b_label),
                button
            ]))
        if len(self.history) > 1:
            self.propsbox.children = props + [self.make_back_button()]
        else:
            self.propsbox.children = props
        # Object doc
        self.doc.value = to_html(obj.__doc__) # Initialize to object docstring
        # Methods (sorted by definition classes)
        self.selected_menu_value = c0
        bases = []
        basemembers = {}
        for c in getmro(c0):
            bases.append(c)
            basemembers[c] = []
        for m in methods:
            basemembers[m.origin].append(m.name)
        for c in basemembers:
            if not basemembers[c]:
                bases.remove(c)
        menus = []
        for i in range(len(bases)):
            c = bases[i]
            menus.append(Select(rows=12, options = [(m.name, m) for m in methods if m.name in basemembers[c]]
            ))
        self.menus.children = menus
        for i in range(len(bases)):
            c = bases[i]
            self.menus.set_title(i, extract_classname(c))
        def menu_on_change(change):
            self.selected_menu_value = change.new
            self.init_selected_menu_value()
        for menu in self.menus.children:
            menu.observe(menu_on_change, names='value')
        def compute_selected_method(button):
            args = []
            for i in self.inputs.children:
                try:
                    arg = i.value or i.placeholder
                    evaled_arg = eval_in_main(arg)
                    if not arg:
                        self.output.value = to_html("Argument '%s' is empty!" % i.description)
                        return
                    args.append(evaled_arg)
                except:
                    self.output.value = to_html("Could not evaluate argument '%s'" % i.description)
                    return
            try:
                if AlarmInterrupt:
                    alarm(TIMEOUT)
                out = self.selected_menu_value.member(obj, *args)
                if AlarmInterrupt:
                    cancel_alarm()
            except AlarmInterrupt:
                self.output.value = to_html("Timeout!")
            except Exception as e:
                self.output.value = to_html(e)
                return
            self.output.value = to_html(out)
        self.gobutton.description = 'Run!'
        self.gobutton.on_click(compute_selected_method)

    def make_back_button(self):
        r"""
        Make a button for getting back to the previous object.

        TESTS::
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p1 = Partition([3,3,2,1])
            sage: p2 = Partition([5,3,2])
            sage: e = SageExplorer(p1)
            sage: e.make_back_button()
            sage: e.set_value(p2)
            sage: e.make_back_button()
            Button(description=u'Back', icon=u'history', layout=Layout(width=u'7em'), style=ButtonStyle(), tooltip=u'Go back to previous object page')
        """
        if len(self.history) <= 1:
            return
        button = Button(description='Back', icon='history', tooltip="Go back to previous object page", layout=back_button_layout)
        button.on_click(lambda event: self.pop_value()) # No back button in this new (previous object) page
        return button

    def make_new_page_button(self, obj):
        r"""
        Make a button for fetching a new explorer with value `obj`.

        TESTS::
            sage: from sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p1 = Partition([3,3,2,1])
            sage: p2 = Partition([5,3,2])
            sage: e = SageExplorer(p1)
            sage: e.make_new_page_button(p2)
            Button(description=u'[5, 3, 2]', style=ButtonStyle(), tooltip=u'Will close current explorer and open a new one')
        """
        button = Button(description=str(obj), tooltip="Will close current explorer and open a new one")
        button.on_click(lambda b:self.set_value(obj))
        return button

    def display_new_value(self, obj):
        r"""
        A callback for the navigation button.
        """
        self.visualbox.children[0].value = str(obj)

    def get_value(self):
        r"""
        Return math object currently explored.

        TESTS::
            sage: from sage_explorer.sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_value()
            [3, 3, 2, 1]
        """
        return self.value

    def set_value(self, obj):
        r"""
        Set new math object `obj` to the explorer.

        TESTS::
            sage: from sage_explorer.sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: e.get_value()
            [3, 3, 2, 1]
            sage: from sage.combinat.tableau import Tableau
            sage: t = Tableau([[1,2,3,4], [5,6]])
            sage: e.set_value(t)
            sage: e.get_value()
            [[1, 2, 3, 4], [5, 6]]
        """
        self.history.append(obj)
        self.value = obj
        self.compute()

    def pop_value(self):
        r"""
        Set again previous math object to the explorer.

        TESTS::
            sage: from sage_explorer.sage_explorer import SageExplorer
            sage: from sage.combinat.partition import Partition
            sage: p = Partition([3,3,2,1])
            sage: e = SageExplorer(p)
            sage: from sage.combinat.tableau import Tableau
            sage: t = Tableau([[1,2,3,4], [5,6]])
            sage: e.set_value(t)
            sage: e.get_value()
            [[1, 2, 3, 4], [5, 6]]
            sage: e.pop_value()
            sage: e.get_value()
            [3, 3, 2, 1]
        """
        if self.history:
            self.history.pop()
        if self.history:
            self.value = self.history[-1]
        else:
            self.value = None
        self.compute()

    def make_index(self):
        try:
            from ._catalogs import catalogs
        except:
            print("To build the index page, we need some catalogs.")
            catalogs = []
        self.selected_object = None
        self.title.value = "Sage Explorer"
        self.visualbox.children = [Title("Index Page")]
        self.tabs.remove_class('invisible')
        self.tabs.add_class('visible')
        self.gobutton.description = 'Go!'
        menus = []
        for label, catalog in catalogs:
            menu = Select(rows=12, options=make_catalog_menu_options(catalog))
            menus.append(menu)
        self.menus.children = menus
        for i, (label, _) in enumerate(catalogs):
            self.menus.set_title(i, label)
        def menu_on_change(change):
            self.selected_object = change.new
            self.display_new_value(self.selected_object.name)
            self.doctab.value = to_html(change.new.doc)
            self.gobutton.on_click(lambda b:self.set_value(self.selected_object.member))
        for menu in self.menus.children:
            menu.observe(menu_on_change, names='value')
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