bmcs-utils
Advanced tools
| import traits.api as tr | ||
| from .editors import EditorFactory | ||
| import ipywidgets as ipw | ||
| class DictEditor(EditorFactory): | ||
| """Polymorphic instance editor. | ||
| """ | ||
| _values = tr.Dict | ||
| def render(self): | ||
| print('render dict') | ||
| self._values = getattr(self.model, self.name) | ||
| list_keys = list(items) | ||
| print('values', self._values) | ||
| if len(list_keys) == 0: | ||
| key = '<empty list>' | ||
| return ipw.VBox() | ||
| else: | ||
| key = list_keys[0] | ||
| drop_down = ipw.Dropdown(description=self.label, value=key, | ||
| tooltip=self.tooltip, options=list_keys) | ||
| drop_down.observe(self._item_selection_changed,'value') | ||
| self.instance_pane = ipw.VBox(self._render_instance(key)) | ||
| return ipw.VBox([drop_down, self.instance_pane]) | ||
| def _render_instance(self, key): | ||
| app_window = self.controller.app_window | ||
| view_model = self._values[key] | ||
| instance_controller = view_model.get_controller(app_window=app_window) | ||
| model_editor = instance_controller.model_editor | ||
| return [model_editor] | ||
| def _item_selection_changed(self, event): | ||
| key = event['new'] | ||
| self.instance_pane.children = self._render_instance(key) |
| """ | ||
| This helps in getting a 3D geometry from a 2D/3D curve by providing | ||
| the section contour as an array of points and the 3D curve. | ||
| See: | ||
| https://stackoverflow.com/a/66501381/9277594 | ||
| and | ||
| http://www.songho.ca/opengl/gl_cylinder.html#pipe | ||
| for reference | ||
| """ | ||
| import numpy as np | ||
| import k3d | ||
| class Extruder: | ||
| def __init__(self, start_contour_points, path_points): | ||
| # path_points should be numpy array with the shape (n, 2), or (n, 3) | ||
| # start_contour_points should be numpy array with the shape (n, 2), or (n, 3) | ||
| path_points, start_contour_points = self.adapt_dimensions(path_points, start_contour_points) | ||
| self.start_contour_points = start_contour_points | ||
| self.path_points = path_points | ||
| self.generate_3d_contours() | ||
| @staticmethod | ||
| def adapt_dimensions(path_points, start_contour_points): | ||
| # If path is 2D, convert it to 3D path ([[x1, y1, 0], [x2, y2, 0].. ]) | ||
| if path_points.shape[1] == 2: | ||
| path_points = np.insert(path_points, 2, 0, axis=1) | ||
| # If contour is 2D, convert it to 3D ([[x1, y1, 0], [x2, y2, 0].. ]) | ||
| if start_contour_points.shape[1] == 2: | ||
| start_contour_points = np.insert(start_contour_points, 2, 0, axis=1) | ||
| # switch columns 0 and 1 | ||
| start_contour_points[:, [0, 1]] = start_contour_points[:, [1, 0]] | ||
| return path_points, start_contour_points | ||
| @staticmethod | ||
| def get_circle_points(r=10, n=100): | ||
| return np.array([(np.cos(2 * np.pi / n * x) * r, np.sin(2 * np.pi / n * x) * r) for x in range(0, n + 1)]) | ||
| def get_3d_contours(self): | ||
| return self.contours_3d | ||
| def generate_3d_contours(self): | ||
| path_points = np.copy(self.path_points) | ||
| start_contour_points = np.copy(self.start_contour_points) | ||
| self.transform_first_contour(path_points, start_contour_points) | ||
| contours = [start_contour_points] | ||
| for i in range(int(len(path_points)) - 1): | ||
| # print('i:i+3 = ', i, ':', str(i+3)) | ||
| # if they're almost equal, skip it because adding two contours in same position will damage the algorithm | ||
| if np.allclose(path_points[i + 1], path_points[i]): | ||
| continue | ||
| path_3p = path_points[i:i + 3] | ||
| # print('path_3p= ', path_3p) | ||
| con = self._project_contour(start_contour_points, path_3p) | ||
| # print('con:', con) | ||
| start_contour_points = con | ||
| contours.append(con) | ||
| self.contours_3d = np.array(contours) | ||
| return self.contours_3d | ||
| @staticmethod | ||
| def transform_first_contour(path, contour_points, adapt_dimensions=False): | ||
| path = path.astype(np.float32) | ||
| if adapt_dimensions: | ||
| path, contour_points = Extruder.adapt_dimensions(path, contour_points) | ||
| path_count = len(path) | ||
| points_count = len(contour_points) | ||
| matrix = Matrix4() | ||
| if path_count > 0: | ||
| if path_count > 1: | ||
| matrix.look_at(path[1] - path[0]) | ||
| matrix.translate(path[0]) | ||
| # NOTE: the contour vertices are transformed here | ||
| for i in range(points_count): | ||
| contour_points[i] = matrix.multiply_with_vector(contour_points[i]) | ||
| return contour_points | ||
| def _project_contour(self, Q1_contour, path_3p): | ||
| # find direction vectors; v1 and v2 | ||
| if len(path_3p) == 2: | ||
| Q1, Q2 = path_3p[0:2] | ||
| Q3 = Q2 + (Q2 - Q1) | ||
| else: | ||
| Q1, Q2, Q3 = path_3p[0:3] # path_3p should have 3 points, but this to make sure | ||
| v1 = Q2 - Q1 | ||
| v2 = Q3 - Q2 | ||
| # print('Q1_contour: ', Q1_contour) | ||
| # normal vector of plane at Q2 | ||
| normal = v1 + v2 | ||
| # define plane equation at Q2 with normal and point | ||
| plane = Plane(normal, Q2) | ||
| # project each vertex of contour to the plane | ||
| Q2_contour = [] | ||
| for i in range(len(Q1_contour)): | ||
| line = Line(v1, Q1_contour[i]) # define line with direction and point | ||
| intersect_point = plane.intersect(line) # find the intersection point | ||
| Q2_contour.append(intersect_point) | ||
| # return the projected vertices of contour at Q2 | ||
| return np.array(Q2_contour) | ||
| def show_in_k3d(self, scale=1): | ||
| plot = k3d.plot(name='points') | ||
| plt_points = k3d.points(positions=self.path_points, point_size=scale * 5) | ||
| plt_points.color = 0xff0000 | ||
| plot += plt_points | ||
| plt_points2 = k3d.points(positions=self.contours_3d, point_size=scale * 5) | ||
| plot += plt_points2 | ||
| plt_points.shader = '3d' | ||
| plot.display() | ||
| def show_in_k3d_as_surface(self, with_ends=True): | ||
| plot = k3d.plot(name='points') | ||
| vertices, indices = self.get_triangulation_vertices_and_indices(with_ends=with_ends) | ||
| mesh = k3d.mesh(vertices, indices, | ||
| color=0xc73737, | ||
| side='double') | ||
| plot += mesh | ||
| plot.display() | ||
| def _get_indices(self): | ||
| # number of contours | ||
| n_c = self.contours_3d.shape[0] | ||
| # number of points in each contour | ||
| n_p = self.contours_3d.shape[1] | ||
| mask = np.arange(n_c * n_p - n_p) | ||
| mask = mask[n_p - 1::n_p] | ||
| indices_1_0 = np.arange(n_c * n_p - n_p) | ||
| indices_1_1 = np.arange(1, n_c * n_p - n_p + 1) | ||
| indices_1_1[mask] = indices_1_1[mask] - n_p | ||
| indices_1_2 = np.arange(n_p, n_c * n_p) | ||
| indices_1 = np.stack((indices_1_0, indices_1_1, indices_1_2), axis=1) | ||
| indices_2_0 = np.arange(n_p, n_c * n_p) | ||
| indices_2_1 = np.arange(n_p + 1, n_c * n_p + 1) | ||
| indices_2_1[mask] = indices_2_1[mask] - n_p | ||
| indices_2_2 = np.arange(1, n_c * n_p - n_p + 1) | ||
| indices_2_2[mask] = indices_2_2[mask] - n_p | ||
| indices_2 = np.stack((indices_2_0, indices_2_1, indices_2_2), axis=1) | ||
| return np.vstack((indices_1, indices_2)) | ||
| def _get_vertices(self): | ||
| contours = self.contours_3d | ||
| return np.reshape(contours, (int(contours.shape[0] * contours.shape[1]), 3)) | ||
| def get_triangulation_vertices_and_indices(self, with_ends=False): | ||
| if with_ends: | ||
| indices = self._get_indices() | ||
| # add ends indices | ||
| indices = np.vstack((indices, self._get_start_and_end_surfaces_indices())) | ||
| return self._get_vertices(), indices | ||
| else: | ||
| return self._get_vertices(), self._get_indices() | ||
| def _get_start_and_end_surfaces_indices(self): | ||
| # number of points in a contour | ||
| n_p = self.contours_3d.shape[1] | ||
| # number of contours | ||
| n_c = self.contours_3d.shape[0] | ||
| # number of first/last surface indices | ||
| n_i = n_p - 2 | ||
| start_surface_indices = self._generate_start_or_end_surface_indices(0, n_i) | ||
| end_surface_indices = self._generate_start_or_end_surface_indices(n_p * n_c - n_p, n_i) | ||
| return np.vstack((start_surface_indices, end_surface_indices)) | ||
| def _generate_start_or_end_surface_indices(self, start_i, n_i): | ||
| surface_1 = np.full(n_i, start_i) | ||
| surface_2 = np.arange(start_i + 1, start_i + 1 + n_i) | ||
| surface_3 = np.arange(start_i + 2, start_i + 2 + n_i) | ||
| return np.stack((surface_1, surface_2, surface_3), axis=1) | ||
| class Matrix4: | ||
| def __init__(self): | ||
| self.m = np.zeros(([4, 4])) | ||
| # @staticmethod | ||
| # def identity(): | ||
| # matrix = Matrix4() | ||
| # matrix.m = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]) | ||
| # return matrix | ||
| def multiply_with_vector(self, vector): | ||
| m = self.m | ||
| return np.array([ | ||
| m[0, 0] * vector[0] + m[0, 1] * vector[1] + m[0, 2] * vector[2] + m[0, 3], | ||
| m[1, 0] * vector[0] + m[1, 1] * vector[1] + m[1, 2] * vector[2] + m[1, 3], | ||
| m[2, 0] * vector[0] + m[2, 1] * vector[1] + m[2, 2] * vector[2] + m[2, 3]]) | ||
| def normalize(self, a, axis=-1, order=2): | ||
| l2 = np.atleast_1d(np.linalg.norm(a, order, axis)) | ||
| l2[l2 == 0] = 1 | ||
| return (a / np.expand_dims(l2, axis))[0] | ||
| def look_at(self, target): | ||
| EPSILON = 0.00001 | ||
| position = np.array([self.m[0, 3], self.m[1, 3], self.m[2, 3]]) | ||
| forward = target - position | ||
| forward = self.normalize(forward) | ||
| # compute temporal up vector | ||
| # if forward vector is near Y-axis, use up vector (0,0,-1) or (0,0,1) | ||
| if abs(forward[0]) < EPSILON and abs(forward[2]) < EPSILON: | ||
| # forward vector is pointing +Y axis | ||
| if forward[1] > 0: | ||
| up = np.array([0, 0, -1]) | ||
| # forward vector is pointing -Y axis | ||
| else: | ||
| up = np.array([0, 0, 1]) | ||
| else: | ||
| # assume up vector is +Y axis | ||
| up = np.array([0, 1, 0]) | ||
| # compute left vector | ||
| left = np.cross(up, forward) | ||
| left = self.normalize(left) | ||
| # re-compute up vector | ||
| up = np.cross(forward, left) | ||
| # NOTE: overwrite rotation and scale info of the current matrix | ||
| # this->setColumn(0, left) | ||
| # this->setColumn(1, up) | ||
| # this->setColumn(2, forward) | ||
| self.m[0:3, 0] = left | ||
| self.m[0:3, 1] = up | ||
| self.m[0:3, 2] = forward | ||
| def translate(self, v): | ||
| m = np.identity(4) | ||
| self.m[0, :] += v[0] * m[3, :] | ||
| self.m[1, :] += v[1] * m[3, :] | ||
| self.m[2, :] += v[2] * m[3, :] | ||
| class Line: | ||
| def __init__(self, v, p): | ||
| self.v = v # /np.sqrt(sum(v*v)) | ||
| self.p = p | ||
| class Plane: | ||
| def __init__(self, normal, point): | ||
| self.normal = normal # /np.sqrt(sum(normal*normal)) | ||
| self.point = point | ||
| self.d = -np.dot(self.normal, self.point) # -(a*x0 + b*y0 + c*z0) | ||
| def intersect(self, line): | ||
| # from line = p + t * v | ||
| p = line.p # (x0, y0, z0) | ||
| v = line.v # (x, y, z) | ||
| # dot products | ||
| dot1 = np.dot(self.normal, p) # a*x0 + b*y0 + c*z0 | ||
| dot2 = np.dot(self.normal, v) # a*x + b*y + c*z | ||
| # if denominator=0, no intersect | ||
| if dot2 == 0: | ||
| print('dot2 == 0!') | ||
| return np.array([0, 0, 0]) | ||
| # find t = -(a*x0 + b*y0 + c*z0 + d) / (a*x + b*y + c*z) | ||
| t = -(dot1 + self.d) / dot2 | ||
| # find intersection point | ||
| return p + (v * t) | ||
| if __name__ == '__main__': | ||
| path = np.array([[0, 0, 0], [50, 0, 0], [100, 50, 0], [150, 50, 0], [200, 0, 0]]) | ||
| ''' Example with circle contour (cross-section) ''' | ||
| circle_points = Extruder.get_circle_points(r=25, n=100) | ||
| contours_3d = Extruder.get_3d_contours(circle_points, path) | ||
| # show_in_k3d(path, contours_3d) | ||
| ''' Example with rectangular contour (cross-section) ''' | ||
| # rect_width = 250 | ||
| # rect_height = 400 | ||
| # contour = np.array([[0, rect_width / 2, rect_height / 2], [0, -rect_width / 2, rect_height / 2], | ||
| # [0, -rect_width / 2, -rect_height / 2], [0, rect_width / 2, -rect_height / 2]]) | ||
| # contours_3d = get_3d_contours(contour, path_xyz=path) |
| import bmcs_utils.api as bu | ||
| import k3d | ||
| class K3DUtils: | ||
| @staticmethod | ||
| def add_circle(k3d_plot, path, r, wireframe=False): | ||
| n = 100 | ||
| # path = np.array([[-4000, 0, -4000], [4000, 0, -4000]]) | ||
| first_contour = bu.Extruder.get_circle_points(r=r, n=n)[int(n / 2):, :] | ||
| extruder = bu.Extruder(first_contour, path) | ||
| vertices, indices = extruder.get_triangulation_vertices_and_indices(with_ends=False) | ||
| # extruder.show_in_k3d_as_surface(with_ends=False) | ||
| mesh = k3d.mesh(vertices, | ||
| indices, | ||
| color=0xde2121, | ||
| opacity=0.2, | ||
| side='double') | ||
| if wireframe: | ||
| wf = k3d.lines(vertices, | ||
| indices, | ||
| width=35, | ||
| shader='mesh', | ||
| color=0xde2121) | ||
| k3d_plot += wf | ||
| k3d_plot += mesh | ||
| @staticmethod | ||
| def add_ref_plane(k3d_plot): | ||
| z = -6000 | ||
| size = 30000 | ||
| ref_plane = k3d.mesh([[size, size, z], [-size, size, z], [-size, -size, z], [size, -size, z]], | ||
| [[0, 1, 2], [2, 3, 0]], | ||
| side='double', | ||
| color=0xe6e6e6) | ||
| k3d_plot += ref_plane |
| import matplotlib.pyplot as plt | ||
| plot_colors = ['#000000', '#bc2122', '#55679e', '#69b628', '#dacf2f', '#ff6600'] | ||
| def set_latex_mpl_format(): | ||
| plt.rcParams["font.family"] = "Times New Roman" | ||
| plt.rcParams["font.size"] = 15 | ||
| # To have math like LaTeX | ||
| plt.rcParams['mathtext.fontset'] = 'cm' | ||
| plt.rcParams['mathtext.rm'] = 'serif' |
| from .model import Model | ||
| from .trait_types.dict_type import Dict | ||
| import traits.api as tr | ||
| from .view import View | ||
| class ModelDict(Model): | ||
| items = Dict({}) | ||
| def __setitem__(self, key, value): | ||
| old_value = self.items.get(key, None) | ||
| if old_value: | ||
| old_value.parents.remove(self) | ||
| value.name = str(key) | ||
| self.items[key] = value | ||
| value.parents.add(self) | ||
| self.notify_graph_change('Notification from child %s' % 'item') | ||
| def __delitem__(self, key): | ||
| value = self.items[key] | ||
| value.parents.remove(self) | ||
| del self.items[key] | ||
| self.notify_graph_change('Notification from child %s' % 'item') | ||
| def __getitem__(self, key): | ||
| return self.items[key] | ||
| tree = tr.Property | ||
| def _get_tree(self): | ||
| return list(self.items) | ||
| tree_submodels = tr.Property(depends_on='graph_changed') | ||
| @tr.cached_property | ||
| def _get_tree_submodels(self): | ||
| return [self.items[key] for key in self.tree] | ||
| def values(self): | ||
| return self.items.values() | ||
| def keys(self): | ||
| return self.items.keys() |
| import traits.api as tr | ||
| state_change_counter = 0 | ||
| class ModelNotifyMixin(tr.HasTraits): | ||
| """Mixin for notifications | ||
| """ | ||
| name = tr.Str("<unnamed>") | ||
| depends_on = tr.List(tr.Str, []) | ||
| def traits_init(self): | ||
| for name in self.depends_on: | ||
| trait = self.trait(name) | ||
| if trait is None: | ||
| return | ||
| #raise ValueError('no trait named %s' % name) | ||
| trait_type = trait.trait_type | ||
| if trait_type is None: | ||
| raise TypeError('trait type not specified for %s, %s' % self, name) | ||
| # value = self.trait_get(name) | ||
| value = getattr(self, name, None) | ||
| # print('name', name, self.__class__, trait_type, value) | ||
| post_setattr = getattr(trait_type, 'post_setattr', None) | ||
| if post_setattr: | ||
| post_setattr(self, name, value) | ||
| # trait_type.post_setattr(self, name, value) | ||
| return | ||
| # name_ = trait_type.get_name_(name) | ||
| # trait_ = getattr(self, name_, None) | ||
| # if trait_ is None: | ||
| # value = getattr(self, name, None) | ||
| # if value: | ||
| # trait_type.post_setattr(self, name, value) | ||
| # trait_ = getattr(self, name_, None) | ||
| # if trait_: | ||
| # trait_.parents.add(self) | ||
| parents = tr.Set(tr.WeakRef, {}) | ||
| _state_change_debug = tr.Bool(False) | ||
| state_change_debug = tr.Property(tr.Bool) | ||
| def _get_state_change_debug(self): | ||
| if self._state_change_debug == True: | ||
| return True | ||
| for parent in self.parents: | ||
| if parent.state_change_debug == True: | ||
| return True | ||
| def _set_state_change_debug(self, value=True): | ||
| self._state_change_debug = value | ||
| @tr.observe('+TIME,+MESH,+MAT,+CS,+BC,+ALG,+FE,+DSC,+GEO,+ITR') | ||
| def notify_value_change(self, event): | ||
| if self.state_change_debug: | ||
| print('value_changed', self, event) | ||
| self.value_changed = True | ||
| self.state_changed = True | ||
| self.notify_parents_value_changed() | ||
| def notify_graph_change(self, event): | ||
| if self.state_change_debug: | ||
| print('graph_changed', self, event) | ||
| self.graph_changed = True | ||
| self.state_changed = True | ||
| self.notify_parents_graph_changed() | ||
| value_changed = tr.Event | ||
| graph_changed = tr.Event | ||
| state_changed = tr.Event | ||
| @tr.observe('state_changed') | ||
| def record_state_change(self, event): | ||
| global state_change_counter | ||
| state_change_counter += 1 | ||
| def reset_state_change(self): | ||
| global state_change_counter | ||
| state_change_counter = 0 | ||
| state_change_counter = tr.Property | ||
| def _get_state_change_counter(self): | ||
| global state_change_counter | ||
| return state_change_counter | ||
| def notify_parents_graph_changed(self): | ||
| for parent in self.parents: | ||
| if parent: # ignore if parent is garbage collected | ||
| parent.notify_graph_change('Notification from child %s' % self) | ||
| def notify_parents_value_changed(self): | ||
| for parent in self.parents: | ||
| if parent: # ignore if parent is garbage collected | ||
| parent.notify_value_change('Notification from child %s' % self) | ||
| def __del__(self): | ||
| if self.state_change_debug: | ||
| print('deleting %s', self) |
| import traits.api as tr | ||
| from .controller import Controller | ||
| from .tree_node import BMCSNode | ||
| class ModelTreeNodeMixin(tr.HasTraits): | ||
| """Base class for interactive bmcs_utils | ||
| """ | ||
| name = tr.Str("<unnamed>") | ||
| tree = [] # kept only for backward compatibility | ||
| ipw_tree = [] | ||
| def get_tree_items(self): | ||
| return self.tree + self.ipw_tree | ||
| tree_submodels = tr.Property(depends_on='graph_changed') | ||
| @tr.cached_property | ||
| def _get_tree_submodels(self): | ||
| submodels = [] | ||
| for name in self.get_tree_items(): | ||
| trait = self.trait(name) | ||
| trait_type = trait.trait_type | ||
| name_ = trait_type.get_name_(name) | ||
| trait_ = getattr(self, name_, None) | ||
| if trait_ is None: | ||
| raise ValueError('trait %s not found in %s' % (name_, self)) | ||
| submodels.append(trait_) | ||
| return submodels | ||
| def get_tree_subnode(self, name): | ||
| # Construct a tree structure of instances tagged by `tree` | ||
| submodels = self.tree_submodels | ||
| tree_subnodes = [ | ||
| submodel.get_tree_subnode(node_name) | ||
| for node_name, submodel in zip(self.get_tree_items(), submodels) | ||
| ] | ||
| return (name, self, tree_subnodes) | ||
| as_tree_node = get_tree_subnode | ||
| #------------------------------------------------------------------------- | ||
| # | ||
| # Copyright (c) 2009, IMB, RWTH Aachen. | ||
| # All rights reserved. | ||
| # | ||
| # This software is provided without warranty under the terms of the BSD | ||
| # license included in simvisage/LICENSE.txt and may be redistributed only | ||
| # under the conditions described in the aforementioned license. The license | ||
| # is also available online at http://www.simvisage.com/licenses/BSD.txt | ||
| # | ||
| # Thanks for using Simvisage open source! | ||
| # | ||
| # Created on Aug 7, 2009 by: rchx | ||
| import traits.api as tr | ||
| from .trait_types import TraitBase | ||
| from bmcs_utils.editors import DictEditor | ||
| class Dict(TraitBase, tr.Dict): | ||
| """List container.""" | ||
| editor_factory = DictEditor |
| # ------------------------------------------------------------------------- | ||
| # | ||
| # Copyright (c) 2009, IMB, RWTH Aachen. | ||
| # All rights reserved. | ||
| # | ||
| # This software is provided without warranty under the terms of the BSD | ||
| # license included in simvisage/LICENSE.txt and may be redistributed only | ||
| # under the conditions described in the aforementioned license. The license | ||
| # is also available online at http://www.simvisage.com/licenses/BSD.txt | ||
| # | ||
| # Thanks for using Simvisage open source! | ||
| # | ||
| # Created on Aug 7, 2009 by: rchx | ||
| from .trait_types import TraitBase | ||
| from traits.api import TraitType, TraitError | ||
| from bmcs_utils.editors import EitherTypeEditor | ||
| class EitherType2(TraitBase, TraitType): | ||
| """Property trait.""" | ||
| editor_factory = EitherTypeEditor | ||
| info_text = 'Property trait test' | ||
| def __init__(self, options=[], on_option_change=None, **metadata): | ||
| # validate that these are trait types | ||
| self.options_dict = {key: value for key, value in options} | ||
| self.on_option_change = on_option_change | ||
| metadata.update({'options': options}) | ||
| super().__init__(**metadata) | ||
| self.map = {} | ||
| def validate(self, object, name, value): | ||
| if value in self.options_dict: | ||
| return value | ||
| self.error(object, name, value) | ||
| def set(self, obj, name, value): | ||
| self.pre_setattr(obj, name) | ||
| # setattr(self, name, value) | ||
| self.set_value(obj, name, value) | ||
| self.post_setattr(obj, name, value) | ||
| def pre_setattr(self, object, name): | ||
| if name in object.depends_on: | ||
| old_value = getattr(object, name + '_', None) | ||
| if old_value: | ||
| old_value.parents.remove(object) | ||
| def post_init(self, object, name, value): | ||
| # check if the last instance of the klass has been | ||
| # registered earlier in the trait history | ||
| klass = self.options_dict.get(value, None) | ||
| new_value = klass() | ||
| # set the shadow attribute | ||
| # editor uses it to associate the value with the option. | ||
| setattr(object, name + "_", new_value) | ||
| new_value.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % new_value) | ||
| if self.on_option_change: | ||
| getattr(object, self.on_option_change)() | ||
| def post_setattr(self, object, name, value): | ||
| # check if the last instance of the klass has been | ||
| # registered earlier in the trait history | ||
| klass = self.options_dict.get(value, None) | ||
| new_value = klass() | ||
| # set the shadow attribute | ||
| # editor uses it to associate the value with the option. | ||
| setattr(object, name + "_", new_value) | ||
| if name in object.depends_on: | ||
| new_value.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % new_value) | ||
| if self.on_option_change: | ||
| getattr(object, self.on_option_change)() | ||
| def get_name_(self, name): | ||
| return name + '_' | ||
| def get(self, obj, name): | ||
| val = self.get_value(obj, name) | ||
| if val is None: | ||
| val = self.default_value | ||
| return val | ||
| def get_default_value(self): | ||
| '''Take the first class to construct the value''' | ||
| key, _ = self.options[0] | ||
| return (0, key) | ||
| def full_info(self, object, name, value): | ||
| """ Returns a description of the trait. | ||
| """ | ||
| values = self.options_dict | ||
| return " or ".join([repr(x) for x in values]) |
| # ------------------------------------------------------------------------- | ||
| # | ||
| # Copyright (c) 2009, IMB, RWTH Aachen. | ||
| # All rights reserved. | ||
| # | ||
| # This software is provided without warranty under the terms of the BSD | ||
| # license included in simvisage/LICENSE.txt and may be redistributed only | ||
| # under the conditions described in the aforementioned license. The license | ||
| # is also available online at http://www.simvisage.com/licenses/BSD.txt | ||
| # | ||
| # Thanks for using Simvisage open source! | ||
| # | ||
| # Created on Aug 7, 2009 by: rchx | ||
| from .trait_types import TraitBase | ||
| from traits.api import TraitType, TraitError | ||
| from bmcs_utils.editors import EitherTypeEditor | ||
| class Selector(TraitBase, TraitType): | ||
| """Property trait.""" | ||
| editor_factory = EitherTypeEditor | ||
| info_text = 'Property trait test' | ||
| def __init__(self, options=[], on_option_change=None, **metadata): | ||
| # validate that these are trait types | ||
| self.options_dict = {key: value for key, value in options} | ||
| self.on_option_change = on_option_change | ||
| metadata.update({'options': options}) | ||
| super().__init__(**metadata) | ||
| self.map = {} | ||
| def validate(self, object, name, value): | ||
| if value in self.options_dict: | ||
| return value | ||
| self.error(object, name, value) | ||
| def set(self, obj, name, value): | ||
| self.pre_setattr(obj, name) | ||
| # setattr(self, name, value) | ||
| self.set_value(obj, name, value) | ||
| self.post_setattr(obj, name, value) | ||
| def pre_setattr(self, object, name): | ||
| if name in object.children: | ||
| selector_trait = object.trait(name) | ||
| if selector_trait: | ||
| old_value = selector_trait.val | ||
| if old_value: | ||
| old_value.parents.remove(object) | ||
| def post_setattr(self, object, name, value): | ||
| self.pre_setattr(obj, name) | ||
| # check if the last instance of the klass has been | ||
| # registered earlier in the trait history | ||
| klass = self.options_dict.get(value, None) | ||
| sub_obj = klass() | ||
| # set the shadow attribute | ||
| # editor uses it to associate the value with the option. | ||
| selector_trait = object.trait(name) | ||
| selector_trait.obj = sub_obj | ||
| # setattr(object, name + "_", new_value) | ||
| if name in object.children: | ||
| sub_obj.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % sub_obj) | ||
| if self.on_option_change: | ||
| getattr(object, self.on_option_change)() | ||
| def get_name_(self, name): | ||
| return name | ||
| def get(self, obj, name): | ||
| val = self.get_value(obj, name) | ||
| if val is None: | ||
| val = self.default_value | ||
| return val | ||
| def get_default_value(self): | ||
| '''Take the first class to construct the value''' | ||
| key, _ = self.options[0] | ||
| return (0, key) | ||
| def full_info(self, object, name, value): | ||
| """ Returns a description of the trait. | ||
| """ | ||
| values = self.options_dict | ||
| return " or ".join([repr(x) for x in values]) |
| from .trait_types import TraitBase | ||
| import traits.api as tr | ||
| from bmcs_utils.editors import InstanceEditor | ||
| class WeakRef(TraitBase, tr.WeakRef): | ||
| editor_factory = InstanceEditor | ||
| def get_sub_nodes(self): | ||
| return [] | ||
| def set(self, obj, name, value): | ||
| self.pre_setattr(obj, name) | ||
| super(WeakRef, self).set(obj, name, value) | ||
| self.post_setattr(obj, name, value) | ||
| def pre_setattr(self, object, name): | ||
| if name in object.depends_on: | ||
| old_value = getattr(object, name + '_', None) | ||
| if old_value: | ||
| old_value.parents.remove(object) | ||
| def post_setattr(self, object, name, value): | ||
| if value and name in object.depends_on: | ||
| value.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % value) | ||
| Metadata-Version: 2.1 | ||
| Name: bmcs-utils | ||
| Version: 0.0.31a0 | ||
| Version: 0.0.32a0 | ||
| Summary: Suite of utilities for to implementation of bmcs_utils for brittle-matrix composites. | ||
@@ -9,17 +9,2 @@ Home-page: https://github.com/bmcs-group/bmcs_utils | ||
| License: MIT | ||
| Description: | ||
| # General utilities and practices | ||
| This repository contains development patterns | ||
| applied for the bmcs packges. They distinguish | ||
| three development phases: | ||
| * Formulation of model components using using jupyter notebooks with Python package | ||
| * Implementation of Python packages | ||
| * Model presentation using examples via Jupyter Notebooks | ||
| The detailed documentation of the package is provided | ||
| [here](docs/README.md) | ||
| Platform: UNKNOWN | ||
| Classifier: License :: OSI Approved :: MIT License | ||
@@ -33,1 +18,15 @@ Classifier: Programming Language :: Python | ||
| Description-Content-Type: text/markdown | ||
| # General utilities and practices | ||
| This repository contains development patterns | ||
| applied for the bmcs packges. They distinguish | ||
| three development phases: | ||
| * Formulation of model components using using jupyter notebooks with Python package | ||
| * Implementation of Python packages | ||
| * Model presentation using examples via Jupyter Notebooks | ||
| The detailed documentation of the package is provided | ||
| [here](docs/README.md) |
@@ -11,3 +11,6 @@ README.md | ||
| bmcs_utils/model.py | ||
| bmcs_utils/model_dict.py | ||
| bmcs_utils/model_list.py | ||
| bmcs_utils/model_notify_mixin.py | ||
| bmcs_utils/model_tree_node_mixin.py | ||
| bmcs_utils/mpl_utils.py | ||
@@ -34,2 +37,3 @@ bmcs_utils/parametric_study.py | ||
| bmcs_utils/editors/__init__.py | ||
| bmcs_utils/editors/dict_editor.py | ||
| bmcs_utils/editors/editor_factory.py | ||
@@ -44,7 +48,16 @@ bmcs_utils/editors/editors.py | ||
| bmcs_utils/editors/progress_editor.py | ||
| bmcs_utils/k3d_utils/__init__.py | ||
| bmcs_utils/k3d_utils/extrusion_for_3d_curve.py | ||
| bmcs_utils/k3d_utils/k3d_utils.py | ||
| bmcs_utils/misc/__init__.py | ||
| bmcs_utils/misc/plot_tools.py | ||
| bmcs_utils/trait_types/__init__.py | ||
| bmcs_utils/trait_types/dict_type.py | ||
| bmcs_utils/trait_types/either_type.py | ||
| bmcs_utils/trait_types/either_type2.py | ||
| bmcs_utils/trait_types/enum_type.py | ||
| bmcs_utils/trait_types/instance_type.py | ||
| bmcs_utils/trait_types/list_type.py | ||
| bmcs_utils/trait_types/trait_types.py | ||
| bmcs_utils/trait_types/selector_type.py | ||
| bmcs_utils/trait_types/trait_types.py | ||
| bmcs_utils/trait_types/weakref_type.py |
| from bmcs_utils.version import __version__ | ||
| from bmcs_utils.version import __version__ | ||
| import collections | ||
| collections.Iterable = collections.abc.Iterable |
+12
-4
@@ -5,7 +5,11 @@ | ||
| from bmcs_utils.model_list import ModelList | ||
| from bmcs_utils.model_dict import ModelDict | ||
| from bmcs_utils.view import View | ||
| from bmcs_utils.item import Item | ||
| from bmcs_utils.mpl_utils import mpl_align_xaxis, mpl_align_yaxis, mpl_align_yaxis_to_zero, mpl_show_one_legend_for_twin_axes | ||
| from bmcs_utils.symb_expr import SymbExpr, InjectSymbExpr | ||
| from bmcs_utils.trait_types import Int, Float, Bool, Str, \ | ||
| from bmcs_utils.mpl_utils import \ | ||
| mpl_align_xaxis, mpl_align_yaxis, mpl_align_yaxis_to_zero, mpl_show_one_legend_for_twin_axes | ||
| from bmcs_utils.symb_expr import \ | ||
| SymbExpr, InjectSymbExpr | ||
| from bmcs_utils.trait_types import \ | ||
| Int, Float, Bool, Str, \ | ||
| Button, Range, Progress, Array, \ | ||
@@ -16,4 +20,8 @@ EitherType, Enum, Instance, List | ||
| ButtonEditor, ArrayEditor, InstanceEditor, EitherTypeEditor, ListEditor, \ | ||
| HistoryEditor, TextAreaEditor, IntRangeEditor | ||
| HistoryEditor, TextAreaEditor, IntRangeEditor, FloatSliderEditorSelector, FloatSliderEditor | ||
| from bmcs_utils.misc.plot_tools import plot_colors, set_latex_mpl_format | ||
| from bmcs_utils.parametric_study import ParametricStudy | ||
| from bmcs_utils.data_cache import data_cache | ||
| from bmcs_utils.k3d_utils.extrusion_for_3d_curve import Extruder | ||
| from bmcs_utils.k3d_utils.k3d_utils import K3DUtils | ||
+25
-16
@@ -64,6 +64,5 @@ ''' | ||
| self.plot_fig.layout = ipw.Layout(width="100%",height="100%") | ||
| with self.plot_widget: | ||
| self.plot_fig.display() | ||
| # with self.plot_widget: | ||
| # self.plot_fig.display() | ||
| self.plot_fig.outputs.append(self.plot_widget) | ||
| self.objects = {} | ||
@@ -90,3 +89,4 @@ | ||
| def show_fig(self): | ||
| pass | ||
| with self.plot_widget: | ||
| self.plot_fig.display() | ||
| def setup_plot(self, model): | ||
@@ -156,14 +156,14 @@ model.setup_plot(self) | ||
| model_tree = tr.Property() | ||
| model_tree = tr.Property(depends_on='model.graph_changed') | ||
| @tr.cached_property | ||
| def _get_model_tree(self): | ||
| tree = self.model.get_sub_node(self.model.name) | ||
| tree = self.model.get_tree_subnode(self.model.name) | ||
| return self.get_tree_entries(tree) | ||
| def get_tree_entries(self, node): | ||
| name, model, sub_nodes = node | ||
| bmcs_sub_nodes = [ | ||
| self.get_tree_entries(sub_node) for sub_node in sub_nodes | ||
| def get_tree_entries(self, tree): | ||
| name, model, subnodes = tree | ||
| bmcs_subnodes = [ | ||
| self.get_tree_entries(subnode) for subnode in subnodes | ||
| ] | ||
| node_ = BMCSNode(name, nodes=tuple(bmcs_sub_nodes), | ||
| node_ = BMCSNode(name, nodes=tuple(bmcs_subnodes), | ||
| controller=model.get_controller(self)) | ||
@@ -173,3 +173,4 @@ node_.observe(self.select_node, 'selected') | ||
| '''upon tree change - rebuild the subnodes''' | ||
| new_node = model.get_sub_node(model.name) | ||
| #new_node = model.get_tree_subnode(model.name) | ||
| new_node = model.get_tree_subnode(name) | ||
| new_node_ = self.get_tree_entries(new_node) | ||
@@ -179,3 +180,4 @@ node_.nodes = new_node_.nodes | ||
| # are the original observers deleted? | ||
| model.observe(update_node, 'tree_changed') | ||
| # this has been | ||
| model.observe(update_node, 'graph_changed') | ||
| return node_ | ||
@@ -242,7 +244,14 @@ | ||
| self.time_editor_pane.children = time_editor | ||
| self.controller.update_time_editor() | ||
| # trait = node.trait | ||
| # trait_type = trait.trait_type | ||
| # editor_factory = trait_type.editor_factory | ||
| # if editor_factory: | ||
| # editor = editor_factory() | ||
| # print('tt', trait_type) | ||
| # print('editor', editor) | ||
| model_editor = controller.model_editor | ||
| self.model_editor_pane.children = model_editor.children | ||
| # with print_output: | ||
| # print('select node: controller', controller) | ||
| # print('time_editor: time_editor', time_editor) | ||
| backend = controller.model.plot_backend | ||
@@ -249,0 +258,0 @@ self.set_plot_backend(backend) |
@@ -53,2 +53,3 @@ import traits.api as tr | ||
| ipw_editors = tr.Property | ||
| @tr.cached_property | ||
@@ -62,2 +63,3 @@ def _get_ipw_editors(self): | ||
| model_editor = tr.Property | ||
| @tr.cached_property | ||
@@ -70,6 +72,7 @@ def _get_model_editor(self): | ||
| box_layout = ipw.Layout(display='flex', | ||
| flex_flow='column', | ||
| align_items='stretch', | ||
| width='100%') | ||
| items_layout = ipw.Layout(width='auto') # override the default width of the button to 'auto' to let the button grow | ||
| flex_flow='column', | ||
| align_items='stretch', | ||
| width='100%') | ||
| items_layout = ipw.Layout( | ||
| width='auto') # override the default width of the button to 'auto' to let the button grow | ||
| for ipw_editor in ipw_editors_list: | ||
@@ -83,7 +86,7 @@ ipw_editor.layout = items_layout | ||
| time_editor = tr.Property | ||
| @tr.cached_property | ||
| def _get_time_editor(self): | ||
| ipw_view = self.model.ipw_view | ||
| return ipw_view.get_time_editor_widget(model=self.model, | ||
| controller=self) | ||
| return ipw_view.get_time_editor_widget(model=self.model, controller=self) | ||
@@ -96,3 +99,5 @@ def plot_k3d(self, k3d_plot): | ||
| def update_plot(self, axes): | ||
| self.model.update_plot(axes) | ||
| def update_time_editor(self): | ||
| ipw_view = self.model.ipw_view | ||
| ipw_view.update_time_editor_widget() | ||
| # self.model.update_plot(axes) |
@@ -33,3 +33,3 @@ from bmcs_utils.trait_types import \ | ||
| x = np.linspace(0,1,self.n) | ||
| axes.plot(x,np.zeros_like(n),marker='o') | ||
| axes.plot(x,np.zeros_like(x),marker='o') | ||
@@ -24,3 +24,3 @@ from bmcs_utils.trait_types import \ | ||
| time_editor=HistoryEditor(var='t', | ||
| max_var='t_max', | ||
| max_var='t_max', | ||
| ) | ||
@@ -31,4 +31,3 @@ ) | ||
| def update_plot(self, axes): | ||
| with bu.print_output: | ||
| print('SELF', self) | ||
| print('SELF', self) | ||
| x_range = np.linspace(0,self.t_max,100) | ||
@@ -52,3 +51,2 @@ y_range = x_range**self.exponent | ||
| def update_plot(self, axes): | ||
| with bu.print_output: | ||
| print('mhwh1') | ||
| print('mhwh1') |
@@ -19,3 +19,3 @@ from bmcs_utils.trait_types import \ | ||
| layout = tr.Instance(LayoutModel,()) | ||
| layout = Instance(LayoutModel,()) | ||
@@ -22,0 +22,0 @@ tree = ['layout'] |
| from .editors import IntEditor, FloatEditor, FloatRangeEditor, \ | ||
| BoolEditor, ButtonEditor, ArrayEditor, TextEditor, TextAreaEditor, IntRangeEditor | ||
| BoolEditor, ButtonEditor, ArrayEditor, TextEditor, TextAreaEditor, IntRangeEditor, FloatSliderEditorSelector, FloatSliderEditor | ||
| from .progress_editor import ProgressEditor | ||
@@ -9,2 +9,3 @@ from .history_editor import HistoryEditor | ||
| from .instance_editor import InstanceEditor | ||
| from .list_editor import ListEditor | ||
| from .list_editor import ListEditor | ||
| from .dict_editor import DictEditor |
@@ -15,8 +15,8 @@ import traits.api as tr | ||
| max = None | ||
| min_name = tr.Str | ||
| max_name = tr.Str | ||
| min_name = None | ||
| max_name = None | ||
| def render(self): | ||
| min = self.min | ||
| max = self.max | ||
| if min is not None or max is not None: | ||
| if min is not None or max is not None or self.min_name is not None or self.max_name is not None: | ||
| if min is None: | ||
@@ -51,8 +51,8 @@ min = -sys.float_info.max | ||
| max = None | ||
| min_name = tr.Str | ||
| max_name = tr.Str | ||
| min_name = None | ||
| max_name = None | ||
| def render(self): | ||
| min = self.min | ||
| max = self.max | ||
| if min is not None or max is not None: | ||
| if min is not None or max is not None or self.min_name is not None or self.max_name is not None: | ||
| if min is None: | ||
@@ -137,3 +137,7 @@ min = -sys.float_info.max | ||
| class FloatSliderEditor(EditorFactory): | ||
| class FloatSliderEditorSelector(EditorFactory): | ||
| """ Use this editor if you have a variable with very small values (<0.001). | ||
| There's a bug in FloatSlider for very small step, see https://github.com/jupyter-widgets/ipywidgets/issues/259 | ||
| it will be fixed in ipywidgets v8.0.0, but until then, the following fix will be used | ||
| with this implementation, entering the number manually in the readout will not work""" | ||
| low = tr.Float | ||
@@ -147,3 +151,2 @@ high = tr.Float | ||
| readout = tr.Bool(True) | ||
| readout_format = tr.Str | ||
@@ -160,8 +163,3 @@ def render(self): | ||
| round_value = self._get_round_value(self.low, self.high, self.n_steps) | ||
| if not self.readout_format: | ||
| self.readout_format = '.' + str(round_value) + 'f' | ||
| # There's a bug in FloatSlider for very small step, see https://github.com/jupyter-widgets/ipywidgets/issues/259 | ||
| # it will be fixed in ipywidgets v8.0.0, but until then, the following fix will be used | ||
| # with this implementation, entering the number manually in the readout will not work | ||
| values = np.linspace(self.low, self.high, int(self.n_steps)) | ||
@@ -184,15 +182,2 @@ values = np.round(values, round_value) | ||
| # return ipw.FloatSlider( | ||
| # value=self.value, | ||
| # min=self.low, | ||
| # max=self.high, | ||
| # step=step, | ||
| # tooltip=self.tooltip, | ||
| # continuous_update=self.continuous_update, | ||
| # description=self.label, | ||
| # disabled=self.disabled, | ||
| # readout=self.readout, | ||
| # readout_format=self.readout_format | ||
| # ) | ||
| def _find_nearest(self, array, value): | ||
@@ -219,5 +204,58 @@ array = np.asarray(array) | ||
| class FloatSliderEditor(EditorFactory): | ||
| low = tr.Float | ||
| high = tr.Float | ||
| low_name = tr.Str | ||
| high_name = tr.Str | ||
| n_steps = tr.Int(20) | ||
| n_steps_name = tr.Str | ||
| continuous_update = tr.Bool(False) | ||
| readout = tr.Bool(True) | ||
| readout_format = None # example: '.2f' for 2 decimals after comma | ||
| def render(self): | ||
| if self.low_name: | ||
| self.low = getattr(self.model, str(self.low_name)) | ||
| if self.high_name: | ||
| self.high = getattr(self.model, str(self.high_name)) | ||
| if self.n_steps_name: | ||
| self.n_steps = getattr(self.model, str(self.n_steps_name)) | ||
| step = (self.high - self.low) / self.n_steps | ||
| round_value = self._get_round_value(self.low, self.high, self.n_steps) | ||
| if self.readout_format is None: | ||
| self.readout_format = '.' + str(round_value) + 'f' | ||
| return ipw.FloatSlider( | ||
| value=self.value, | ||
| min=self.low, | ||
| max=self.high, | ||
| step=step, | ||
| tooltip=self.tooltip, | ||
| continuous_update=self.continuous_update, | ||
| description=self.label, | ||
| disabled=self.disabled, | ||
| readout=self.readout, | ||
| readout_format=self.readout_format, | ||
| style=style | ||
| ) | ||
| def _get_round_value(self, low, high, n_steps): | ||
| magnitude_n_steps = self._get_order_of_magnitude(n_steps) | ||
| magnitude_low = self._get_order_of_magnitude(low) | ||
| magnitude_high = self._get_order_of_magnitude(high) | ||
| min_magnitude = min(magnitude_low, magnitude_high) | ||
| if min_magnitude >= 0: | ||
| req_decimals = 2 | ||
| else: | ||
| req_decimals = abs(min_magnitude) + magnitude_n_steps | ||
| return req_decimals | ||
| def _get_order_of_magnitude(self, num): | ||
| sci_num = '{:.1e}'.format(num) | ||
| sci_num_suffix = sci_num.split('e')[1] | ||
| return int(sci_num_suffix) | ||
| # Backward compatibility (renaming) | ||
| FloatRangeEditor = FloatSliderEditor | ||
| FloatRangeEditor = FloatSliderEditorSelector # TODO change this to FloatSliderEditor when ipywidgets 8 is released | ||
@@ -224,0 +262,0 @@ |
@@ -16,3 +16,3 @@ | ||
| self.drop_down = ipw.Dropdown(description=self.label, value=key, | ||
| tooltip=self.tooltip, options=option_keys) | ||
| tooltip=self.tooltip, options=option_keys) | ||
| self.drop_down.observe(self._selection_changed_from_dropdown,'value') | ||
@@ -19,0 +19,0 @@ self.model.observe(self._selection_changed_from_model,self.name) |
@@ -7,4 +7,4 @@ | ||
| import time | ||
| from functools import reduce | ||
| from threading import Thread | ||
| import bmcs_utils.api as bu | ||
@@ -53,5 +53,11 @@ class HistoryEditor(EditorFactory): | ||
| submodel = tr.Property | ||
| def _get_submodel(self): | ||
| submodel_path = tuple(str(self.var).split('.')[:-1]) | ||
| return reduce(lambda obj, attr: getattr(obj, attr, None), submodel_path, self.model) | ||
| def render(self): | ||
| history_bar_widgets = [] | ||
| eta = (getattr(self.model, str(self.var)) - self.t_min) / (self.t_max - self.t_min) | ||
| var = str(self.var).split('.')[-1] | ||
| eta = (getattr(self.submodel, var) - self.t_min) / (self.t_max - self.t_min) | ||
| self.history_slider = ipw.FloatSlider( | ||
@@ -73,6 +79,5 @@ value=eta, | ||
| eta = event['new'] | ||
| # with bu.print_output: | ||
| # print('slider on',self.model) | ||
| t = self.t_min + (self.t_max - self.t_min) * eta | ||
| setattr(self.model, self.var, t) | ||
| var = str(self.var).split('.')[-1] | ||
| setattr(self.submodel, var, t) | ||
| app_window = self.controller.app_window | ||
@@ -101,1 +106,5 @@ app_window.update_plot(self.model) | ||
| def update_from_model(self): | ||
| var = str(self.var).split('.')[-1] | ||
| eta = (getattr(self.submodel, var) - self.t_min) / (self.t_max - self.t_min) | ||
| self.history_slider.value = eta |
@@ -162,1 +162,3 @@ | ||
| def update_from_model(self): | ||
| print('Warning: Update progress bar from model - not implemented yet') |
@@ -30,4 +30,4 @@ | ||
| def get_submodels(self): | ||
| def get_tree_submodels(self): | ||
| return self.items | ||
+9
-58
@@ -7,5 +7,7 @@ | ||
| from .controller import Controller | ||
| from .model_notify_mixin import ModelNotifyMixin | ||
| from .model_tree_node_mixin import ModelTreeNodeMixin | ||
| @tr.provides(IModel) | ||
| class Model(tr.HasTraits): | ||
| class Model(ModelNotifyMixin, ModelTreeNodeMixin): | ||
| """Base class for interactive bmcs_utils | ||
@@ -16,4 +18,2 @@ """ | ||
| tree = [] | ||
| ipw_view = View() | ||
@@ -23,3 +23,2 @@ | ||
| super().__init__(*args, **kw) | ||
| self.update_observers() | ||
@@ -47,61 +46,13 @@ def get_controller(self, app_window): | ||
| def new_app_window(self, **kw): | ||
| return AppWindow(self, **kw) | ||
| def interact(self,**kw): | ||
| return AppWindow(self,**kw).interact() | ||
| app_window = self.new_app_window() | ||
| return app_window.interact() | ||
| def app(self,**kw): | ||
| return AppWindow(self,**kw).interact() | ||
| return self.interact(**kw) | ||
| def get_tree_items(self): | ||
| return self.tree | ||
| def get_submodels(self): | ||
| sub_models = [] | ||
| for key in self.get_tree_items(): | ||
| trait = self.trait(key) | ||
| if trait == None: | ||
| raise ValueError('trait %s not found in %s' % (key, self)) | ||
| if trait.is_mapped: | ||
| sub_models.append(getattr(self, key + '_')) | ||
| else: | ||
| sub_models.append(getattr(self, key)) | ||
| return sub_models | ||
| def get_sub_node(self, name): | ||
| # Construct a tree structure of instances tagged by `tree` | ||
| sub_models = self.get_submodels() | ||
| sub_nodes = [ | ||
| sub_model.get_sub_node(node_name) | ||
| for node_name, sub_model in zip(self.tree, sub_models) | ||
| ] | ||
| return (name, self, sub_nodes) | ||
| as_node = get_sub_node | ||
| def _notify_tree_change(self, event): | ||
| self.tree_changed = True | ||
| # self.update_observers() | ||
| tree_changed = tr.Event | ||
| """Event signaling the tree widgets to rebuild""" | ||
| @tr.observe('+TIME,+MESH,+MAT,+CS,+BC,+ALG,+FE,+DSC,+GEO,+ITR') | ||
| def notify_state_change(self, event): | ||
| if self.state_change_debug: | ||
| print('state_changed', self, event) | ||
| self.state_changed = True | ||
| state_change_debug = tr.Bool(False) | ||
| state_changed = tr.Event | ||
| """Event used in model implementation to notify the need for update""" | ||
| def update_observers(self): | ||
| name, model, nodes = self.as_node('root') | ||
| for sub_name, sub_model, sub_nodes in nodes: | ||
| # model.observe(self._notify_tree_change,'state_changed') | ||
| # model.observe(self._notify_tree_change,sub_name) | ||
| sub_model.observe(model.notify_state_change,'state_changed') | ||
| # backward compatibility | ||
| InteractiveModel = Model |
@@ -45,3 +45,4 @@ ''' | ||
| def traits_init(self): | ||
| def __init__(self, *args, **kw): | ||
| super().__init__(*args, **kw) | ||
| # gather the symbols and construct an ordered tuple | ||
@@ -90,4 +91,8 @@ default_symbols = tuple([getattr(self, sym_name) for sym_name in self.symb_variables]) | ||
| def traits_init(self): | ||
| def __init__(self, *args, **kw): | ||
| super().__init__(*args, **kw) | ||
| self.symb = self.symb_class(model = self) | ||
| # def traits_init(self): | ||
| # self.symb = self.symb_class(model = self) | ||
| # |
| from .trait_types import * | ||
| from .either_type import EitherType | ||
| from .either_type2 import EitherType2 as EitherType | ||
| from .enum_type import Enum | ||
| from .instance_type import Instance | ||
| from .list_type import List | ||
| from .weakref_type import WeakRef | ||
| from .list_type import List | ||
| from .dict_type import Dict | ||
| from .selector_type import Selector |
@@ -15,8 +15,7 @@ #------------------------------------------------------------------------- | ||
| from .trait_types import TraitBase | ||
| from traits.api import TraitType, TraitError | ||
| from bmcs_utils.editors import EitherTypeEditor | ||
| import traits.api as tr | ||
| class EitherType(TraitType): | ||
| class EitherType(TraitBase, TraitType): | ||
| """Polymorphic instance - can accommodate the values | ||
@@ -75,1 +74,6 @@ of specified classes. Instances of the classes are created upon key assignment | ||
| # def get(self, object, name): | ||
| # return getattr(object, name + '_') | ||
| # | ||
| # def set(self, object, name, value): | ||
| # return setattr(object, name + '_', value) |
@@ -16,7 +16,6 @@ #------------------------------------------------------------------------- | ||
| from traits.api import TraitType, TraitError | ||
| from .trait_types import TraitBase | ||
| from bmcs_utils.editors import EnumTypeEditor | ||
| import traits.api as tr | ||
| class Enum(TraitType): | ||
| class Enum(TraitBase, TraitType): | ||
| """Polymorphic instance - can accommodate the values | ||
@@ -23,0 +22,0 @@ of specified classes. Instances of the classes are created upon key assignement |
@@ -10,2 +10,29 @@ | ||
| def get_sub_nodes(self): | ||
| return [] | ||
| return [] | ||
| def set(self, obj, name, value): | ||
| self.pre_setattr(obj, name) | ||
| self.set_value(obj, name, value) | ||
| self.post_setattr(obj, name, value) | ||
| def pre_setattr(self, object, name): | ||
| if name in object.depends_on: | ||
| old_value = getattr(object, name + '_', None) | ||
| if old_value: | ||
| old_value.parents.remove(object) | ||
| def init_setattr(self, object, name, value): | ||
| value.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % value) | ||
| def post_setattr(self, object, name, value): | ||
| if value and name in object.depends_on: | ||
| value.parents.add(object) | ||
| object.notify_graph_change('Notification from child %s' % value) | ||
| def get(self, obj, name): | ||
| val = self.get_value(obj, name) | ||
| if val is None: | ||
| val = self.default_value | ||
| return val | ||
@@ -8,2 +8,9 @@ import traits.api as tr | ||
| class TraitBase: | ||
| def get_name_(self, name): | ||
| return name | ||
| def post_init(self, object, name, value): | ||
| return | ||
| def get_sub_nodes(self): | ||
@@ -10,0 +17,0 @@ """Let the trait extract the subnodes from its model |
@@ -7,3 +7,4 @@ | ||
| class BMCSNode(ipt.Node): | ||
| trait = tl.Any | ||
| controller = tl.Any | ||
@@ -1,1 +0,1 @@ | ||
| __version__='0.0.31a' | ||
| __version__='0.0.32a' |
@@ -79,1 +79,5 @@ | ||
| def update_time_editor_widget(self): | ||
| if self.time_editor == None: | ||
| return | ||
| self.time_editor.update_from_model() |
+15
-16
| Metadata-Version: 2.1 | ||
| Name: bmcs_utils | ||
| Version: 0.0.31a0 | ||
| Version: 0.0.32a0 | ||
| Summary: Suite of utilities for to implementation of bmcs_utils for brittle-matrix composites. | ||
@@ -9,17 +9,2 @@ Home-page: https://github.com/bmcs-group/bmcs_utils | ||
| License: MIT | ||
| Description: | ||
| # General utilities and practices | ||
| This repository contains development patterns | ||
| applied for the bmcs packges. They distinguish | ||
| three development phases: | ||
| * Formulation of model components using using jupyter notebooks with Python package | ||
| * Implementation of Python packages | ||
| * Model presentation using examples via Jupyter Notebooks | ||
| The detailed documentation of the package is provided | ||
| [here](docs/README.md) | ||
| Platform: UNKNOWN | ||
| Classifier: License :: OSI Approved :: MIT License | ||
@@ -33,1 +18,15 @@ Classifier: Programming Language :: Python | ||
| Description-Content-Type: text/markdown | ||
| # General utilities and practices | ||
| This repository contains development patterns | ||
| applied for the bmcs packges. They distinguish | ||
| three development phases: | ||
| * Formulation of model components using using jupyter notebooks with Python package | ||
| * Implementation of Python packages | ||
| * Model presentation using examples via Jupyter Notebooks | ||
| The detailed documentation of the package is provided | ||
| [here](docs/README.md) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
120307
34.76%62
26.53%2872
33.46%