Advertisement
Guest User

tree_generator_script.py

a guest
Jan 24th, 2024
122
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 15.26 KB | None | 0 0
  1. import bpy
  2. import bmesh
  3. import random
  4. from collections import namedtuple
  5. import blenderbim.tool as tool
  6. from math import sin, cos, pi
  7. from random import uniform
  8. from mathutils import Matrix, Vector
  9.  
  10. # TODO:
  11. # - generate simple shapes
  12. # - generate palm trees
  13.  
  14.  
  15. def blender_simple_enum_items(*items):
  16.     return [(i, i, i) for i in items]
  17.  
  18.  
  19. SimpleTreeParams = namedtuple("SimpleTreeParams", "plant_height crown_diameter trunk_diameter")
  20. LowPolyTreeParams = namedtuple(
  21.     "LowPolyTreeParams", "plant_height crown_diameter crown_max_loc crown_taper trunk_height trunk_diameter random_seed"
  22. )
  23. PalmTreeParams = namedtuple("PalmTreeParams", "plant_height crown_diameter trunk_diameter")
  24. TreePresetData = namedtuple("TreePresetData", "preset_class generator")
  25.  
  26.  
  27. GeneratedGeometry = namedtuple("GeneratedGeometry", "verts_2D edges_2D faces_2D verts_3D faces_3D")
  28.  
  29.  
  30. def generate_low_poly_tree(
  31.     crown_taper=0.5,
  32.     crown_max_loc=0.3,
  33.     plant_height=12.0,
  34.     trunk_height=2.0,
  35.     crown_diameter=4,
  36.     trunk_diameter=0.3,
  37.     random_seed=10,
  38. ) -> GeneratedGeometry:
  39.     random.seed(random_seed)
  40.     res = 6
  41.     crown_height = plant_height - trunk_height
  42.  
  43.     def circle(center, r, res):
  44.         points = []
  45.         for i in range(res):
  46.             # fmt: off
  47.             vert = (
  48.                 cos(i * 2 * pi / res) * r + center[0],
  49.                 sin(i * 2 * pi / res) * r + center[1],
  50.                 0.0 + center[2]
  51.             )
  52.             # fmt: on
  53.             points.append(vert)
  54.         return points
  55.  
  56.     def merge_lists(lists):
  57.         merged_list = []
  58.         for i in range(len(lists)):
  59.             merged_list = merged_list + lists[i]
  60.         return merged_list
  61.  
  62.     def XY_scale(vert, scale):
  63.         vert_scaled = (vert[0] * scale, vert[1] * scale, vert[2])
  64.         return vert_scaled
  65.  
  66.     def rand_add(vert, amp):
  67.         # fmt: off
  68.         vert_new = (
  69.             vert[0] + uniform(-amp, amp),
  70.             vert[1] + uniform(-amp, amp),
  71.             vert[2] + uniform(-amp, amp)
  72.         )
  73.         # fmt: on
  74.         return vert_new
  75.  
  76.     height_segments_trunk = 2
  77.     height_segments_crown = 7
  78.     height_segments_total = height_segments_trunk + height_segments_crown
  79.  
  80.     verts = []
  81.  
  82.     # Trunk
  83.     for i in range(height_segments_trunk):
  84.         points = circle((0.0, 0.0, i * trunk_height), trunk_diameter / 2, res)
  85.         verts.append(points)
  86.  
  87.     # Crown
  88.     h_crown_max_loc = int((height_segments_crown - 1) * crown_max_loc)
  89.     crown_indices = []
  90.     for i in range(0, h_crown_max_loc):
  91.         crown_indices.append(i)
  92.     for i in range(0, height_segments_crown - h_crown_max_loc):
  93.         crown_indices.append(h_crown_max_loc - i)
  94.  
  95.     my_min_val = min(crown_indices)
  96.     my_max_val = max(crown_indices)
  97.  
  98.     n_crown_taper = crown_taper * 0.8
  99.  
  100.     crown_diameters = []
  101.     for x in crown_indices:
  102.         crown_diameters.append(((x - my_min_val) / (my_max_val - my_min_val)) * n_crown_taper + (1 - n_crown_taper))
  103.  
  104.     randomize_amplitude = plant_height / 50
  105.  
  106.     crown_segment_height = crown_height / height_segments_crown
  107.     for i in range(0, height_segments_crown - 1):
  108.         points = circle(
  109.             (0.0, 0.0, trunk_height + (i + 1) * crown_segment_height),
  110.             crown_diameter * crown_diameters[i] / 2,
  111.             res,
  112.         )
  113.         for i in range(0, len(points)):
  114.             points[i] = rand_add(points[i], randomize_amplitude)
  115.         verts.append(points)
  116.  
  117.     # Top
  118.     verts.append(circle((0.0, 0.0, plant_height), 0.05, res))
  119.  
  120.     edge_loops = []
  121.     for i in range(height_segments_total):
  122.         edge_loops.append(range(i * res, (i + 1) * res))
  123.  
  124.     verts = merge_lists(verts)
  125.  
  126.     faces = []
  127.     bottom = [*edge_loops[0]]
  128.     faces.append(bottom)
  129.     for i in range(height_segments_total - 1):
  130.         for j in range(res):
  131.             face = (edge_loops[i][j - 1], edge_loops[i][j], edge_loops[i + 1][j], edge_loops[i + 1][j - 1])
  132.             faces.append(face)
  133.     top = [*edge_loops[-1]]
  134.     faces.append(top)
  135.  
  136.     verts_3D = [verts]
  137.     faces_3D = [faces]
  138.  
  139.     verts_2D = [
  140.         [
  141.             (-0.5865642428398132, 0.8647078275680542, 0.0),
  142.             (-0.7583824992179871, 0.6572927236557007, 0.0),
  143.             (-0.7234422564506531, 0.5865941047668457, 0.0),
  144.             (-0.8714070916175842, 0.46631893515586853, 0.0),
  145.             (-0.9203394055366516, 0.15792329609394073, 0.0),
  146.             (-0.6487269997596741, 0.11522211134433746, 0.0),
  147.             (-0.9273074269294739, 0.0903107076883316, 0.0),
  148.             (-0.9606701731681824, -0.2852219343185425, 0.0),
  149.             (-0.8060722351074219, -0.35055163502693176, 0.0),
  150.             (-0.8271104693412781, -0.4677186608314514, 0.0),
  151.             (-0.6899875998497009, -0.6695915460586548, 0.0),
  152.             (-0.6215555667877197, -0.6767659187316895, 0.0),
  153.             (-0.4315463900566101, -0.8824808597564697, 0.0),
  154.             (-0.30316293239593506, -0.9023936986923218, 0.0),
  155.             (-0.32218849658966064, -0.8037619590759277, 0.0),
  156.             (-0.21792533993721008, -0.9530860185623169, 0.0),
  157.             (0.36917445063591003, -0.877334475517273, 0.0),
  158.             (0.30831706523895264, -0.685136079788208, 0.0),
  159.             (0.4778200685977936, -0.8094909191131592, 0.0),
  160.             (0.9163193106651306, -0.368840754032135, 0.0),
  161.             (0.827597439289093, -0.29377281665802, 0.0),
  162.             (0.9478662610054016, -0.245104119181633, 0.0),
  163.             (0.9667968153953552, 0.08458180725574493, 0.0),
  164.             (0.7529001832008362, 0.05212751030921936, 0.0),
  165.             (0.9102436900138855, 0.19098728895187378, 0.0),
  166.             (0.8279229998588562, 0.5522171258926392, 0.0),
  167.             (0.6635335087776184, 0.6461355686187744, 0.0),
  168.             (0.5753684639930725, 0.8170149326324463, 0.0),
  169.             (0.3976244330406189, 0.8779193162918091, 0.0),
  170.             (0.18733686208724976, 0.7513588666915894, 0.0),
  171.             (0.338344544172287, 0.910933256149292, 0.0),
  172.             (-0.013130240142345428, 0.9798095226287842, 0.0),
  173.             (-0.18649885058403015, 0.9414206743240356, 0.0),
  174.             (-0.3193224370479584, 0.8835403919219971, 0.0),
  175.         ]
  176.     ]
  177.     edges_2D = []
  178.     faces_2D = []
  179.  
  180.     verts_2D_new = []
  181.     for vert in verts_2D[0]:
  182.         verts_2D_new.append(XY_scale(vert, crown_diameter / 2))
  183.  
  184.     range_of_verts = range(0, len(verts_2D[0]))
  185.     for i in range_of_verts:
  186.         edges_2D.append([range_of_verts[i - 1], i])
  187.         faces_2D.append(i)
  188.  
  189.     verts_2D[0] = verts_2D_new
  190.     edges_2D = [edges_2D]
  191.     faces_2D = [[faces_2D]]
  192.     return GeneratedGeometry(verts_2D, edges_2D, faces_2D, verts_3D, faces_3D)
  193.  
  194.  
  195. def bm_extrude(bm, geom, extrusion_vector):
  196.     extruded = bmesh.ops.extrude_face_region(bm, geom=geom)
  197.     translate_verts = [v for v in extruded["geom"] if isinstance(v, bmesh.types.BMVert)]
  198.     bmesh.ops.translate(bm, vec=extrusion_vector, verts=translate_verts)
  199.  
  200.  
  201. def generate_simple_tree(plant_height=2, crown_diameter=2, trunk_diameter=0.1) -> GeneratedGeometry:
  202.     # NOTE: with ifc it will be just IFC sphere
  203.     bm = bmesh.new()
  204.     crown_radius = crown_diameter / 2
  205.  
  206.     # trunk
  207.     circle_geom = bmesh.ops.create_circle(bm, cap_ends=True, segments=6, radius=trunk_diameter / 2)
  208.     extrusion_vector = Vector((0, 0, 1)) * (plant_height - crown_diameter)
  209.     bm_extrude(bm, [circle_geom["verts"][0].link_faces[0]], extrusion_vector)
  210.  
  211.     # crown
  212.     crown_offset_matrix = Matrix.Translation((0, 0, plant_height - crown_radius))
  213.     bmesh.ops.create_uvsphere(bm, u_segments=12, v_segments=12, radius=crown_radius, matrix=crown_offset_matrix)
  214.  
  215.     verts_3D = [v.co.copy() for v in bm.verts]
  216.     faces_3D = [[v.index for v in face.verts] for face in bm.faces]
  217.  
  218.     # 2d shape
  219.     shape_2D = bmesh.ops.create_circle(bm, cap_ends=True, segments=12, radius=crown_radius)["verts"]
  220.     verts_2D = [v.co.copy() for v in shape_2D]
  221.     faces_2D = [list(range(len(verts_2D)))]
  222.  
  223.     bm.free()
  224.  
  225.     return GeneratedGeometry([verts_2D], None, [faces_2D], [verts_3D], [faces_3D])
  226.  
  227.  
  228. def generate_palm_tree(plant_height=2, crown_diameter=5.8, trunk_diameter=0.1) -> GeneratedGeometry:
  229.     palm_verts = [
  230.         Vector((-3.6415634155273438, -3.8275668621063232, -1.0948246717453003)),
  231.         Vector((-0.3698049783706665, -1.805966854095459, 0.493910551071167)),
  232.         Vector((-1.6397790908813477, -0.5056067705154419, 0.04353363811969757)),
  233.         Vector((0.0, 0.0, 0.0)),
  234.         Vector((5.023162364959717, -1.6531240940093994, -1.0948246717453003)),
  235.         Vector((1.8090602159500122, 0.4589407444000244, 0.493910551071167)),
  236.         Vector((1.1666079759597778, -1.2413609027862549, 0.04353363811969757)),
  237.         Vector((1.6942416429519653, 5.038582801818848, -1.0948246717453003)),
  238.         Vector((-0.43215513229370117, 1.833944320678711, 0.493910551071167)),
  239.         Vector((1.2652605772018433, 1.1839056015014648, 0.04353363811969757)),
  240.         Vector((-4.577561378479004, 2.689336061477661, -1.0948246717453003)),
  241.         Vector((-1.8646581172943115, -0.0367276668548584, 0.493910551071167)),
  242.         Vector((-0.8873085975646973, 1.4957730770111084, 0.04353363811969757)),
  243.     ]
  244.     palm_faces = [(2, 1, 0), (2, 3, 1), (6, 5, 4), (6, 3, 5), (9, 8, 7), (9, 3, 8), (12, 11, 10), (12, 3, 11)]
  245.  
  246.     scale = Vector()
  247.     scale.xyz = crown_diameter / 11.6
  248.     leaves_height = 0.493910551071167 * scale.z
  249.     plant_height -= leaves_height
  250.     for v in palm_verts:
  251.         v *= scale
  252.         v.z += plant_height
  253.  
  254.     # 3d
  255.     bm = bmesh.new()
  256.     palm_bm_verts = [bm.verts.new(v) for v in palm_verts]
  257.     [bm.faces.new([palm_bm_verts[i] for i in face]) for face in palm_faces]
  258.  
  259.     circle_geom = bmesh.ops.create_circle(bm, cap_ends=True, segments=6, radius=trunk_diameter / 2)
  260.     extrusion_vector = Vector((0, 0, 1)) * plant_height
  261.     bm_extrude(bm, [circle_geom["verts"][0].link_faces[0]], extrusion_vector)
  262.     verts_3D = [v.co.copy() for v in bm.verts]
  263.     faces_3D = [[v.index for v in face.verts] for face in bm.faces]
  264.  
  265.     bm.free()
  266.  
  267.     # 2d
  268.     remove_z = Vector((1, 1, 0))
  269.     verts_2D = [v * remove_z for v in palm_verts]
  270.     faces_2D = palm_faces.copy()
  271.  
  272.     return GeneratedGeometry([verts_2D], None, [faces_2D], [verts_3D], [faces_3D])
  273.  
  274.  
  275. tree_presets = {
  276.     "simple": TreePresetData(SimpleTreeParams, generate_simple_tree),
  277.     "low_poly": TreePresetData(LowPolyTreeParams, generate_low_poly_tree),
  278.     "palm_tree": TreePresetData(PalmTreeParams, generate_palm_tree),
  279. }
  280.  
  281.  
  282. def update_tree_geometry_bmesh():
  283.     obj = bpy.context.active_object
  284.     props = obj.tree_generator_props
  285.  
  286.     get_params = lambda tree_class: {prop_name: getattr(props, prop_name) for prop_name in tree_class._fields}
  287.     preset = tree_presets.get(props.preset)
  288.     prop_data = get_params(preset.preset_class)
  289.     tree_geometry = preset.generator(**prop_data)
  290.  
  291.     if props.mode == "3d":
  292.         verts = tree_geometry.verts_3D[0]
  293.         faces = tree_geometry.faces_3D[0]
  294.     else:
  295.         verts = tree_geometry.verts_2D[0]
  296.         faces = tree_geometry.faces_2D[0]
  297.  
  298.     bm = tool.Blender.get_bmesh_for_mesh(obj.data, clean=True)
  299.     bm_verts = [bm.verts.new(v) for v in verts]
  300.     for face in faces:
  301.         bm.faces.new((bm_verts[i] for i in face))
  302.     tool.Blender.apply_bmesh(obj.data, bm, obj)
  303.  
  304.  
  305. class SaveTreeGeneratorParams(bpy.types.Operator):
  306.     bl_idname = "object.save_tree_generator_params"
  307.     bl_label = "Save Tree Generator Params To Clipboard"
  308.  
  309.     def execute(self, context):
  310.         obj = context.object
  311.         props = obj.tree_generator_props
  312.         preset = tree_presets[props.preset]
  313.         props_data = "\t".join(str(round(getattr(props, prop_name), 3)) for prop_name in preset.preset_class._fields)
  314.         props_data = f"{props.preset}\t{props_data}"
  315.         bpy.context.window_manager.clipboard = props_data
  316.         self.report({"INFO"}, f"Tree generator props saved to clipboard: {props_data}")
  317.         return {"FINISHED"}
  318.  
  319.  
  320. class LoadTreeGeneratorParams(bpy.types.Operator):
  321.     bl_idname = "object.load_tree_generator_params"
  322.     bl_label = "Load Tree Generator Params From Clipboard"
  323.  
  324.     def execute(self, context):
  325.         obj = context.object
  326.         props = obj.tree_generator_props
  327.         props_data = bpy.context.window_manager.clipboard.split("\t")
  328.         preset_name = props_data[0]
  329.         props.preset = preset_name
  330.  
  331.         preset = tree_presets[preset_name]
  332.         props_data = preset.preset_class(*props_data[1:])
  333.         for prop_name in preset.preset_class._fields:
  334.             value = getattr(props_data, prop_name)
  335.             value = type(getattr(props, prop_name))(value)
  336.             setattr(props, prop_name, value)
  337.         self.report({"INFO"}, f"Tree generator props loaded from clipboard: {props_data}")
  338.         return {"FINISHED"}
  339.  
  340.  
  341. class TreeGeneratorPanel(bpy.types.Panel):
  342.     bl_label = "Tree Generator Panel"
  343.     bl_idname = "OBJECT_PT_tree_generator"
  344.     bl_space_type = "PROPERTIES"
  345.     bl_region_type = "WINDOW"
  346.     bl_context = "modifier"
  347.  
  348.     def draw(self, context):
  349.         layout = self.layout
  350.         obj = context.active_object
  351.         props = obj.tree_generator_props
  352.  
  353.         layout.prop(props, "is_editing")
  354.         if props.is_editing:
  355.             layout.operator("object.save_tree_generator_params")
  356.             layout.operator("object.load_tree_generator_params")
  357.             layout.prop(props, "preset")
  358.             layout.prop(props, "mode")
  359.             for prop_name in tree_presets.get(props.preset).preset_class._fields:
  360.                 layout.prop(props, prop_name)
  361.             update_tree_geometry_bmesh()
  362.  
  363.  
  364. class TreeGeneratorProps(bpy.types.PropertyGroup):
  365.     is_editing: bpy.props.BoolProperty(name="Enable Tree Generator", default=False)
  366.     preset: bpy.props.EnumProperty(
  367.         name="Preset", items=blender_simple_enum_items(*tree_presets.keys()), default="low_poly"
  368.     )
  369.     mode: bpy.props.EnumProperty(name="Display Mode", items=blender_simple_enum_items("3d", "2d"))
  370.     random_seed: bpy.props.IntProperty(default=10)
  371.  
  372.     # low poly
  373.     plant_height: bpy.props.FloatProperty(default=12.0, min=0, subtype="DISTANCE")
  374.     crown_diameter: bpy.props.FloatProperty(default=4.0, min=0, subtype="DISTANCE")
  375.     crown_max_loc: bpy.props.FloatProperty(default=0.3, min=0, max=1)
  376.     crown_taper: bpy.props.FloatProperty(default=0.5, min=0, max=1)
  377.     trunk_height: bpy.props.FloatProperty(default=2.0, min=0, subtype="DISTANCE")
  378.     trunk_diameter: bpy.props.FloatProperty(default=0.3, min=0, subtype="DISTANCE")
  379.  
  380.     # simple
  381.     plant_height: bpy.props.FloatProperty(default=2, subtype="DISTANCE")
  382.  
  383.  
  384. bpy.utils.register_class(TreeGeneratorPanel)
  385. bpy.utils.register_class(TreeGeneratorProps)
  386. bpy.utils.register_class(LoadTreeGeneratorParams)
  387. bpy.utils.register_class(SaveTreeGeneratorParams)
  388. bpy.types.Object.tree_generator_props = bpy.props.PointerProperty(type=TreeGeneratorProps)
  389.  
  390.  
  391. print("Trees setup in current .blend project:")
  392. trees_data = []
  393. for obj in bpy.data.objects:
  394.     props = obj.tree_generator_props
  395.     preset_name = props.preset
  396.     preset = tree_presets.get(preset_name).preset_class
  397.     tree_data = [obj.name, preset_name]
  398.     preset_data = []
  399.     for prop_name in preset._fields:
  400.         value = round(getattr(props, prop_name), 3)
  401.         preset_data.append(value)
  402.     tree_data.append(tuple(preset_data))
  403.     print(f"{tuple(tree_data)},")
  404.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement