Tutorial
1. Install bpy Package
In most of the time, the Blender-Python API is used just inside of the Blender, if you use other text editor like VSCode or IDE like PyCharm, install the bpy package will help you to get code hint.
pip install fake-bpy-module-2.80
2. Blender Import Tutorial
Preparation
Firstly, link all the python files you need in the blender text editor.
Implementation
For example, if you want to use the codes inside of the member.py in component.py, you need to add these codes at the beginning of the component.py.
import bpy
# import the member.py as a module
mb = bpy.data.texts["member.py"].as_module()
# Then you can use the classes or functions inside of the member.py in the target python file
rectangle_cfg = {
'name': rectangle,
'shape': {
'Flange length': 2,
'Web length': 1,
}
}
rectangle = mb.Rectangle(rectangle_cfg)
3. Run Python script from the command line
The following demonstration is done on Windows.
Firstly, go to the directory where Blender is installed.
cd c:\<blender installation directory> # Replace the content in <>
Use blender [args ...] [file] [args ...]
to use Blender in command line.
For Python, to run the Python script file. Use -P
, --python
<filepath>
blender -P G:\srpp\Synthetic-Structural-Benchmark\BridgeGenerator\test_chj.py
4. Random Bridge Generator
4.1. Set CFG Dictionary
CFG dictionary consists of the basis parameters for future construction. It has two keys: “name” (the model that these parameters are applied to), and “shape” (all the parameters’ name together with their value). For example, the CFG dictionary for deck is called setDeckBasic
, and it is constructed as
def setDeckBasic(w_deck, t_deck, h_deck):
cfg = {
"name": "deck_basic",
"shape": {
'deck width': w_deck,
'deck thickness': t_deck,
'deck top surface height': h_deck
}
}
return cfg
3.2. Member Class
Meber class consists of three main functions. setMember()
and setMember3d()
are used to transfer the CFG variable into Blender vertices list and faces list. createObj()
is used to transfer Blender vertices list and faces list into Blender object.
For argument of this class | arg name | meaning | | :——: | :——:| |cfg| CFG dictionary| |n| length of the object (x direction)| |t| translation of the cross-section| |quat| rotation in quaternion of the cross-sections|
For Member variables | variable name | meaning | | :——: | :——:| |v| Blender vertices| |f| Blender faces| |npts| number of points in one cross-section| |obj| Blender object|
3.2.1 setMember
@rwh
3.2.2 setMember3D
The main idea of this method is construct a Blender object using only two faces: start and end face. The advantage of this method is that it can not only input 2D coordinate but also 3D coodinate, and the shape of face can change as long as the number of points in faces keep the same. The disadvantage of this method is that it can’t construct rotation and transformation.
The code is as follow:
def setMember3d(self):
self.v = []
self.f = []
if self.three_d == False:
start = np.zeros([self.yz.shape[0], 3])
start[:, 0] = -self.n/2
start[:, 1:] = self.yz[:, :]
end = np.zeros([self.yz_end.shape[0], 3])
end[:, 0] = self.n/2
end[:, 1:] = self.yz_end[:, :]
else:
start = self.yz
end = self.yz_end
for i1 in range(start.shape[0]):
self.v.append(start[i1, :])
for i2 in range(end.shape[0]):
self.v.append(end[i2, :])
npts = start.shape[0]
for j in range(npts):
self.f = self.f + [(j, np.mod(j+1,npts),np.mod(j+1,npts) + npts, j + npts) for k in range(npts)]
f1 = ()
f2 = ()
for i in range(npts):
f1 += (i,)
f2 += (npts + i,)
self.f.append(f1)
self.f.append(f2)
For function variable
| variable name | meaning |
| :——: | :——:|
|yz
|coordinate of start face |
|yz_end
| coodinate of end face|
|three_d
| whether face coordinate is 2D or 3D (True: 3D)|
First change the input coordinate into 3D. If input coordinate is 2D, add x coordinate according to object length n
. The coordinate of start face is transformed into start
, end face is transformed into end
. Code is following:
if self.three_d == False:
start = np.zeros([self.yz.shape[0], 3])
start[:, 0] = -self.n/2
start[:, 1:] = self.yz[:, :]
end = np.zeros([self.yz_end.shape[0], 3])
end[:, 0] = self.n/2
end[:, 1:] = self.yz_end[:, :]
else:
start = self.yz
end = self.yz_end
Then, append all the points to v
(Blender vertices):
for i1 in range(start.shape[0]):
self.v.append(start[i1, :])
for i2 in range(end.shape[0]):
self.v.append(end[i2, :])
Next, connect points of start and end to construct new faces:
npts = start.shape[0]
for j in range(npts):
self.f = self.f + [(j, np.mod(j+1,npts),np.mod(j+1,npts) + npts, j + npts) for k in range(npts)]
Finally, cover the start and end faces:
f1 = ()
f2 = ()
for i in range(npts):
f1 += (i,)
f2 += (npts + i,)
self.f.append(f1)
self.f.append(f2)
2.3 createObj
This part of code is used to create Blender object. The code is as follow:
def createObj(self, name, obj_num=1):
vertices = self.v
edges = []
faces = self.f
new_mesh = bpy.data.meshes.new("new_mesh")
new_mesh.from_pydata(vertices, edges, faces)
new_mesh.update()
obj = bpy.data.objects.new(name, new_mesh)
view_layer = bpy.context.view_layer
view_layer.active_layer_collection.collection.objects.link(obj)
self.obj = obj
3. Construct Cross-Section Coordinate
All these classes based on Member
class create all the cross-section coordinate according to input CFG dictionary. Take ConcreteSolid
class as an example:
class ConcreteSolid(Member):
def __init__(self, cfg, cfg_end, n, three_d = False, t=None, quat=None):
super().__init__(cfg, n, t, quat)
self.three_d = three_d
self.cfg_end = cfg_end
self.shape_end = self.cfg_end['shape']
w_deck = self.shape['deck width']
t_deck = self.shape['deck thickness']
h_deck_t = self.shape['deck top surface height']
h_deck = h_deck_t - t_deck
m = random.uniform(0, 1)
self.yz = np.array([
[(w_deck/2 - m), (h_deck)],
[(w_deck/2), (h_deck + t_deck/2)],
[(w_deck/2 - m), (h_deck + t_deck)],
[-(w_deck/2 - m), (h_deck + t_deck)],
[-(w_deck/2), (h_deck + t_deck/2)],
[-(w_deck/2 - m), (h_deck)]
])
self.yz_end = self.yz
self.setMember3d()
For class arguments: | arg name | meaning | | :——: | :——:| |cfg| CFG dictionary of start face| |cfg_end| CFG dictionary of end face| |n| length of the object (x direction)| | three_d | whether face coordinate is 2D or 3D (True: 3D) | |t| translation of the cross-section| |quat| rotation in quaternion of the cross-sections|
First, it reads CFG dictionary and create the coordinate. If three_d
is True, construct 3D coordinate; if three_d
is False, construct 2D coordinate.
self.three_d = three_d
self.cfg_end = cfg_end
self.shape_end = self.cfg_end['shape']
w_deck = self.shape['deck width']
t_deck = self.shape['deck thickness']
h_deck_t = self.shape['deck top surface height']
h_deck = h_deck_t - t_deck
m = random.uniform(0, 1)
self.yz = np.array([
[(w_deck/2 - m), (h_deck)],
[(w_deck/2), (h_deck + t_deck/2)],
[(w_deck/2 - m), (h_deck + t_deck)],
[-(w_deck/2 - m), (h_deck + t_deck)],
[-(w_deck/2), (h_deck + t_deck/2)],
[-(w_deck/2 - m), (h_deck)]
])
self.yz_end = self.yz
m
here is used to create a little randomization.
Next, create v
and f
using setMember3d()
.
Here is also an example of 3D coordinate.
class Triangle2(Member):
def __init__(self, cfg, cfg_end, n, three_d=True, t=None, quat=None):
super().__init__(cfg, n, t, quat)
self.cfg_end = cfg_end
self.three_d = three_d
self.shape_end = self.cfg_end['shape']
a1 = self.shape['x1']
a2 = self.shape['x2']
a3 = self.shape['x3']
b1 = self.shape['z1']
b2 = self.shape['z2']
b3 = self.shape['z3']
c_start = self.shape['y']
self.yz = np.array([
[a1, c_start, b1],
[a2, c_start, b2],
[a3, c_start, b3]
])
a1 = self.shape_end['x1']
a2 = self.shape_end['x2']
a3 = self.shape_end['x3']
b1 = self.shape_end['z1']
b2 = self.shape_end['z2']
b3 = self.shape_end['z3']
c_end = self.shape_end['y']
self.yz_end = np.array([
[a1, c_end, b1],
[a2, c_end, b2],
[a3, c_end, b3]
])
self.setMember3d()
4. Construct Blender Object
Blender objects are constructed according to different classifications: SuperStructure
, SubStructure
, Deck
, Bearing
. Each classification is one parent class, and specific structure is children class. Take cable-stayed bridge as an example, Cable
is the children class of SuperStructure
, Column
is the children class of SuperStructure
, Slab
is the children class of Deck
, CableBase
and CableTop
is the children class of Bearing
. Each parent class return the Blender object constructed by its children class. For example, SuperStructure
return column
, which is Blender column object.
First, it is necessary to introduce one auxiliary function, Hollow()
. The code is as follow:
def Hollow(big_obj, small_obj):
hollow = big_obj.modifiers.new("MyModifier", "BOOLEAN")
hollow.object = small_obj
# blender need first activate the object, then apply modifier
bpy.context.view_layer.objects.active = big_obj
bpy.ops.object.modifier_apply(modifier="MyModifier")
# blender need first choose the object, then delete
small_obj.select_set(True)
bpy.ops.object.delete()
The arguments for this function is big_obj and small_obj, which are the origin Blender object and the object used to hollow the origin object. This function creates a hollowed origin Blender object.
4.1 SuperStructure
4.1.1 Cable
This class create the cable object by connecting the start circle face and end circle face. Class arguments are as follow:
arg name | meaning |
---|---|
cable_start | coordinate of circle center for start face |
cable_end | coordinate of circle center for end face |
name | the name of the object |
cable_radius | radius of cable |
num | number of points used to approximate the circle |
4.2 SubStructure
4.2.1 Column
This class create different shape of column. Current column shape include A1
, double
, door
, tower
, each column shape is a class function. Class arguments are as follow:
arg name | meaning |
---|---|
w_column | width of column |
h_column | height of column |
t_column | thickness of column (in yz plane) |
l_column | length of column (in x direction) |
h_deck_t | height of deck top surface |
t_deck | thickness of deck |
name | name of Blender object |
Column
class has one variable: cable_function
. It is used to locate the cable contact point on the column and help to calculate the proper width of deck. This variable is a list that consists of six elements: [z_start
, z_end
, k
, b
, b_in
, y_cable
]. The contact point on the column can be connected as a strict line in yz plane. z_start
is the z coordinate of the start position for the cable contact position; z_end
is the z coordinate for the end position for the cable contact position; k
and b
is the slope and intercept for this cable contact straight line (only consider positive k
). The position that the side of deck in contact with column also has a track of straight line, and it is parallel to cable contact straight line in current situation. k
and b_in
is the slope and intercept for this straight line track. However, cable contact straight line is parallel to z axis, k
no longer exist, so k
, b
are directly set as 0, and the function of cable contact straight line is y = y_cable
.
This class use Hollow
function a lot. For example, A1
column is constructed by original A1
object hollowed by a triangle object.
4.3 Deck
4.3.1 Slab
This class create different shape of slab. Current column shape include concrete_solid
, concrete_PK
, concrete_box
, concrete_costalia
, steel_box
, steel_sidebox
, truss
each column shape is a class function. Class arguments are as follow:
arg name | meaning |
---|---|
t_deck | thickness of deck |
h_deck | height of deck top surface |
l_deck | length of deck (x direction) |
w_column | width of column |
t_column | thickness of column (in yz plane) |
name | name of Blender object |
w_deck | width of deck |
For class variable | class variable name | meaning | | :——: | :——:| |T_truss| thickness of truss|
Class function truss
has little different with other class funtion in this class. Basically, for first step, a bigger cuboid is Hollow
by a smaller cuboid in x direction. For second step, the hollowed shape is Hollow
by several other triangular columns in y direction. Through this way, a truss is constructed.
For function variable, | function variable name | meaning | | :——: | :——:| |v_width|thickness of wall in yz plane z direction| |h_width|thickness of wall in yz plane y direction| |thick_bar|thickness of truss bar in xz plane| |width_bar|width of one truss unit in x direction|
For first step, the code is as follow:
v_width = 0.25
h_width = 0.25
cfg_start = setRectangle(self.W_deck, self.T_truss, h)
cfg_end = cfg_start
cfg_hollow_start = setRectangle(self.W_deck - 2*h_width, self.T_truss - 2*v_width, h - v_width)
cfg_hollow_end = cfg_hollow_start
orig = Rectangle(cfg_start, cfg_end, self.L_deck)
orig.createObj(self.name)
hollow = Rectangle(cfg_hollow_start, cfg_hollow_end, self.L_deck + 5)
hollow.createObj('hollow')
Hollow(orig.obj, hollow.obj)
For second step, the code is as follow:
thick_bar = v_width * 1.5
l = self.L_deck - thick_bar
width_bar = 4
height_bar = T_truss
a11 = -l/2 + thick_bar/2
b11 = thick_bar + self.H_deck - self.T_deck
a12 = -l/2 + width_bar - thick_bar/2
b12 = thick_bar+ self.H_deck - self.T_deck
a13 = -l/2 + thick_bar/2
b13 = height_bar - thick_bar - height_bar/width_bar * thick_bar + self.H_deck - self.T_deck
a23 = -l/2 + thick_bar/2
b23 = height_bar - thick_bar + self.H_deck - self.T_deck
a22 = -l/2 + width_bar - thick_bar/2
b22 = height_bar - thick_bar + self.H_deck - self.T_deck
a21 = -l/2 + width_bar - thick_bar/2
b21 = thick_bar + height_bar/width_bar * thick_bar + self.H_deck - self.T_deck
for i in range(int(l/width_bar)):
# for i in range(1):
name = 'down_hollow' + str(i)
cfg1_start = setTriangle2(a11 + width_bar*i, b11, a12+ width_bar*i, b12, a13+ width_bar*i, b13, 10)
cfg1_end = setTriangle2(a11+ width_bar*i, b11, a12+ width_bar*i, b12, a13+ width_bar*i, b13, -10)
tria1 = Triangle2(cfg1_start, cfg1_end, 1)
tria1.createObj(name)
Hollow(orig.obj, tria1.obj)
for i in range(int(l/width_bar)):
# for i in range(1):
name = 'up_hollow' + str(i)
cfg2_start = setTriangle2(a21 + width_bar*i, b21, a22+ width_bar*i, b22, a23+ width_bar*i, b23, 10)
cfg2_end = setTriangle2(a21+ width_bar*i, b21, a22+ width_bar*i, b22, a23+ width_bar*i, b23, -10)
tria2 = Triangle2(cfg2_start, cfg2_end, 1)
tria2.createObj(name)
Hollow(orig.obj, tria2.obj)
self.slab = orig.obj
4.4 Bearing
4.4.1 CableBase
CableBase
is the connection for cable and slab. For class arguments,
| function variable name | meaning |
| :——: | :——:|
|t1 |currently not used|
|t2|the length of cylinder burried inside|
|t3|the length of cylinder exposed outside|
|r2| radius of cylinder|
|name|name of Blender object|
|turn|make sure CableBase point to correct direction|
4.4.2 CableTop
CableTop
is the connection for cable and column. For class arguments,
| function variable name | meaning |
| :——: | :——:|
|t1 |currently not used|
|t2|the length of cylinder burried inside|
|t3|the length of cylinder exposed outside|
|r1| radius of cylinder|
|name|name of Blender object|
|turn|make sure CableTop point to correct direction|
5. Put All the Part Together
In this part, the Blender objects created in part4 are put together by setting their size, location and rotation. For class argument, | arg name | meaning | | :——: | :——:| |num_column|number of column| |h_column| height of column| |t_column| thickness of column in yz plane| |l_column| length of column in x direction| |w_column| width of column| |h_deck| height of deck top surface| |t_deck| thickness of deck| |index_column| the choice of column type| |index_deck| the choice of deck type| |index_cable|the choice cable type| |face_cable|number of cable plane| |num_cable|number of cable in single cable plane| |truss| whether slab is truss|
For class variable, | class variable| meaning| | :——: | :——:| |dist_column|distance between two column| |W_deck|width of deck| |cable_funciton| introduced in 4.2.1| |cable_top| all the coordicate for circle center at top of cable| |cable_bottom| all the coordinate for circle center at bottom of cable|
During initialization, according to cable-stayed bridge standard, L_deck
is randomized as follow:
if self.num_column == 1:
a = random.uniform(1.5, 3)
self.L_deck = a * self.H_column
else:
a = random.uniform(3, 6)
self.dist_column = a * self.H_column
self.L_deck = 2 * self.dist_column
5.1 column
In this class function, column object is created according to different index_column
:
def column(self):
for i in range(self.num_column):
if self.index_column == 1:
member = Column(self.W_column, self.H_column, self.T_column, self.L_column, self.H_deck, self.T_deck, 'A1 column')
member.A1()
elif self.index_column == 2:
member = Column(self.W_column, self.H_column, self.T_column, self.L_column, self.H_deck, self.T_deck, 'double column')
member.double()
elif self.index_column == 3:
member = Column(self.W_column, self.H_column, self.T_column, self.L_column, self.H_deck, self.T_deck, 'door column')
member.door()
elif self.index_column == 4:
member = Column(self.W_column, self.H_column, self.T_column, self.L_column, self.H_deck, self.T_deck, 'tower column')
member.tower()
self.cable_function = member.cable_function
member.column.location.x = (-1)**(i+1) * self.dist_column/2
The last command location.x
is a Blender-python API command used to set the location of Blender object.
5.2 deck
The code is as follow:
def deck(self):
k = self.cable_function[2]
b_in = self.cable_function[4]
h = self.H_deck + self.T_deck
if b_in == 0:
self.W_deck = self.W_column - self.T_column * 2
else:
self.W_deck = - (h - b_in)/k * 2
if self.tru == 0:
if self.index_deck == 1:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'comcrete solid deck', None, self.cable_function)
member.concrete_solid()
elif self.index_deck == 2:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'comcrete PK deck', None, self.cable_function)
member.concrete_PK()
elif self.index_deck == 3:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'comcrete box deck', None, self.cable_function)
member.concrete_box()
elif self.index_deck == 4:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'comcrete constalia deck', None, self.cable_function)
member.concrete_costalia()
elif self.index_deck == 5:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'steel box deck', None, self.cable_function)
member.steel_box()
elif self.index_deck == 6:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'steel sidebox deck', None, self.cable_function)
member.steel_sidebox()
elif self.tru == 1:
member = Slab(self.T_deck, self.H_deck, self.L_deck, self.W_column, self.T_column, 'truss', None, self.cable_function)
member.truss()
In this function, W_deck
is set at first according to cable_function
, in case shape of deck crush into shape of column. And then, the shape of deck is choosen according to index_deck
.
5.3 cable
There are three different type of cable used in this situation:
- Fan or Intermediate Cable System Schematic: cable is not parallel to each other in xz plane. All cables’ top and bottom coordinate are different.
- Harp or Parallel Cable System Schematic: cable is parallel to each other in xz plane.
- Radial or Converging Cable System Schematic: cable is not parallel to each other in xz plane. cables at same cable unit shape same cable top coordinate.
For function variable: | function variable name | meaning | | :——: | :——:| |column_loc| x coordinate for center of column| |dist_top| distance between circle center coordinate at top of cable (z direction)| |dist_bottom| distance between circle center coordinate at bottom of cable (x direction)|
The code for cable
is as follow:
def cable(self):
z_start = self.cable_function[0]
z_end = self.cable_function[1]
k = self.cable_function[2]
b = self.cable_function[3]
y_top = self.cable_function[5]
column_loc = np.zeros(self.num_column)
for i in range(self.num_column):
column_loc[i] = (-1)**(i+1) * self.dist_column/2
# top right side
dist_top = (z_end - z_start) / (self.num_cable - 1)
y_cable_top0 = np.zeros([self.num_cable, 1])
z_cable_top0 = np.zeros([self.num_cable, 1])
z_rand = random.uniform(z_start + (z_end - z_start)/4, z_end - (z_end - z_start)/4) ## for cable index3
for i in range(self.num_cable):
if self.index_cable == 3:
z_cable_top0[i] = z_rand
dist_top = 0
else:
z_cable_top0[i] = z_end - i * dist_top
if self.face_cable == 1:
y_cable_top0[i] = 0
elif self.face_cable == 2:
if b == 0:
y_cable_top0[i] = y_top
else:
y_cable_top0[i] = -(z_end - i * dist_top - b)/k
x_cable_top = np.ones([self.num_column, self.num_cable*2]) * column_loc.reshape([self.num_column, 1]) # *2: front and back
x_cable_top = x_cable_top.reshape([-1, 1])
for i in range(self.num_column): ## adjust the top part of cable just in touch with the surface of column
index_even = i*2
index_odd = i*2 + 1
x_cable_top[(index_odd*self.num_cable) : ((index_odd+1)*self.num_cable)] += (self.L_column/2)
x_cable_top[(index_even*self.num_cable) : ((index_even+1)*self.num_cable)] -= (self.L_column/2)
yz_cable_top0 = np.concatenate((y_cable_top0, z_cable_top0), 1)
yz_cable_top = yz_cable_top0
for i in range(self.num_column*2 - 1):
yz_cable_top = np.concatenate((yz_cable_top, yz_cable_top0), 0)
cable_top_right = np.concatenate((x_cable_top, yz_cable_top), 1)
# bottom right side
if self.tru == 1:
z_cable_bottom = self.H_deck - self.T_deck + self.T_truss
elif self.tru ==0:
z_cable_bottom = self.H_deck
if self.face_cable == 2:
print(self.W_deck)
y_cable_bottom = self.W_deck/2 * (7/10)
elif self.face_cable == 1:
y_cable_bottom = 0
a = random.uniform(7/8, 9/10)
x_L = self.L_deck / self.num_column / 2 * a ## outer distance of cable at bottom
if self.index_cable == 2:
k_cable = (z_end - z_cable_bottom)/x_L
dist_bottom = dist_top / k_cable ## distance between two cable in x direction
else:
b = random.uniform(1/7, 1/5)
# x_L_in = b * x_L
dist_bottom = x_L / self.num_cable
x_cable_bottom = np.zeros([self.num_column, self.num_cable*2]) ## one row represents x coordinate of all cable for one column
loc = np.hstack((np.linspace(-self.num_cable, -1, self.num_cable), np.linspace(self.num_cable, 1, self.num_cable))) ## help to locate x coordinate
for i in range(self.num_column):
x_cable_bottom[i, :] = column_loc[i] + dist_bottom * loc
x_cable_bottom = x_cable_bottom.reshape([-1, 1])
y_cable_bottom = x_cable_bottom * 0 + y_cable_bottom
z_cable_bottom = x_cable_bottom * 0 + z_cable_bottom
cable_bottom_right = np.concatenate((x_cable_bottom, y_cable_bottom, z_cable_bottom), 1)
if self.face_cable == 1:
cable_top = cable_top_right
cable_bottom = cable_bottom_right
for i in range(self.num_column * self.num_cable * 2):
Cable(cable_bottom[i, :], cable_top[i, :], "cable" + str(i+1))
elif self.face_cable == 2:
cable_top_left = cable_top_right * np.array([[1, -1, 1]])
cable_bottom_left = cable_bottom_right * np.array([[1, -1, 1]])
cable_top = np.concatenate((cable_top_left, cable_top_right), 0)
cable_bottom = np.concatenate((cable_bottom_left, cable_bottom_right), 0)
for i in range(self.num_column * self.num_cable * 4):
Cable(cable_bottom[i, :], cable_top[i, :], "cable" + str(i+1))
self.cable_top = cable_top
self.cable_bottom = cable_bottom
For the detail of this code, first some constrain for cable position is set according to cable_function
and column_loc
.
z_start = self.cable_function[0]
z_end = self.cable_function[1]
k = self.cable_function[2]
b = self.cable_function[3]
y_top = self.cable_function[5]
column_loc = np.zeros(self.num_column)
for i in range(self.num_column):
column_loc[i] = (-1)**(i+1) * self.dist_column/2
And then, the coordinate of circle center for top face of cable is set. First, set the circle center y and z coordinate for top face of every cable. If it is type3 cable, all coordinates have y = 0; for other two type, it is the right face of cable, which means y > 0.
dist_top = (z_end - z_start) / (self.num_cable - 1)
y_cable_top0 = np.zeros([self.num_cable, 1])
z_cable_top0 = np.zeros([self.num_cable, 1])
z_rand = random.uniform(z_start + (z_end - z_start)/4, z_end - (z_end - z_start)/4) ## for cable index3
for i in range(self.num_cable):
if self.index_cable == 3:
z_cable_top0[i] = z_rand
dist_top = 0
else:
z_cable_top0[i] = z_end - i * dist_top
if self.face_cable == 1:
y_cable_top0[i] = 0
elif self.face_cable == 2:
if b == 0:
y_cable_top0[i] = y_top
else:
y_cable_top0[i] = -(z_end - i * dist_top - b)/k
Second, set the x coordinate for top face of cable. The little loop here is to make sure the contact point for cable and column is just at the surface of column.
x_cable_top = np.ones([self.num_column, self.num_cable*2]) * column_loc.reshape([self.num_column, 1]) # *2: front and back
x_cable_top = x_cable_top.reshape([-1, 1])
for i in range(self.num_column): ## adjust the top part of cable just in touch with the surface of column
index_even = i*2
index_odd = i*2 + 1
x_cable_top[(index_odd*self.num_cable) : ((index_odd+1)*self.num_cable)] += (self.L_column/2)
x_cable_top[(index_even*self.num_cable) : ((index_even+1)*self.num_cable)] -= (self.L_column/2)
Third, concatenate all the x, y, z coordinate to construct one-side top face coordinate.
yz_cable_top0 = np.concatenate((y_cable_top0, z_cable_top0), 1)
yz_cable_top = yz_cable_top0
for i in range(self.num_column*2 - 1):
yz_cable_top = np.concatenate((yz_cable_top, yz_cable_top0), 0)
cable_top_right = np.concatenate((x_cable_top, yz_cable_top), 1)
After all of this, one-side bottom face coordinate is constructed. First, z coordinate for all bottom face is set according to the whether the slab is a truss or not. Because truss and other slab have different thickness. And y coordinate is set according to whether cable face is 1 or 2.
if self.tru == 1:
z_cable_bottom = self.H_deck - self.T_deck + self.T_truss
elif self.tru ==0:
z_cable_bottom = self.H_deck
if self.face_cable == 2:
print(self.W_deck)
y_cable_bottom = self.W_deck/2 * (7/10)
elif self.face_cable == 1:
y_cable_bottom = 0
Third, x coordinate is set. x_L
here is the largest distance a bottom face of cable can be to it nearest column. dist_bottom
is set according to whether cable is parallel to each other in xz plane (type2 cable). According these two parameters, x coordinate is constructed.
a = random.uniform(7/8, 9/10)
x_L = self.L_deck / self.num_column / 2 * a ## outer distance of cable at bottom
if self.index_cable == 2:
k_cable = (z_end - z_cable_bottom)/x_L
dist_bottom = dist_top / k_cable ## distance between two cable in x direction
else:
b = random.uniform(1/7, 1/5)
# x_L_in = b * x_L
dist_bottom = x_L / self.num_cable
x_cable_bottom = np.zeros([self.num_column, self.num_cable*2]) ## one row represents x coordinate of all cable for one column
loc = np.hstack((np.linspace(-self.num_cable, -1, self.num_cable), np.linspace(self.num_cable, 1, self.num_cable))) ## help to locate x coordinate
for i in range(self.num_column):
x_cable_bottom[i, :] = column_loc[i] + dist_bottom * loc
Fourth, concatenate all x, y, z coordinate for one-side cable bottom face.
x_cable_bottom = x_cable_bottom.reshape([-1, 1])
y_cable_bottom = x_cable_bottom * 0 + y_cable_bottom
z_cable_bottom = x_cable_bottom * 0 + z_cable_bottom
cable_bottom_right = np.concatenate((x_cable_bottom, y_cable_bottom, z_cable_bottom), 1)
Finally, complete all the coordinate for top face and bottom face according to the cable face number, and get cable_top
and cable_bottom
.
if self.face_cable == 1:
cable_top = cable_top_right
cable_bottom = cable_bottom_right
for i in range(self.num_column * self.num_cable * 2):
Cable(cable_bottom[i, :], cable_top[i, :], "cable" + str(i+1))
elif self.face_cable == 2:
cable_top_left = cable_top_right * np.array([[1, -1, 1]])
cable_bottom_left = cable_bottom_right * np.array([[1, -1, 1]])
cable_top = np.concatenate((cable_top_left, cable_top_right), 0)
cable_bottom = np.concatenate((cable_bottom_left, cable_bottom_right), 0)
for i in range(self.num_column * self.num_cable * 4):
Cable(cable_bottom[i, :], cable_top[i, :], "cable" + str(i+1))
self.cable_top = cable_top
self.cable_bottom = cable_bottom
5.4 cablebase & cabletop
For both of these two components, first use CableBase
and CableTop
class to construct the Blender object, and then use rotation_euler
Blender command to orient the object according to cable orientation, together with location
command to set the correct position. Code is as follow.
def cablebase(self):
t1 = random.uniform(0.8, 1.4) # need further revise
t2 = random.uniform(0.4, 0.7)
t3 = random.uniform(0.8, 1.5)
r2 = 0.15
for i in range(self.cable_top.shape[0]):
kxy = (self.cable_top[i, 1] - self.cable_bottom[i, 1])/(self.cable_top[i, 0] - self.cable_bottom[i, 0])
kxz = (self.cable_top[i, 2] - self.cable_bottom[i, 2])/(self.cable_top[i, 0] - self.cable_bottom[i, 0])
kyz = (self.cable_top[i, 2] - self.cable_bottom[i, 2])/(self.cable_top[i, 1] - self.cable_bottom[i, 1])
turn = kxz/abs(kxz)
member = CableBase(t1, t2, t3, r2, 'cable_base' + str(i+1), turn)
base = member.cable_base
theta_z = math.atan(kxy)
theta_y = math.atan(kxz)
theta_x = math.atan(kyz)
base.rotation_euler[0] = -theta_x
base.rotation_euler[1] = -theta_y
base.rotation_euler[2] = theta_z
base.location.x = self.cable_bottom[i, 0]
base.location.y = self.cable_bottom[i, 1]
base.location.z = self.cable_bottom[i, 2]
def cabletop(self):
t1 = random.uniform(0.8, 1.4) # need further revise
t2 = random.uniform(0.4, 0.7)
t3 = random.uniform(0.8, 1.5)
r1 = 0.08
for i in range(self.cable_top.shape[0]):
kxy = (self.cable_top[i, 1] - self.cable_bottom[i, 1])/(self.cable_top[i, 0] - self.cable_bottom[i, 0])
kxz = (self.cable_top[i, 2] - self.cable_bottom[i, 2])/(self.cable_top[i, 0] - self.cable_bottom[i, 0])
kyz = (self.cable_top[i, 2] - self.cable_bottom[i, 2])/(self.cable_top[i, 1] - self.cable_bottom[i, 1])
turn = kxz/abs(kxz)
member = CableBase(t1, t2, t3, r1, 'cable_top' + str(i+1), turn)
top = member.cable_base
theta_z = math.atan(kxy)
theta_y = math.atan(kxz)
theta_x = math.atan(kyz)
top.rotation_euler[0] = -theta_x
top.rotation_euler[1] = -theta_y
top.rotation_euler[2] = theta_z
top.location.x = self.cable_top[i, 0]
top.location.y = self.cable_top[i, 1]
top.location.z = self.cable_top[i, 2]