• Daz 3D
  • Shop
  • 3D Software
    • Daz Studio Premier
    • Daz Studio
    • Install Manager
    • Partnerships
    • AI Training data
    • Exporters
    • Daz to Roblox
    • Daz to Maya
    • Daz to Blender
    • Daz to Unreal
    • Daz to Unity
    • Daz to 3ds Max
    • Daz to Cinema 4D
  • 3D Models
    • Genesis 9
    • Genesis 8.1
    • Free 3D Models
  • Community
    • Gallery
    • Forums
    • Blog
    • Press
    • Help
  • Memberships
    • Daz Premier
    • Daz Plus
    • Daz Base
    • Compare
  • Download Studio
ADVANCED SEARCH
  • Menu
  • Daz 3D
ADVANCED SEARCH
Add image
  • Shop
  • 3d Software
    • Daz Studio Premier
    • Daz Studio
    • Install Manager
    • Partnerships
    • AI Training data
    • Exporters
    • Daz to Roblox
    • Daz to Maya
    • Daz to Blender
    • Daz to Unreal
    • Daz to Unity
    • Daz to 3ds Max
    • Daz to Cinema 4D
  • 3D Models
    • Genesis 9
    • Genesis 8.1
    • Free 3D Models
  • Community
    • Our Community
    • Gallery
    • Forums
    • Blog
    • Press
    • Help
  • Memberships
    • Daz Premier
    • Daz Plus
    • Daz Base
    • Compare

Notifications

You currently have no notifications.

Loading...
    • Categories
    • Recent Discussions
Daz 3D Forums > 3rd Party Software > Blender Discussion

Trying to create a script for creating a road/highway system

EightiesIsEnoughEightiesIsEnough Posts: 1,228
September 22 edited September 22 in Blender Discussion

Last night, with the help of Google Gemini, I have a script created to help design highways by adding straight road objects, curved road objects, and intersections, and even have the ability to create road shoulders and even create slopes (for hills).

Adding a straight road section with shoulders is no problem. However, I am experiencing problems with creating a curved road section with shoulders, and the intersection should have curved shoulders on the T-intersection.

Here is the code I have so far, in Python script:

import bpy
import bmesh
import math
import random
from mathutils import Vector, Euler

def get_or_create_material(mat_name):
    if mat_name not in bpy.data.materials:
        mat = bpy.data.materials.new(name=mat_name)
        mat.use_nodes = True
        bsdf = mat.node_tree.nodes["Principled BSDF"]
        if mat_name == "RoadTexture":
            bsdf.inputs['Base Color'].default_value = (0.1, 0.1, 0.1, 1)
        elif mat_name == "GravelTexture":
            bsdf.inputs['Base Color'].default_value = (0.3, 0.3, 0.3, 1)
        return mat
    return bpy.data.materials[mat_name]

def create_straight_road(name, start_point, length, width, shoulder_width, slope, lanes):
    bm = bmesh.new()
    lane_width = width / lanes
    
    v_road_left_front = bm.verts.new((0, 0, 0))
    v_road_right_front = bm.verts.new((0, width, 0))
    v_road_left_back = bm.verts.new((length, 0, 0))
    v_road_right_back = bm.verts.new((length, width, 0))
    bm.faces.new((v_road_left_front, v_road_right_front, v_road_right_back, v_road_left_back))
    
    me_road = bpy.data.meshes.new(name + "_road_mesh")
    bm.to_mesh(me_road)
    bm.free()
    obj_road = bpy.data.objects.new(name + "_road", me_road)
    bpy.context.collection.objects.link(obj_road)

    bm_shoulders = bmesh.new()
    v_sl_front = bm_shoulders.verts.new((0, -shoulder_width, 0))
    v_sl_back = bm_shoulders.verts.new((length, -shoulder_width, 0))
    v_sl_road_back = bm_shoulders.verts.new((length, 0, 0))
    v_sl_road_front = bm_shoulders.verts.new((0, 0, 0))
    bm_shoulders.faces.new((v_sl_front, v_sl_road_front, v_sl_road_back, v_sl_back))

    v_sr_front = bm_shoulders.verts.new((0, width, 0))
    v_sr_back = bm_shoulders.verts.new((length, width, 0))
    v_sr_road_back = bm_shoulders.verts.new((length, width + shoulder_width, 0))
    v_sr_road_front = bm_shoulders.verts.new((0, width + shoulder_width, 0))
    bm_shoulders.faces.new((v_sr_front, v_sr_road_front, v_sr_road_back, v_sr_back))

    me_shoulders = bpy.data.meshes.new(name + "_shoulders_mesh")
    bm_shoulders.to_mesh(me_shoulders)
    bm_shoulders.free()
    obj_shoulders = bpy.data.objects.new(name + "_shoulders", me_shoulders)
    bpy.context.collection.objects.link(obj_shoulders)

    mat_road = get_or_create_material("RoadTexture")
    mat_gravel = get_or_create_material("GravelTexture")
    obj_road.data.materials.append(mat_road)
    obj_shoulders.data.materials.append(mat_gravel)

    obj_road.location = start_point
    obj_shoulders.location = start_point
    obj_road.rotation_euler = Euler((0, math.radians(slope), 0))
    obj_shoulders.rotation_euler = Euler((0, math.radians(slope), 0))

    return obj_road, obj_shoulders

def create_curved_road(name, start_point, radius, angle, width, shoulder_width, segments):
    bm_road = bmesh.new()
    bm_shoulders = bmesh.new()
    
    center = (start_point[0], start_point[1] + radius, start_point[2])
    start_angle_rad = 0
    end_angle_rad = math.radians(angle)
    angle_step = (end_angle_rad - start_angle_rad) / segments
    
    prev_road_v_inner = None
    prev_road_v_outer = None
    prev_shoulder_v_inner = None
    prev_shoulder_v_outer = None
    
    for i in range(segments + 1):
        current_angle = start_angle_rad + (i * angle_step)
        
        # Calculate road vertices
        x_road_inner = center[0] + radius * math.cos(current_angle)
        y_road_inner = center[1] + radius * math.sin(current_angle)
        x_road_outer = center[0] + (radius + width) * math.cos(current_angle)
        y_road_outer = center[1] + (radius + width) * math.sin(current_angle)
        
        road_v_inner = bm_road.verts.new((x_road_inner, y_road_inner, center[2]))
        road_v_outer = bm_road.verts.new((x_road_outer, y_road_outer, center[2]))
        
        # Calculate shoulder vertices
        x_shoulder_inner = center[0] + (radius - shoulder_width) * math.cos(current_angle)
        y_shoulder_inner = center[1] + (radius - shoulder_width) * math.sin(current_angle)
        x_shoulder_outer = center[0] + (radius + width + shoulder_width) * math.cos(current_angle)
        y_shoulder_outer = center[1] + (radius + width + shoulder_width) * math.sin(current_angle)
        
        shoulder_v_inner = bm_shoulders.verts.new((x_shoulder_inner, y_shoulder_inner, center[2]))
        shoulder_v_outer = bm_shoulders.verts.new((x_shoulder_outer, y_shoulder_outer, center[2]))
        
        if i > 0:
            # Create road face
            bm_road.faces.new((road_v_inner, road_v_outer, prev_road_v_outer, prev_road_v_inner))
            
            # **FIXED:** Create inner shoulder face using only shoulder vertices
            bm_shoulders.faces.new((shoulder_v_inner, prev_shoulder_v_inner, prev_road_v_inner, road_v_inner))
            
            # **FIXED:** Create outer shoulder face using only shoulder vertices
            bm_shoulders.faces.new((road_v_outer, prev_road_v_outer, prev_shoulder_v_outer, shoulder_v_outer))
        
        prev_road_v_inner = road_v_inner
        prev_road_v_outer = road_v_outer
        prev_shoulder_v_inner = shoulder_v_inner
        prev_shoulder_v_outer = shoulder_v_outer
            
    me_road = bpy.data.meshes.new(name + "_road_mesh")
    bm_road.to_mesh(me_road)
    bm_road.free()
    obj_road = bpy.data.objects.new(name + "_road", me_road)
    bpy.context.collection.objects.link(obj_road)

    me_shoulders = bpy.data.meshes.new(name + "_shoulders_mesh")
    bm_shoulders.to_mesh(me_shoulders)
    bm_shoulders.free()
    obj_shoulders = bpy.data.objects.new(name + "_shoulders", me_shoulders)
    bpy.context.collection.objects.link(obj_shoulders)

    mat_road = get_or_create_material("RoadTexture")
    mat_gravel = get_or_create_material("GravelTexture")
    obj_road.data.materials.append(mat_road)
    obj_shoulders.data.materials.append(mat_gravel)
    
    obj_road.location = start_point
    obj_shoulders.location = start_point
    
    return obj_road, obj_shoulders

def create_intersection(name, start_point, width, shoulder_width):
    bm_road = bmesh.new()
    size = (width + shoulder_width * 2) / 2
    v1 = bm_road.verts.new((start_point[0] - size, start_point[1] - size, start_point[2]))
    v2 = bm_road.verts.new((start_point[0] + size, start_point[1] - size, start_point[2]))
    v3 = bm_road.verts.new((start_point[0] + size, start_point[1] + size, start_point[2]))
    v4 = bm_road.verts.new((start_point[0] - size, start_point[1] + size, start_point[2]))
    bm_road.faces.new((v1, v2, v3, v4))

    me_road = bpy.data.meshes.new(name + "_mesh")
    bm_road.to_mesh(me_road)
    bm_road.free()
    obj_road = bpy.data.objects.new(name, me_road)
    bpy.context.collection.objects.link(obj_road)

    mat_road = get_or_create_material("RoadTexture")
    obj_road.data.materials.append(mat_road)
    
    return obj_road

def get_last_road_end_point(obj):
    if obj and "Road" in obj.name:
        local_end = Vector((obj.dimensions.x, 0, 0))
        return obj.matrix_world @ local_end
    return None

class RoadProperties(bpy.types.PropertyGroup):
    lanes: bpy.props.IntProperty(
        name="Lanes",
        description="Number of lanes",
        default=2,
        min=1,
        max=4
    )
    lane_width: bpy.props.FloatProperty(
        name="Lane Width",
        description="Width of a single lane",
        default=3.5,
        min=2.0,
        max=5.0
    )
    length: bpy.props.FloatProperty(
        name="Length",
        description="Length of the straight road",
        default=10.0,
        min=1.0,
        max=50.0
    )
    radius: bpy.props.FloatProperty(
        name="Radius",
        description="Radius of the curved road",
        default=10.0,
        min=1.0,
        max=50.0
    )
    angle: bpy.props.IntProperty(
        name="Angle",
        description="Bend angle of the curved road",
        default=90,
        min=0,
        max=180
    )
    segments: bpy.props.IntProperty(
        name="Segments",
        description="Number of faces for the curved road",
        default=20,
        min=1,
        max=100
    )
    slope: bpy.props.FloatProperty(
        name="Slope",
        description="Road incline/decline in degrees",
        default=0.0,
        min=-20.0,
        max=20.0
    )
    shoulder_width: bpy.props.FloatProperty(
        name="Shoulder Width",
        description="Width of the road shoulders",
        default=1.0,
        min=0.1,
        max=5.0
    )
    randomize: bpy.props.BoolProperty(
        name="Randomize Parameters",
        description="Randomly generate a road section",
        default=False
    )

class CreateStraightRoadOperator(bpy.types.Operator):
    bl_idname = "object.create_straight_road"
    bl_label = "Create Straight Road"

    def execute(self, context):
        props = context.scene.road_props
        
        if props.randomize:
            props.length = random.uniform(5, 30)
            props.lanes = random.randint(1, 4)
            props.lane_width = random.uniform(3, 4)
            props.shoulder_width = random.uniform(0.5, 2.0)
            props.slope = random.uniform(-10, 10)
        
        road_width = props.lanes * props.lane_width
        
        start_point = (0, 0, 0)
        last_obj = bpy.context.view_layer.objects.active
        if last_obj and "Road" in last_obj.name:
            start_point = get_last_road_end_point(last_obj)

        create_straight_road("StraightRoad", start_point, props.length, road_width, props.shoulder_width, props.slope, props.lanes)
        return {'FINISHED'}

class CreateCurvedRoadOperator(bpy.types.Operator):
    bl_idname = "object.create_curved_road"
    bl_label = "Create Curved Road"

    def execute(self, context):
        props = context.scene.road_props
        
        if props.randomize:
            props.radius = random.uniform(5, 30)
            props.angle = random.randint(10, 180)
            props.lanes = random.randint(1, 4)
            props.lane_width = random.uniform(3, 4)
            props.shoulder_width = random.uniform(0.5, 2.0)
            props.segments = random.randint(10, 50)
        
        road_width = props.lanes * props.lane_width
        
        start_point = (0, 0, 0)
        last_obj = bpy.context.view_layer.objects.active
        if last_obj and "Road" in last_obj.name:
            start_point = get_last_road_end_point(last_obj)

        create_curved_road("CurvedRoad", start_point, props.radius, props.angle, road_width, props.shoulder_width, props.segments)
        return {'FINISHED'}

class CreateIntersectionOperator(bpy.types.Operator):
    bl_idname = "object.create_intersection"
    bl_label = "Create Intersection"

    def execute(self, context):
        props = context.scene.road_props
        
        if props.randomize:
            props.lanes = random.randint(1, 4)
            props.lane_width = random.uniform(3, 4)
            props.shoulder_width = random.uniform(0.5, 2.0)
            
        road_width = props.lanes * props.lane_width
        start_point = (0, 0, 0)
        
        create_intersection("Intersection", start_point, road_width, props.shoulder_width)
        return {'FINISHED'}

class RoadGeneratorPanel(bpy.types.Panel):
    bl_label = "Road Generator"
    bl_idname = "VIEW3D_PT_road_generator"
    bl_space_type = 'VIEW_3D'
    bl_region_type = 'UI'
    bl_category = 'Create'

    def draw(self, context):
        layout = self.layout
        props = context.scene.road_props
        
        row = layout.row(align=True)
        row.prop(props, "randomize", toggle=True)

        box = layout.box()
        box.label(text="Road Parameters:")
        box.prop(props, "lanes")
        box.prop(props, "lane_width")
        box.prop(props, "length")
        
        box = layout.box()
        box.label(text="Curved Road Parameters:")
        box.prop(props, "radius")
        box.prop(props, "angle")
        box.prop(props, "segments")
        
        box = layout.box()
        box.label(text="Advanced Parameters:")
        box.prop(props, "shoulder_width")
        box.prop(props, "slope")
        
        layout.separator()
        
        layout.label(text="Create Road Section:")
        layout.operator("object.create_straight_road", text="Create Straight Road")
        layout.operator("object.create_curved_road", text="Create Curved Road")
        layout.operator("object.create_intersection", text="Create Intersection")

def register():
    bpy.utils.register_class(RoadProperties)
    bpy.utils.register_class(RoadGeneratorPanel)
    bpy.utils.register_class(CreateStraightRoadOperator)
    bpy.utils.register_class(CreateCurvedRoadOperator)
    bpy.utils.register_class(CreateIntersectionOperator)
    bpy.types.Scene.road_props = bpy.props.PointerProperty(type=RoadProperties)

def unregister():
    bpy.utils.unregister_class(RoadProperties)
    bpy.utils.unregister_class(RoadGeneratorPanel)
    bpy.utils.unregister_class(CreateStraightRoadOperator)
    bpy.utils.unregister_class(CreateCurvedRoadOperator)
    bpy.utils.unregister_class(CreateIntersectionOperator)
    del bpy.types.Scene.road_props

if __name__ == "__main__":
    register()

What can be done with regards to editing the code in order to successfully create curved roads with shoulders, and T-intersections with curved shoulders at the junction?

Post edited by EightiesIsEnough on September 22
Sign In or Register to comment.
Adding to Cart…

Daz 3D is part of Tafi

Connect

DAZ Productions, Inc.
7533 S Center View Ct #4664
West Jordan, UT 84084

HELP

Contact Us

Tutorials

Help Center

Sell Your 3D Content

Affiliate Program

Documentation Center

Open Source

Consent Preferences

JOIN DAZ

Memberships

Blog

About Us

Press

Careers

Bridges

Community

In the Studio

Gallery

Forum

DAZ STORE

Shop

Freebies

Published Artists

Licensing Agreement | Terms of Service | Privacy Policy | EULA

© 2025 Daz Productions Inc. All Rights Reserved.