Hi,
I am trying to create python script for netting, where I could change parameters parametrically. This net is in the “frame”. The script nearly works, but only for certain parameters. e.g. if I changed angle some fibers went missing, or the cuts don’t work properly. The bug is in the section, where booleans (probably Intersect, and after then union) are applied.
We want to use the model for CFD simulation, that’s why we want to have it parametrically.
I know it is quite a large script. However did anybody come across this problem? And did you solve it?
Blender v2.82.7, Python 3.7.4
Alpha_top = [0]
Alpha_bottom = [90]
For these settings it works properly:
Alpha_top = [1]
Alpha_bottom = [45]
import bpy
import os
import math
from math import pi
from shutil import copyfile
bpy.ops.object.mode_set(mode='OBJECT')
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
bpy.context.scene.unit_settings.length_unit = 'MILLIMETERS'
# -------------DISTRIBUTOR PARAMETERS---------------------------------
# distributor
l_distr = 210 # mm
w_distr = 60 # mm
h_distr = 0.8 # mm
# hole
r_hole = 3 # mm
n_hole = 4 # 1
rel_offset_hole = 2 # 1=sticking to neighbours, 2= hole diameter inbetween
y_offset_hole = 1 # mm the first hole translation in y direction (+=out,-=in)
# channels
w_channel = 2 # mm
hole_in = 2 # connection hole order
y_offset_in = 1 # mm
hole_out = 4 # connection hole order
y_offset_out = -5 # mm
# -------------SPACER PARAMETERS---------------------------------
# Spacer size -
l = 160 # mm
w = 40 # mm
h = 0.8 # mm
# string shape
Anvert = [10] # 1............... number of verices in circle(3-256)
# string coincidence
Aspacing = [4] # mm ............distance beetwen centers of strings
Aoverlap = [0.2] # 1..............string overlap ratio (1=full diameter 0=lying on top)
Aalpha_top = [1] # deg ...........angle between x axis and top-string (-90...90)
Aalpha_bottom = [45] # deg ...........angle between x axis and bottom-string (-90...90)
x_offset = 1 # mm ............translation of strings layer according x-axis
y_offset = 0 # mm ............translation of strings layer according y-axis
cut = 3 # 1...BETA............ (0=no cut, 1=cut by yz plane, 2=cut to NET size) BETA - IF doesnt work change offset,spacing or turn it off :)
# -----------STL export------------------------------------
separate = True
quality = 0 # 1...BETA...........Refinement level (Negative=turn-off,0=smooth,1=smooth+refinement1)
path = bpy.path.abspath('//stlexport/')
# --------------------------------------------------------
# --------------------MAIN LOOP---------------------------
# --------------------------------------------------------
y_offset =y_offset -20
for nvert in Anvert:
for spacing in Aspacing:
for overlap in Aoverlap:
for alpha_top in Aalpha_top:
for alpha_bottom in Aalpha_bottom:
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete()
# -----------CALCULATED_PARAMETERS------------------------------------
# string radius
r = 1 / 2 * h / (2 - overlap)
# RASTR size
xx = 2.2 * l
yy = 2.2 * l
y_hole = w / 2 - r_hole + y_offset_hole
x_hole = -(l_distr - l) / 4
alpha_in = (90 * pi / 180) + math.atan(
-x_hole / (y_hole - y_offset_in - (hole_in - 1) * 2 * r_hole * rel_offset_hole))
alpha_out = (90 * pi / 180) + math.atan(
x_hole / (y_hole - y_offset_out - (hole_out - 1) * 2 * r_hole * rel_offset_hole))
l_in = 2 * (x_hole / math.cos(alpha_in))
l_out = 2 * (x_hole / math.cos(alpha_out))
# ------------------- CUT-OFF--------------------------------------------
obj_names = ["Cube", "Channel_in", "Channel_out", "Hole_in", "Hole_out"]
if cut == 1:
bpy.ops.mesh.primitive_cube_add(size=1, location=(xx / 5, 0, 0))
bpy.context.object.scale[0] = xx / 2.5
bpy.context.object.scale[1] = yy
bpy.context.object.scale[2] = 3 * h
elif cut >= 2:
bpy.ops.mesh.primitive_cube_add(size=1, location=(l / 2, 0, 0))
bpy.context.active_object.name = obj_names[0]
bpy.context.object.scale[0] = l
bpy.context.object.scale[1] = w
bpy.context.object.scale[2] = 3 * h
# ------------------- DISTRIBUTOR --------------------------------------------
if cut == 3:
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, y_offset_in, 0))
bpy.context.active_object.name = obj_names[1] # "Channel_in"
bpy.context.object.scale[0] = l_in
bpy.context.object.scale[1] = w_channel
bpy.context.object.scale[2] = 3 * h
bpy.context.object.rotation_euler[2] = alpha_in
bpy.ops.mesh.primitive_cube_add(size=1, location=(l, y_offset_out, 0))
bpy.context.active_object.name = obj_names[2] # "Channel_out"
bpy.context.object.scale[0] = l_out
bpy.context.object.scale[1] = w_channel
bpy.context.object.scale[2] = 3 * h
bpy.context.object.rotation_euler[2] = alpha_out
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=r_hole, depth=(3 * h),
location=(x_hole, y_hole, 0), rotation=(0, 0, 0))
bpy.context.active_object.name = obj_names[3] # "Hole_in"
bpy.ops.object.modifier_add(type='ARRAY')
bpy.context.object.modifiers["Array"].count = n_hole
bpy.context.object.modifiers["Array"].fit_type = 'FIXED_COUNT'
bpy.context.object.modifiers["Array"].relative_offset_displace[0] = 0
bpy.context.object.modifiers["Array"].relative_offset_displace[1] = -rel_offset_hole
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Array")
bpy.ops.mesh.primitive_cylinder_add(vertices=64, radius=r_hole, depth=(3 * h),
location=(x_hole + (l + l_distr) / 2, y_hole, 0),
rotation=(0, 0, 0))
bpy.context.active_object.name = obj_names[4] # "Hole_out"
bpy.ops.object.modifier_add(type='ARRAY')
bpy.context.object.modifiers["Array"].count = n_hole
bpy.context.object.modifiers["Array"].fit_type = 'FIXED_COUNT'
bpy.context.object.modifiers["Array"].relative_offset_displace[0] = 0
bpy.context.object.modifiers["Array"].relative_offset_displace[1] = -rel_offset_hole
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Array")
bpy.ops.mesh.primitive_cube_add(size=1, location=(l / 2, 0, h_distr / 2))
bpy.context.active_object.name = "Distributor"
bpy.context.object.scale[0] = l_distr
bpy.context.object.scale[1] = w_distr
bpy.context.object.scale[2] = h_distr
for obj_name in obj_names:
bpy.ops.object.modifier_add(type='BOOLEAN')
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects[obj_name]
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
bpy.ops.object.select_all(action='DESELECT')
for i in range(1, len(obj_names)):
bpy.data.objects[obj_names[i]].select_set(True)
bpy.ops.object.delete()
# ------------------- Top ARRAY--------------------------------------------
bpy.ops.mesh.primitive_cylinder_add(vertices=nvert, radius=r, depth=xx,
location=(x_offset, y_offset, (r + 2 * r * (1 - overlap))),
rotation=((90 * pi / 180), 0, ((90 - alpha_top) * pi / 180)))
bpy.context.active_object.name = "Top"
obj = bpy.context.object
bpy.ops.object.modifier_add(type='SUBSURF')
bpy.context.object.modifiers["Subdivision"].render_levels = 4
bpy.context.object.modifiers["Subdivision"].levels = 4
bpy.context.object.modifiers["Subdivision"].subdivision_type = 'SIMPLE'
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Subdivision")
bpy.ops.object.modifier_add(type='ARRAY')
bpy.context.object.modifiers["Array"].name = "Array.001"
bpy.context.object.modifiers["Array.001"].use_constant_offset = True
bpy.context.object.modifiers["Array.001"].constant_offset_displace[0] = spacing
bpy.context.object.modifiers["Array.001"].fit_type = 'FIT_LENGTH'
bpy.context.object.modifiers["Array.001"].fit_length = yy / 2
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Array.001")
if cut >= 1:
bpy.ops.object.modifier_add(type='BOOLEAN')
obj.modifiers["Boolean"].operation = 'INTERSECT'
obj.modifiers["Boolean"].double_threshold = 0
obj.modifiers["Boolean"].object = bpy.data.objects["Cube"]
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
#
# ------------------- Bottom ARRAY--------------------------------------------
bpy.ops.mesh.primitive_cylinder_add(vertices=nvert, radius=r, depth=yy,
location=(x_offset, y_offset, r),
rotation=((90 * pi / 180), 0, ((90 - alpha_bottom) * pi / 180)))
bpy.context.active_object.name = "Bottom"
bpy.ops.object.modifier_add(type='SUBSURF')
bpy.context.object.modifiers["Subdivision"].render_levels = 4
bpy.context.object.modifiers["Subdivision"].levels = 4
bpy.context.object.modifiers["Subdivision"].subdivision_type = 'SIMPLE'
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Subdivision")
bpy.ops.object.modifier_add(type='ARRAY')
bpy.context.object.modifiers["Array"].name = "Array.002"
bpy.context.object.modifiers["Array.002"].use_constant_offset = True
bpy.context.object.modifiers["Array.002"].constant_offset_displace[0] = spacing
bpy.context.object.modifiers["Array.002"].fit_type = 'FIT_LENGTH'
bpy.context.object.modifiers["Array.002"].fit_length = xx / 2
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Array.002")
if cut >= 1:
bpy.ops.object.modifier_add(type='BOOLEAN')
bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT'
bpy.context.object.modifiers["Boolean"].double_threshold = 0
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["Cube"]
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
bpy.ops.object.select_all(action='DESELECT')
if cut >= 1:
bpy.data.objects['Cube'].select_set(True)
bpy.ops.object.delete()
bpy.ops.object.select_all(action='DESELECT')
# -----------------UNION OF PARTS-----------------------------------------------
if cut >= 3:
Top = bpy.context.scene.objects["Top"]
bpy.context.view_layer.objects.active = Top
Top.select_set(True)
bpy.ops.object.modifier_add(type='BOOLEAN')
bpy.context.object.modifiers["Boolean"].operation = 'UNION'
bpy.context.object.modifiers["Boolean"].double_threshold = 0
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["Bottom"]
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
bpy.ops.object.modifier_add(type='BOOLEAN')
bpy.context.object.modifiers["Boolean"].operation = 'UNION'
bpy.context.object.modifiers["Boolean"].double_threshold = 0
bpy.context.object.modifiers["Boolean"].object = bpy.data.objects["Distributor"]
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Boolean")
bpy.data.objects.remove(bpy.data.objects["Bottom"], do_unlink=True)
bpy.data.objects.remove(bpy.data.objects["Distributor"], do_unlink=True)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.mesh.remove_doubles(threshold=0.05)
bpy.ops.object.mode_set(mode='OBJECT')