You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

bmcs-utils

Package Overview
Dependencies
Maintainers
2
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bmcs-utils - pypi Package Compare versions

Comparing version
0.0.31a0
to
0.0.32a0
+38
bmcs_utils/editors/dict_editor.py
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)
+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)

@@ -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

@@ -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

@@ -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

@@ -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)