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))
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 ]) ])
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')
class CadqueryDisplay(object): types = [ "reset", "fit", "isometric", "right", "front", "left", "rear", "top", "bottom", ] directions = { "left": (1, 0, 0), "right": (-1, 0, 0), "front": (0, 1, 0), "rear": (0, -1, 0), "top": (0, 0, 1), "bottom": (0, 0, -1), "isometric": (1, 1, 1), } def __init__(self): super().__init__() self.info = None self.cq_view = None self.assembly = None self.image_path = join(dirname(__file__), "icons") self.image_paths = [ { UNSELECTED: "%s/no_shape.png" % self.image_path, SELECTED: "%s/shape.png" % self.image_path, MIXED: "%s/mix_shape.png" % self.image_path, EMPTY: "%s/empty_shape.png" % self.image_path, }, { UNSELECTED: "%s/no_mesh.png" % self.image_path, SELECTED: "%s/mesh.png" % self.image_path, MIXED: "%s/mix_mesh.png" % self.image_path, EMPTY: "%s/empty_mesh.png" % self.image_path, }, ] self._display = "cell" self._tools = True self.id = uuid4().hex[:10] self.clean = True def _dump_config(self): print("\nCadDisplay:") config = { k: v for k, v in self.__dict__.items() if not k in [ "cq_view", "output", "info", "clipping", "tree_clipping", "image_paths", "image_path", "view_controls", "check_controls", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCadView:") config = { k: v for k, v in self.cq_view.__dict__.items() if not k in [ "pickable_objects", "scene", "controller", "renderer", "key_lights", "picker", "shapes", "camera", "info", "axes", "grid", "cq_renderer", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCamera:") config = { k: v for k, v in self.cq_view.camera.__dict__["_trait_values"].items() if not k in [ "keys", "matrix", "matrixWorldInverse", "modelViewMatrix", "normalMatrix", "matrixWorld", "projectionMatrix", "comm", ] and not k.startswith("_") } for k, v in config.items(): print(f"- {k:30s}: {v}") # Buttons def create_button(self, image_name, handler, tooltip): button = ImageButton( width=36, height=28, image_path="%s/%s.png" % (self.image_path, image_name), tooltip=tooltip, type=image_name, ) button.on_click(handler) button.add_class("view_button") return button def create_checkbox(self, kind, description, value, handler): checkbox = Checkbox(value=value, description=description, indent=False) checkbox.observe(handler, "value") checkbox.add_class("view_%s" % kind) return checkbox # UI Handler def change_view(self, typ, directions): def reset(b): self.cq_view._reset() def refit(b): self.cq_view.camera.zoom = self.cq_view.camera_initial_zoom self.cq_view._update() def change(b): self.cq_view.camera.position = self.cq_view._add( self.cq_view.bb.center, self.cq_view._scale(directions[typ])) self.cq_view._update() if typ == "fit": return refit elif typ == "reset": return reset else: return change def bool_or_new(self, val): return val if isinstance(val, bool) else val["new"] def toggle_axes(self, change): self.axes = self.bool_or_new(change) self.cq_view.set_axes_visibility(self.axes) def toggle_grid(self, change): self.grid = self.bool_or_new(change) self.cq_view.set_grid_visibility(self.grid) def toggle_center(self, change): self.axes0 = self.bool_or_new(change) self.cq_view.set_axes_center(self.axes0) def toggle_ortho(self, change): self.ortho = self.bool_or_new(change) self.cq_view.toggle_ortho(self.ortho) def toggle_transparent(self, change): self.transparent = self.bool_or_new(change) self.cq_view.set_transparent(self.transparent) def toggle_black_edges(self, change): self.black_edges = self.bool_or_new(change) self.cq_view.set_black_edges(self.black_edges) def toggle_clipping(self, change): if change["name"] == "selected_index": self.cq_view.set_clipping(change["new"]) def create( self, render_shapes=None, render_edges=None, height=None, bb_factor=None, tree_width=None, cad_width=None, quality=None, angular_tolerance=None, optimal_bb=None, edge_accuracy=None, axes=None, axes0=None, grid=None, ortho=None, transparent=None, position=None, rotation=None, zoom=None, mac_scrollbar=None, display=None, tools=None, timeit=None, ): def preset(key, value): return get_default(key) if value is None else value self.height = preset("height", height) self.tree_width = preset("tree_width", tree_width) self.cad_width = preset("cad_width", cad_width) self.bb_factor = preset("bb_factor", bb_factor) self.render_shapes = preset("render_shapes", render_shapes) self.render_edges = preset("render_edges", render_edges) self.quality = preset("quality", quality) self.angular_tolerance = preset("angular_tolerance", angular_tolerance) self.optimal_bb = preset("optimal_bb", optimal_bb) self.edge_accuracy = preset("edge_accuracy", edge_accuracy) self.axes = preset("axes", axes) self.axes0 = preset("axes0", axes0) self.grid = preset("grid", grid) self.ortho = preset("ortho", ortho) self.transparent = preset("transparent", transparent) self.position = preset("position", position) self.rotation = preset("rotation", rotation) self.zoom = preset("zoom", zoom) self.mac_scrollbar = (platform.system() == "Darwin") and preset( "mac_scrollbar", mac_scrollbar) self.timeit = preset("timeit", timeit) self._display = preset("display", display) self._tools = preset("tools", tools) self.black_edges = False # Output widget output_height = self.height * 0.4 - 20 + 2 self.info = Info(self.tree_width, output_height - 6) self.info.html.add_class("scroll-area") ## Threejs rendering of Cadquery objects self.cq_view = CadqueryView( width=self.cad_width, height=self.height, bb_factor=self.bb_factor, quality=self.quality, edge_accuracy=self.edge_accuracy, angular_tolerance=self.angular_tolerance, optimal_bb=self.optimal_bb, render_shapes=self.render_shapes, render_edges=self.render_edges, info=self.info, position=self.position, rotation=self.rotation, zoom=self.zoom, timeit=self.timeit, ) renderer = self.cq_view.create() renderer.add_class("view_renderer") renderer.add_class(f"view_renderer_{self.id}") # Prepare the CAD view tools # Output area self.output = Box([self.info.html]) self.output.layout = Layout( height="%dpx" % output_height, width="%dpx" % self.tree_width, overflow_y="scroll", overflow_x="scroll", ) self.output.add_class("view_output") # Clipping tool self.clipping = Clipping(self.image_path, self.output, self.cq_view, self.tree_width) for normal in ((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)): self.clipping.add_slider(1, -1, 1, 0.01, normal) # Empty dummy Tree View self.tree_view = Output() # Tab widget with Tree View and Clipping tools self.tree_clipping = Tab(layout=Layout(height="%dpx" % (self.height * 0.6 + 20), width="%dpx" % self.tree_width)) self.tree_clipping.children = [self.tree_view, self.clipping.create()] for i, c in enumerate(["Tree", "Clipping"]): self.tree_clipping.set_title(i, c) self.tree_clipping.observe(self.toggle_clipping) self.tree_clipping.add_class("tab-content-no-padding") # Check controls to swith orto, grid and axis self.check_controls = [ self.create_checkbox("axes", "Axes", self.axes, self.toggle_axes), self.create_checkbox("grid", "Grid", self.grid, self.toggle_grid), self.create_checkbox("zero", "@ 0", self.axes0, self.toggle_center), self.create_checkbox("ortho", "Ortho", self.ortho, self.toggle_ortho), self.create_checkbox( "transparent", "Transparency", self.transparent, self.toggle_transparent, ), self.create_checkbox("black_edges", "Black Edges", False, self.toggle_black_edges), ] self.check_controls[-2].add_class("indent") # Buttons to switch camera position self.view_controls = [] for typ in CadqueryDisplay.types: if typ == "refit": tooltip = "Fit view" elif typ == "reset": tooltip = "Reset view" else: tooltip = "Change view to %s" % typ button = self.create_button( typ, self.change_view(typ, CadqueryDisplay.directions), tooltip) self.view_controls.append(button) self.info.version_msg() # only show pure renderer if self._tools == False: return renderer else: return HBox([ VBox([ HBox(self.check_controls[:-2]), self.tree_clipping, self.output ]), VBox([ HBox(self.view_controls + self.check_controls[-2:]), renderer ]), ]) def add_shapes(self, shapes, mapping, tree, reset=True): def count_shapes(shapes): count = 0 for shape in shapes["parts"]: if shape.get("parts") is None: count += 1 else: count += count_shapes(shape) return count self.clear() self.states = {k: v["state"] for k, v in mapping.items()} self.paths = {k: v["path"] for k, v in mapping.items()} self.tree_view = Output() self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] self.progress = Progress(count_shapes(shapes) + 3) with self.tree_view: ipy_display(self.progress.progress) add_shapes_timer = Timer(self.timeit, "add shapes") self.cq_view.add_shapes(shapes, self.progress, reset=reset) add_shapes_timer.stop() configure_display_timer = Timer(self.timeit, "configure display") def set_slider(i, s_min, s_max): s_min = -0.02 if abs(s_min) < 1e-4 else s_min * self.bb_factor s_max = 0.02 if abs(s_max) < 1e-4 else s_max * self.bb_factor self.clipping.sliders[ i].max = 2**31 # first increase max to avoid traitlet error that min > max self.clipping.sliders[ i].min = s_min # set min which now is always < max self.clipping.sliders[i].max = s_max # correct max self.clipping.sliders[i].value = s_max bb = self.cq_view.bb set_slider(1, bb.xmin, bb.xmax) set_slider(3, bb.ymin, bb.ymax) set_slider(5, bb.zmin, bb.zmax) # Tree widget to change visibility self.tree_view = TreeView( image_paths=self.image_paths, tree=tree, state=self.states, layout=Layout(height="%dpx" % (self.height * 0.6 - 25), width="%dpx" % (self.tree_width - 20)), ) self.tree_view.add_class("view_tree") self.tree_view.add_class("scroll-area") if self.mac_scrollbar: self.tree_view.add_class("mac-scrollbar") self.tree_view.observe(self.cq_view.change_visibility(self.paths), "state") self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Set initial state for obj, vals in self.states.items(): for i, val in enumerate(vals): self.cq_view.set_visibility(self.paths[obj], i, val) self.toggle_axes(self.axes) self.toggle_center(self.axes0) self.toggle_grid(self.grid) self.toggle_transparent(self.transparent) self.toggle_black_edges(self.black_edges) self.toggle_ortho(self.ortho) self.clean = False configure_display_timer.stop() if SIDECAR is not None: print("Done, using side car '%s'" % SIDECAR.title) def clear(self): if not self.clean: self.cq_view.clear() # clear tree self.tree_clipping.children = [ Output(), self.tree_clipping.children[1] ] self.clean = True def display(self, widget): if self._display == "cell" or SIDECAR is None: ipy_display(widget) else: SIDECAR.clear_output(True) with SIDECAR: ipy_display(widget) @property def root_group(self): return self.cq_view.root_group
class CadqueryDisplay(object): types = [ "reset", "fit", "isometric", "right", "front", "left", "rear", "top", "bottom", ] directions = { "left": (1, 0, 0), "right": (-1, 0, 0), "front": (0, 1, 0), "rear": (0, -1, 0), "top": (0, 0, 1), "bottom": (0, 0, -1), "isometric": (1, 1, 1), } def __init__(self): super().__init__() self.info = None self.cq_view = None self.assembly = None self.image_path = join(dirname(__file__), "icons", get_default("theme")) self.image_paths = [ { UNSELECTED: join(self.image_path, "no_shape.png"), SELECTED: join(self.image_path, "shape.png"), MIXED: join(self.image_path, "mix_shape.png"), EMPTY: join(self.image_path, "empty_shape.png"), }, { UNSELECTED: join(self.image_path, "no_mesh.png"), SELECTED: join(self.image_path, "mesh.png"), MIXED: join(self.image_path, "mix_mesh.png"), EMPTY: join(self.image_path, "empty_mesh.png"), }, ] self._display = "cell" self._tools = True self.id = uuid4().hex[:10] self.clean = True self.splash = False self.tree_clipping = None def _dump_config(self): print("\nCadDisplay:") config = { k: v for k, v in self.__dict__.items() if not k in [ "cq_view", "output", "info", "clipping", "tree_clipping", "image_paths", "image_path", "view_controls", "check_controls", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCadView:") config = { k: v for k, v in self.cq_view.__dict__.items() if not k in [ "pickable_objects", "scene", "controller", "renderer", "key_lights", "picker", "shapes", "camera", "info", "axes", "grid", "cq_renderer", ] } for k, v in config.items(): print(f"- {k:30s}: {v}") print("\nCamera:") config = { k: v for k, v in self.cq_view.camera.__dict__["_trait_values"].items() if not k in [ "keys", "matrix", "matrixWorldInverse", "modelViewMatrix", "normalMatrix", "matrixWorld", "projectionMatrix", "comm", ] and not k.startswith("_") } for k, v in config.items(): print(f"- {k:30s}: {v}") # Buttons def create_button(self, image_name, handler, tooltip): button = ImageButton( width=36, height=28, image_path=join(self.image_path, f"{image_name}.png"), tooltip=tooltip, type=image_name, ) button.on_click(handler) button.add_class("view_button") return button def create_checkbox(self, kind, description, value, handler): checkbox = Checkbox(value=value, description=description, indent=False) checkbox.observe(handler, "value") checkbox.add_class("view_%s" % kind) return checkbox # UI Handler def change_view(self, typ, directions): def reset(b): self.cq_view._reset_camera() def refit(b): self.cq_view.camera.zoom = self.cq_view.camera_initial_zoom self.cq_view._update() def change(b): self.cq_view.camera.position = self.cq_view._add( self.cq_view.bb.center, self.cq_view._scale(directions[typ])) self.cq_view._update() if typ == "fit": return refit elif typ == "reset": return reset else: return change def bool_or_new(self, val): return val if isinstance(val, bool) else val["new"] def toggle_axes(self, change): self.axes = self.bool_or_new(change) self.cq_view.set_axes_visibility(self.axes) def toggle_grid(self, change): self.grid = self.bool_or_new(change) self.cq_view.set_grid_visibility(self.grid) def toggle_axes0(self, change): self.axes0 = self.bool_or_new(change) self.cq_view.set_axes_center(self.axes0) def toggle_ortho(self, change): self.ortho = self.bool_or_new(change) self.cq_view.toggle_ortho(self.ortho) def toggle_transparent(self, change): self.transparent = self.bool_or_new(change) self.cq_view.set_transparent(self.transparent) def toggle_black_edges(self, change): self.black_edges = self.bool_or_new(change) self.cq_view.set_black_edges(self.black_edges) def toggle_clipping(self, change): if change["name"] == "selected_index": self.cq_view.set_clipping(change["new"]) def init_progress(self, num_shapes): self.progress.progress.value = 0 self.progress.reset(num_shapes) def _set_checkboxes(self): self.checkbox_axes.value = self.axes self.checkbox_grid.value = self.grid self.checkbox_axes0.value = self.axes0 self.checkbox_ortho.value = self.ortho self.checkbox_transparent.value = self.transparent self.checkbox_black_edges.value = self.black_edges def _update_settings(self, **kwargs): preset = lambda key, value: get_default(key ) if value is None else value self.height = preset("height", kwargs.get("height")) self.tree_width = preset("tree_width", kwargs.get("tree_width")) self.cad_width = preset("cad_width", kwargs.get("cad_width")) self.bb_factor = preset("bb_factor", kwargs.get("bb_factor")) self.axes = preset("axes", kwargs.get("axes")) self.axes0 = preset("axes0", kwargs.get("axes0")) self.grid = preset("grid", kwargs.get("grid")) self.ortho = preset("ortho", kwargs.get("ortho")) self.transparent = preset("transparent", kwargs.get("transparent")) self.black_edges = preset("black_edges", kwargs.get("black_edges")) self.mac_scrollbar = preset("mac_scrollbar", kwargs.get("mac_scrollbar")) self.timeit = preset("timeit", kwargs.get("timeit")) self._display = preset("display", kwargs.get("display")) self._tools = preset("tools", kwargs.get("tools")) def _info_height(self, height): return int(height * 0.4) - 20 + 2 def _tree_clipping_height(self, height): return int(height * 0.6) + 17 def _tree_height(self, height): return int(height * 0.6) - 30 def set_size(self, tree_width, width, height): if width is not None: # adapt renderer self.cad_width = width self.cq_view.camera.width = width self.cq_view.renderer.width = width if height is not None: # adapt renderer self.height = height self.cq_view.camera.height = height self.cq_view.renderer.height = height # adapt info box info_height = self._info_height(height) self.info.height = info_height self.info.html.layout.height = px(info_height - 6) self.output.layout.height = px(info_height) # adapt tree and clipping tree_clipping_height = self._tree_clipping_height(height) self.tree_clipping.layout.height = px(tree_clipping_height) tree_height = self._tree_height(height) self.tree_view.layout.height = px(tree_height) if tree_width is not None: # adapt tree and clipping self.tree_clipping.layout.width = px(tree_width) self.tree_view.layout.width = px(tree_width) self.clipping.set_width(tree_width) # adapt info box self.output.layout.width = px(tree_width) self.info.html.layout.width = px(tree_width) # adapt progress bar self.progress.progress.layout.width = px(tree_width) def create( self, height=None, bb_factor=None, tree_width=None, cad_width=None, axes=None, axes0=None, grid=None, ortho=None, transparent=None, mac_scrollbar=None, display=None, tools=None, timeit=None, ): self._update_settings( height=height, bb_factor=bb_factor, tree_width=tree_width, cad_width=cad_width, axes=axes, axes0=axes0, grid=grid, ortho=ortho, transparent=transparent, mac_scrollbar=mac_scrollbar, display=display, tools=tools, timeit=timeit, ) # Output widget output_height = self._info_height(self.height) self.info = Info(self.tree_width, output_height - 6) self.info.html.add_class("scroll-area") if self.mac_scrollbar: self.info.html.add_class("mac-scrollbar") ## Threejs rendering of Cadquery objects self.cq_view = CadqueryView( width=self.cad_width, height=self.height, info=self.info, timeit=self.timeit, ) renderer = self.cq_view.create() renderer.add_class("view_renderer") renderer.add_class(f"view_renderer_{self.id}") # Prepare the CAD view tools # Output area self.output = Box([self.info.html]) self.output.layout = Layout( height=px(output_height), width=px(self.tree_width), overflow_y="hidden", overflow_x="hidden", ) self.output.add_class("view_output") # TODO # if get_default("theme") == "dark": # self.output.add_class("p-Collapse-contents") # Clipping tool self.clipping = Clipping(self.image_path, self.output, self.cq_view, self.tree_width) for normal in ((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)): self.clipping.add_slider(1, -1, 1, 0.01, normal) # Empty dummy Tree View self.tree_view = Output() self.progress = Progress(3, self.tree_width) # Tab widget with Tree View and Clipping tools self.tree_clipping = Tab( layout=Layout(height=px(self._tree_clipping_height(self.height)), width=px(self.tree_width))) self.tree_clipping.children = [self.tree_view, self.clipping.create()] for i, c in enumerate(["Tree", "Clipping"]): self.tree_clipping.set_title(i, c) self.tree_clipping.observe(self.toggle_clipping) self.tree_clipping.add_class("tab-content-no-padding") # Check controls to switch orto, grid and axis self.checkbox_axes = self.create_checkbox("axes", "Axes", self.axes, self.toggle_axes) self.checkbox_grid = self.create_checkbox("grid", "Grid", self.grid, self.toggle_grid) self.checkbox_axes0 = self.create_checkbox("zero", "@ 0", self.axes0, self.toggle_axes0) self.checkbox_ortho = self.create_checkbox("ortho", "Ortho", self.ortho, self.toggle_ortho) self.checkbox_transparent = self.create_checkbox( "transparent", "Transparency", self.transparent, self.toggle_transparent, ) self.checkbox_black_edges = self.create_checkbox( "black_edges", "Black Edges", False, self.toggle_black_edges) self.check_controls = [ self.checkbox_axes, self.checkbox_grid, self.checkbox_axes0, self.checkbox_ortho, self.checkbox_transparent, self.checkbox_black_edges, ] self.check_controls[-2].add_class("indent") # Buttons to switch camera position self.view_controls = [] for typ in CadqueryDisplay.types: if typ == "refit": tooltip = "Fit view" elif typ == "reset": tooltip = "Reset view" else: tooltip = "Change view to %s" % typ button = self.create_button( typ, self.change_view(typ, CadqueryDisplay.directions), tooltip) self.view_controls.append(button) # only show pure renderer if self._tools == False: return renderer else: return HBox([ VBox([ HBox(self.check_controls[:-2]), self.tree_clipping, self.progress.progress, self.output ]), VBox([ HBox(self.view_controls + self.check_controls[-2:]), renderer ]), ]) def add_shapes( self, shapes, mapping, tree, bb, ticks=None, reset_camera=True, bb_factor=None, ambient_intensity=None, direct_intensity=None, default_edgecolor=None, position=None, rotation=None, zoom=None, ): self.clear() self.states = {k: v["state"] for k, v in mapping.items()} self.paths = {k: v["path"] for k, v in mapping.items()} self.tree_view = Output() self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Force reset of camera to inhereit splash settings for first object if self.splash: reset_camera = True self.splash = False with Timer(self.timeit, "", "mesh shapes", 2): self.cq_view.add_shapes( shapes, bb, ticks, self.progress, reset_camera=reset_camera, bb_factor=bb_factor, ambient_intensity=ambient_intensity, direct_intensity=direct_intensity, default_edgecolor=default_edgecolor, position=position, rotation=rotation, zoom=zoom, ) with Timer(self.timeit, "", "configure display", 2): def set_slider(i, s_min, s_max): s_min = -0.02 if abs(s_min) < 1e-4 else s_min * self.bb_factor s_max = 0.02 if abs(s_max) < 1e-4 else s_max * self.bb_factor self.clipping.sliders[ i].max = 2**31 # first increase max to avoid traitlet error that min > max self.clipping.sliders[ i].min = s_min # set min which now is always < max self.clipping.sliders[i].max = s_max # correct max self.clipping.sliders[i].value = s_max bb = self.cq_view.bb set_slider(1, bb.xmin, bb.xmax) set_slider(3, bb.ymin, bb.ymax) set_slider(5, bb.zmin, bb.zmax) # Tree widget to change visibility self.tree_view = TreeView( image_paths=self.image_paths, tree=tree, state=self.states, layout=Layout(height=px(self._tree_height(self.height)), width=px(self.tree_width - 20)), ) self.tree_view.add_class("view_tree") self.tree_view.add_class("scroll-area") if self.mac_scrollbar: self.tree_view.add_class("mac-scrollbar") self.tree_view.observe(self.cq_view.change_visibility(self.paths), "state") self.tree_clipping.children = [ self.tree_view, self.tree_clipping.children[1] ] # Set initial state for obj, vals in self.states.items(): for i, val in enumerate(vals): self.cq_view.set_visibility(self.paths[obj], i, val) self._set_checkboxes() self.toggle_axes(self.axes) self.toggle_axes0(self.axes0) self.toggle_grid(self.grid) self.toggle_transparent(self.transparent) self.toggle_black_edges(self.black_edges) self.toggle_ortho(self.ortho) self.clean = False def clear(self): if not self.clean: self.cq_view.clear() self.info.clear() # clear tree self.tree_clipping.children = [ Output(), self.tree_clipping.children[1] ] self.clean = True def display(self, widget): if self._display == "cell" or SIDECAR is None: ipy_display(widget) else: SIDECAR.clear_output(True) with SIDECAR: ipy_display(widget) @property def root_group(self): return self.cq_view.root_group