import os
import json
import datetime
import warnings
from collections import defaultdict, OrderedDict
'''length scale conversion to meter'''
LENGTH_SCALE_CONVERSION = {
'millimeter': 1e-3,
'meter': 1.0,
}
#############################################
def parse_point(json_point, scale=1.0):
return [scale*json_point[0], scale*json_point[1], scale*json_point[2]]
def parse_nodes(node_data, scale=1.0):
nodes = []
for n in node_data:
n['point'] = parse_point(n['point'], scale=scale)
nodes.append(Node.from_data(n))
return nodes
def extract_model_name_from_path(file_path):
forename = file_path.split('.json')[0]
return forename.split(os.sep)[-1]
#############################################
[docs]class Model(object):
[docs] def __init__(self, nodes, elements, supports, joints, materials, crosssecs, unit='meter', model_name=None):
self.model_name = model_name
self.generate_time = str(datetime.datetime.now())
assert unit in LENGTH_SCALE_CONVERSION
self.unit = unit
scale = LENGTH_SCALE_CONVERSION[unit]
if scale != 1.0:
warnings.warn('Model unit {}, scaled by {} and converted to Meter unit.'.format(unit, scale))
for node in nodes:
node.point = [scale*cp for cp in node.point]
self.nodes = nodes
for i, n in enumerate(self.nodes):
assert i == n.node_ind
self.elements = elements
for i, e in enumerate(self.elements):
assert i == e.elem_ind
# turn lists into dicts
self.supports = {}
for support in supports:
self.supports[support.node_ind] = support
self.joints = joints or []
joint_id_from_etag = defaultdict(set)
if joints is not None:
for i, joint in enumerate(joints):
for e_tag in joint.elem_tags:
joint_id_from_etag[e_tag].add(i)
for etag, jt_ids in joint_id_from_etag.items():
if len(jt_ids) > 1:
warnings.warn('{} joints (#{}) assigned to the same element tag {}. Assume using the first one!'.format(len(jt_ids), jt_ids, etag))
self.joint_id_from_etag = {e_tag : list(ids)[0] for e_tag, ids in joint_id_from_etag.items()}
assert len(materials) > 0
self.materials = materials
material_id_from_etag = defaultdict(set)
for i, mat in enumerate(materials):
for e_tag in mat.elem_tags:
material_id_from_etag[e_tag].add(i)
for etag, m_ids in material_id_from_etag.items():
if len(m_ids) > 1:
warnings.warn('{} materials (#{}) assigned to the same element tag {}. Assume using the first one!'.format(len(m_ids), m_ids, etag))
self.material_id_from_etag = {e_tag : list(ids)[0] for e_tag, ids in material_id_from_etag.items()}
assert len(crosssecs) > 0
self.crosssecs = crosssecs
crosssec_id_from_etag = defaultdict(set)
for i, cs in enumerate(crosssecs):
for e_tag in cs.elem_tags:
crosssec_id_from_etag[e_tag].add(i)
for etag, cs_ids in crosssec_id_from_etag.items():
if len(cs_ids) > 1:
warnings.warn('{} cross secs (#{}) assigned to the same element tag {}. Assume using the first one!'.format(len(cs_ids), cs_ids, etag))
self.crosssec_id_from_etag = {e_tag : list(ids)[0] for e_tag, ids in crosssec_id_from_etag.items()}
@property
def node_num(self):
return len(self.nodes)
@property
def element_num(self):
return len(self.elements)
@classmethod
def from_json(cls, file_path, verbose=False):
assert os.path.exists(file_path) and "json file path does not exist!"
with open(file_path, 'r') as f:
json_data = json.loads(f.read())
if 'model_name' not in json_data:
json_data['model_name'] = extract_model_name_from_path(file_path)
return cls.from_data(json_data, verbose)
@classmethod
def from_data(cls, data, verbose=False):
model_name = data['model_name']
unit = data['unit']
# length scale for vertex positions
scale = LENGTH_SCALE_CONVERSION[unit]
unit = 'meter'
nodes = parse_nodes(data['nodes'], scale=scale)
elements = [Element.from_data(e) for e in data['elements']]
element_inds_from_tag = defaultdict(list)
for e in elements:
element_inds_from_tag[e.elem_tag].append(e.elem_ind)
supports = [Support.from_data(s) for s in data['supports']]
joints = [Joint.from_data(j) for j in data['joints']]
materials = [Material.from_data(m) for m in data['materials']]
crosssecs = [CrossSec.from_data(c) for c in data['cross_secs']]
# sanity checks
if 'node_num' in data:
assert len(nodes) == data['node_num']
if 'element_num' in data:
assert len(elements) == data['element_num']
node_id_range = list(range(len(nodes)))
for e in elements:
assert e.end_node_inds[0] in node_id_range and \
e.end_node_inds[1] in node_id_range, \
'element end point id not in node_list id range!'
num_grounded_nodes = 0
for n in nodes:
if n.is_grounded:
num_grounded_nodes += 1
# assert grounded_nodes > 0, 'The structure must have at lease one grounded node!'
if num_grounded_nodes == 0:
warnings.warn('The structure must have at lease one grounded node!')
if verbose:
print('Model: {} | Original unit: {} | Generated time: {}'.format(model_name, data['unit'],
data['generate_time'] if 'generate_time' in data else ''))
print('Nodes: {} | Elements: {} | Supports: {} | Joints: {} | Materials: {} | Cross Secs: {} | Tag Ground: {} '.format(
len(nodes), len(elements), len(supports), len(joints), len(materials), len(crosssecs), num_grounded_nodes))
return cls(nodes, elements, supports, joints, materials, crosssecs, model_name=model_name)
def to_data(self):
data = OrderedDict()
data['model_name'] = self.model_name
data['unit'] = self.unit
data['generate_time'] = self.generate_time
data['node_num'] = len(self.nodes)
data['element_num'] = len(self.elements)
data['nodes'] = [n.to_data() for n in self.nodes]
data['elements'] = [e.to_data() for e in self.elements]
data['supports'] = [s.to_data() for s in self.supports.values()]
data['joints'] = [j.to_data() for j in self.joints]
data['materials'] = [m.to_data() for m in self.materials]
data['cross_secs'] = [cs.to_data() for cs in self.crosssecs]
return data
[docs]class LoadCase(object):
[docs] def __init__(self, point_loads=None, uniform_element_loads=None, gravity_load=None):
self.point_loads = point_loads or []
self.uniform_element_loads = uniform_element_loads or []
self.gravity_load = gravity_load
@classmethod
def from_json(cls, file_path, lc_ind=0):
assert os.path.exists(file_path) and "json file path does not exist!"
with open(file_path, 'r') as f:
json_data = json.loads(f.read())
json_data = json_data['loadcases'] if 'loadcases' in json_data else json_data
return cls.from_data(json_data[str(lc_ind)])
@classmethod
def from_data(cls, data):
point_loads = [PointLoad.from_data(pl) for pl in data['ploads']]
uniform_element_loads = [UniformlyDistLoad.from_data(el) for el in data['eloads']]
gravity_load = None if 'gravity' not in data else data['gravity']
return cls(point_loads, uniform_element_loads, gravity_load)
def to_data(self):
data = {'ploads' : [pl.to_data() for pl in self.point_loads],
'eloads' : [el.to_data() for el in self.uniform_element_loads],
'gravity' : self.gravity_load.to_data() if self.gravity_load is not None else None
}
return data
def __repr__(self):
return '{}(#pl:{},#el:{},gravity:{})'.format(self.__class__.__name__, len(self.point_loads),
len(self.uniform_element_loads), self.gravity_load)
##############################################
class Node(object):
def __init__(self, point, node_ind, is_grounded):
self.point = point
self.node_ind = node_ind
self.is_grounded = is_grounded
@classmethod
def from_data(cls, data):
return cls(data['point'], data['node_ind'], data['is_grounded'])
def to_data(self):
data = {'point' : list(self.point), 'node_ind' : self.node_ind, 'is_grounded' : self.is_grounded}
return data
def __repr__(self):
return '{}(#{},{},Grd:{})'.format(self.__class__.__name__, self.node_ind, self.point, self.is_grounded)
class Element(object):
def __init__(self, end_node_inds, elem_ind, elem_tag='', bending_stiff=True):
assert end_node_inds[0] != end_node_inds[1], 'zero length element not allowed!'
self.end_node_inds = end_node_inds
self.elem_tag = elem_tag
self.elem_ind = elem_ind
self.bending_stiff = bending_stiff
@classmethod
def from_data(cls, data):
return cls(data['end_node_inds'], data['elem_ind'], data['elem_tag'], data['bending_stiff'])
def to_data(self):
data = {'end_node_inds' : self.end_node_inds,
'elem_ind' : self.elem_ind,
'elem_tag' : self.elem_tag,
'bending_stiff' : self.bending_stiff,
}
return data
def __repr__(self):
return '{}(#{}({}),{},Bend:{})'.format(self.__class__.__name__, self.elem_ind, self.elem_tag, self.end_node_inds, self.bending_stiff)
class Support(object):
def __init__(self, condition, node_ind):
self.condition = condition
self.node_ind = node_ind
@classmethod
def from_data(cls, data):
return cls(data['condition'], data['node_ind'])
def to_data(self):
return {
'condition' : self.condition,
'node_ind' : self.node_ind,
}
def __repr__(self):
return '{}(#{},{})'.format(self.__class__.__name__, self.node_ind, self.condition)
# TODO: assumed to be converted to kN/m (translational dof) and kNm/rad (rotational dof)
class Joint(object):
def __init__(self, c_conditions, elem_tags):
assert len(c_conditions) == 12
self.c_conditions = c_conditions
self.elem_tags = elem_tags
@classmethod
def from_data(cls, data):
return cls(data['c_conditions'], data['elem_tags'])
def to_data(self):
return {
'c_conditions' : self.c_conditions,
'elem_tags' : self.elem_tags,
}
# TODO: assumed to be converted to meter-based unit
class CrossSec(object):
def __init__(self, A, Jx, Iy, Iz, elem_tags=None, family='unnamed', name='unnamed'):
self.A = A
self.Jx = Jx
self.Iy = Iy
self.Iz = Iz
self.elem_tags = elem_tags if elem_tags else [None]
self.family = family
self.name = name
@classmethod
def from_data(cls, data):
return cls(data['A'], data['Jx'], data['Iy'], data['Iz'], data['elem_tags'], data['family'], data['name'])
def to_data(self):
return {
'A' : self.A,
'Jx' : self.Jx,
'Iy' : self.Iy,
'Iz' : self.Iz,
'elem_tags' : self.elem_tags,
'family' : self.family,
'name' : self.name,
}
def __repr__(self):
return '{}(family:{} name:{} area:{}[m2] Jx:{}[m4] Iy:{}[m4] Iz:{}[m4] applies to elements:{})'.format(
self.__class__.__name__, self.family, self.name, self.A, self.Jx, self.Iy, self.Iz, self.elem_tags)
def mu2G(mu, E):
"""compute shear modulus from poisson ratio mu and Youngs modulus E
"""
return E/(2*(1+mu))
def G2mu(G, E):
"""compute poisson ratio from shear modulus and Youngs modulus E
"""
return E/(2*G)-1
# TODO: assumed to be converted to kN/m2, kN/m3
class Material(object):
def __init__(self, E, G12, fy, density, elem_tags=None, family='unnamed', name='unnamed', type_name='ISO', G3=None):
self.E = E
# in-plane shear modulus
self.G12 = G12
# transverse shear modulus
self.G3 = G3 or G12
self.mu = G2mu(G12, E)
# material strength in the specified direction (local x direction)
self.fy = fy
self.density = density
self.elem_tags = elem_tags if elem_tags else [None]
self.family = family
self.name = name
self.type_name = type_name
@classmethod
def from_data(cls, data):
return cls(data['E'], data['G12'], data['fy'], data['density'], data['elem_tags'], data['family'], data['name'], data['type_name'], data.get('G3', None))
def to_data(self):
return {
'E' : self.E,
'G12' : self.G12,
'G3' : self.G3,
'mu' : self.mu,
'fy' : self.fy,
'density' : self.density,
'elem_tags' : self.elem_tags,
'family' : self.family,
'name' : self.name,
'type_name' : self.type_name,
}
def __repr__(self):
# G3:8076[kN/cm2]
return '{}(|{}| E:{}[kN/m2] mu:{} G12:{}[kN/m2] G3:{}[kN/m2] density:{}[kN/m3] fy:{}[kN/m2] applies to elements:{})'.format(
self.__class__.__name__, self.family+'-'+self.name, self.E, self.mu, self.G12, self.G3, self.density, self.fy, self.elem_tags)
class PointLoad(object):
def __init__(self, force, moment, node_ind, loadcase=0):
self.force = force
self.moment = moment
self.node_ind = node_ind
self.loadcase = loadcase
@classmethod
def from_data(cls, data):
lc_ind = 0 if 'loadcase' not in data else data['loadcase']
return cls(data['force'], data['moment'], data['node_ind'], lc_ind)
def to_data(self):
return {
'force' : self.force,
'moment' : self.moment,
'node_ind' : self.node_ind,
'loadcase' : self.loadcase,
}
def __repr__(self):
return '{}(node_ind {} | force {} | moment {} | lc#{})'.format(self.__class__.__name__, self.node_ind, self.force, self.moment, self.loadcase)
class UniformlyDistLoad(object):
def __init__(self, q, load, elem_tags, loadcase=0):
self.q = q
# not sure if load is used in Karamba, we only use q here
self.load = load
self.elem_tags = elem_tags
self.loadcase = loadcase
@classmethod
def from_data(cls, data):
lc_ind = 0 if 'loadcase' not in data else data['loadcase']
return cls(data['q'], data['load'], data['elem_tags'], lc_ind)
def to_data(self):
return {
'q' : self.q,
'load' : self.load,
'elem_tags' : self.elem_tags,
'loadcase' : self.loadcase,
}
def __repr__(self):
return '{}(element_tags {} | q {} | load {} | lc#{})'.format(self.__class__.__name__,
self.elem_tags, self.q, self.load, self.loadcase)
class GravityLoad(object):
def __init__(self, force=[0,0,-1], loadcase=0):
self.force = force
self.loadcase = loadcase
@classmethod
def from_data(cls, data):
lc_ind = 0 if 'loadcase' not in data else data['loadcase']
return cls(data['force'], lc_ind)
def to_data(self):
return {
'force' : self.force,
'loadcase' : self.loadcase,
}
def __repr__(self):
return '{}({}, lc#{})'.format(self.__class__.__name__, self.force, self.loadcase)
##########################################
class AnalysisResult(object):
def __init__(self):
pass