```python
import bpy
import math
from mathutils import Vector

# ------------------------------------------------------------
# Clean scene
# ------------------------------------------------------------
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

for datablocks in (
    bpy.data.meshes,
    bpy.data.curves,
    bpy.data.materials,
    bpy.data.cameras,
    bpy.data.lights,
):
    pass

# ------------------------------------------------------------
# Materials
# ------------------------------------------------------------
def make_material(name, color, metallic=0.0, roughness=0.45):
    mat = bpy.data.materials.new(name)
    mat.diffuse_color = (*color, 1.0)
    mat.use_nodes = True
    bsdf = mat.node_tree.nodes.get("Principled BSDF")
    bsdf.inputs["Base Color"].default_value = (*color, 1.0)
    bsdf.inputs["Metallic"].default_value = metallic
    bsdf.inputs["Roughness"].default_value = roughness
    return mat

concrete = make_material("Warm Architectural Concrete", (0.48, 0.50, 0.52), 0.0, 0.34)
column_mat = make_material("Column Concrete", (0.62, 0.64, 0.66), 0.0, 0.28)
glass = make_material("Deep Blue Window Insets", (0.025, 0.09, 0.15), 0.28, 0.17)

# ------------------------------------------------------------
# Base object: all visible tower geometry comes from one modifier
# ------------------------------------------------------------
mesh = bpy.data.meshes.new("Parametric_Tower_Source")
tower = bpy.data.objects.new("PARAMETRIC_HIGH_RISE_TOWER", mesh)
bpy.context.collection.objects.link(tower)

tower.data.materials.append(concrete)
tower.data.materials.append(column_mat)
tower.data.materials.append(glass)

# ------------------------------------------------------------
# Geometry Nodes tree
# ------------------------------------------------------------
ng = bpy.data.node_groups.new("Procedural_High_Rise_Tower", "GeometryNodeTree")

# Exactly two exposed inputs, plus one output
floors_socket = ng.interface.new_socket(
    name="Floors", in_out='INPUT', socket_type='NodeSocketInt'
)
floors_socket.default_value = 12
floors_socket.min_value = 3
floors_socket.max_value = 40
floors_socket.description = "Number of stacked floors"

taper_socket = ng.interface.new_socket(
    name="Taper", in_out='INPUT', socket_type='NodeSocketFloat'
)
taper_socket.default_value = 0.30
taper_socket.min_value = 0.0
taper_socket.max_value = 1.0
taper_socket.description = "Amount of inward taper toward the top"

ng.interface.new_socket(
    name="Geometry", in_out='OUTPUT', socket_type='NodeSocketGeometry'
)

nodes = ng.nodes
links = ng.links
nodes.clear()

group_in = nodes.new("NodeGroupInput")
group_in.location = (-1250, 100)
group_in.label = "Tower Controls"

group_out = nodes.new("NodeGroupOutput")
group_out.location = (1250, 100)

# ------------------------------------------------------------
# Floor point distribution and taper field
# ------------------------------------------------------------
line = nodes.new("GeometryNodeMeshLine")
line.location = (-1050, 500)
line.label = "One point per floor"
line.mode = 'OFFSET'
line.inputs["Offset"].default_value = (0.0, 0.0, 3.2)
links.new(group_in.outputs["Floors"], line.inputs["Count"])

index = nodes.new("GeometryNodeInputIndex")
index.location = (-1050, 250)

floors_minus_one = nodes.new("ShaderNodeMath")
floors_minus_one.operation = 'SUBTRACT'
floors_minus_one.location = (-850, 250)
floors_minus_one.inputs[1].default_value = 1.0
links.new(group_in.outputs["Floors"], floors_minus_one.inputs[0])

normalized_index = nodes.new("ShaderNodeMath")
normalized_index.operation = 'DIVIDE'
normalized_index.location = (-650, 250)
links.new(index.outputs["Index"], normalized_index.inputs[0])
links.new(floors_minus_one.outputs[0], normalized_index.inputs[1])

taper_amount = nodes.new("ShaderNodeMath")
taper_amount.operation = 'MULTIPLY'
taper_amount.location = (-450, 250)
links.new(normalized_index.outputs[0], taper_amount.inputs[0])
links.new(group_in.outputs["Taper"], taper_amount.inputs[1])

scale_factor = nodes.new("ShaderNodeMath")
scale_factor.operation = 'SUBTRACT'
scale_factor.location = (-250, 250)
scale_factor.inputs[0].default_value = 1.0
links.new(taper_amount.outputs[0], scale_factor.inputs[1])

scale_vector = nodes.new("ShaderNodeCombineXYZ")
scale_vector.location = (-50, 250)
links.new(scale_factor.outputs[0], scale_vector.inputs["X"])
links.new(scale_factor.outputs[0], scale_vector.inputs["Y"])
scale_vector.inputs["Z"].default_value = 1.0

# ------------------------------------------------------------
# Helpers for source geometry
# ------------------------------------------------------------
def cube_node(name, size, location):
    n = nodes.new("GeometryNodeMeshCube")
    n.name = name
    n.label = name
    n.location = location
    n.inputs["Size"].default_value = size
    return n

def transform_node(name, geometry, translation, location, material=None):
    t = nodes.new("GeometryNodeTransform")
    t.name = name + "_Transform"
    t.label = name
    t.location = location
    t.inputs["Translation"].default_value = translation
    links.new(geometry.outputs["Mesh"], t.inputs["Geometry"])
    if material:
        sm = nodes.new("GeometryNodeSetMaterial")
        sm.name = name + "_Material"
        sm.location = (location[0] + 170, location[1])
        sm.inputs["Material"].default_value = material
        links.new(t.outputs["Geometry"], sm.inputs["Geometry"])
        return sm
    return t

def instance_scaled(name, source, y, location):
    inst = nodes.new("GeometryNodeInstanceOnPoints")
    inst.name = name + "_Instances"
    inst.label = name
    inst.location = location
    links.new(line.outputs["Mesh"], inst.inputs["Points"])
    links.new(source.outputs["Geometry"], inst.inputs["Instance"])

    scale = nodes.new("GeometryNodeScaleInstances")
    scale.name = name + "_Taper"
    scale.location = (location[0] + 190, location[1])
    links.new(inst.outputs["Instances"], scale.inputs["Instances"])
    links.new(scale_vector.outputs["Vector"], scale.inputs["Scale"])

    realize = nodes.new("GeometryNodeRealizeInstances")
    realize.name = name + "_Realize"
    realize.location = (location[0] + 380, location[1])
    links.new(scale.outputs["Instances"], realize.inputs["Geometry"])
    return realize

# ------------------------------------------------------------
# Floor slabs
# ------------------------------------------------------------
slab_cube = cube_node("Floor Slab", (8.0, 6.0, 0.22), (-950, -150))
slab_mat = nodes.new("GeometryNodeSetMaterial")
slab_mat.location = (-760, -150)
slab_mat.name = "Floor Slab Material"
slab_mat.inputs["Material"].default_value = concrete
links.new(slab_cube.outputs["Mesh"], slab_mat.inputs["Geometry"])

slabs = instance_scaled("Stacked Floor Slabs", slab_mat, 0, (-500, -150))

# ------------------------------------------------------------
# Four-corner structural columns, repeated per floor
# ------------------------------------------------------------
column_parts = []
corner_positions = [
    (-3.82, -2.82, 0.0),
    (-3.82,  2.82, 0.0),
    ( 3.82, -2.82, 0.0),
    ( 3.82,  2.82, 0.0),
]

for i, pos in enumerate(corner_positions):
    c = cube_node("Corner Column %02d" % (i + 1), (0.38, 0.38, 3.0), (-980, -420 - i * 75))
    cm = transform_node(
        "Corner Column %02d" % (i + 1),
        c,
        pos,
        (-780, -420 - i * 75),
        column_mat
    )
    column_parts.append(cm)

column_join = nodes.new("GeometryNodeJoinGeometry")
column_join.location = (-470, -470)
column_join.label = "Four corner columns"
for part in column_parts:
    links.new(part.outputs["Geometry"], column_join.inputs["Geometry"])

columns = instance_scaled("Corner Columns", column_join, 0, (-260, -470))

# ------------------------------------------------------------
# Window inset panels around all four facades
# ------------------------------------------------------------
window_parts = []

# Front and rear facades: three vertical window bays each
for side_y, side_name in [(-2.925, "Front"), (2.925, "Rear")]:
    for j, x in enumerate((-2.65, 0.0, 2.65)):
        w = cube_node(
            "%s Window %d" % (side_name, j + 1),
            (1.95, 0.105, 2.32),
            (-990, -800 - len(window_parts) * 62)
        )
        wm = transform_node(
            "%s Window %d" % (side_name, j + 1),
            w,
            (x, side_y, 0.0),
            (-790, -800 - len(window_parts) * 62),
            glass
        )
        window_parts.append(wm)

# Side facades: two bays each
for side_x, side_name in [(-3.925, "Left"), (3.925, "Right")]:
    for j, y in enumerate((-1.42, 1.42)):
        w = cube_node(
            "%s Window %d" % (side_name, j + 1),
            (0.105, 1.72, 2.32),
            (-990, -800 - len(window_parts) * 62)
        )
        wm = transform_node(
            "%s Window %d" % (side_name, j + 1),
            w,
            (side_x, y, 0.0),
            (-790, -800 - len(window_parts) * 62),
            glass
        )
        window_parts.append(wm)

window_join = nodes.new("GeometryNodeJoinGeometry")
window_join.location = (-460, -900)
window_join.label = "Recessed window bays"
for part in window_parts:
    links.new(part.outputs["Geometry"], window_join.inputs["Geometry"])

windows = instance_scaled("Window Insets", window_join, 0, (-250, -900))

# ------------------------------------------------------------
# Final join
# ------------------------------------------------------------
final_join = nodes.new("GeometryNodeJoinGeometry")
final_join.location = (850, 100)
final_join.label = "Complete Procedural Tower"
links.new(slabs.outputs["Geometry"], final_join.inputs["Geometry"])
links.new(columns.outputs["Geometry"], final_join.inputs["Geometry"])
links.new(windows.outputs["Geometry"], final_join.inputs["Geometry"])
links.new(final_join.outputs["Geometry"], group_out.inputs["Geometry"])

# Add modifier
mod = tower.modifiers.new(name="PARAMETRIC TOWER CONTROLS", type='NODES')
mod.node_group = ng

# Explicit modifier values using interface identifiers
for item in ng.interface.items_tree:
    if item.item_type == 'SOCKET' and item.in_out == 'INPUT':
        if item.name == "Floors":
            mod[item.identifier] = 12
        elif item.name == "Taper":
            mod[item.identifier] = 0.30

# ------------------------------------------------------------
# Camera and lighting
# ------------------------------------------------------------
def look_at(obj, target):
    direction = Vector(target) - obj.location
    obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()

cam_data = bpy.data.cameras.new("Presentation Camera")
cam = bpy.data.objects.new("Presentation Camera", cam_data)
bpy.context.collection.objects.link(cam)
cam.location = (17.5, -22.0, 16.0)
cam_data.lens = 58
cam_data.sensor_width = 36
look_at(cam, (0.0, 0.0, 18.5))
bpy.context.scene.camera = cam

def add_area(name, location, energy, size, color):
    data = bpy.data.lights.new(name, type='AREA')
    data.energy = energy
    data.shape = 'DISK'
    data.size = size
    data.color = color
    obj = bpy.data.objects.new(name, data)
    bpy.context.collection.objects.link(obj)
    obj.location = location
    look_at(obj, (0.0, 0.0, 17.0))
    return obj

add_area("Key Softbox", (10, -12, 30), 1250, 8.0, (1.0, 0.91, 0.80))
add_area("Cool Fill", (-12, -8, 19), 850, 10.0, (0.72, 0.84, 1.0))
add_area("Rim Light", (5, 10, 31), 1450, 7.0, (0.78, 0.88, 1.0))

# Subtle ambient world
world = bpy.data.worlds.new("Architectural Studio World")
bpy.context.scene.world = world
world.use_nodes = True
bg = world.node_tree.nodes.get("Background")
bg.inputs["Color"].default_value = (0.025, 0.035, 0.05, 1.0)
bg.inputs["Strength"].default_value = 0.28

# ------------------------------------------------------------
# Render and scene presentation settings
# ------------------------------------------------------------
scene = bpy.context.scene
scene.render.engine = 'BLENDER_EEVEE_NEXT'
scene.render.resolution_x = 1536
scene.render.resolution_y = 1536
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.filepath = "//parametric_tower_preview.png"

scene.render.film_transparent = False
scene.view_settings.look = 'AgX - Medium High Contrast'
scene.view_settings.exposure = 0.0
scene.view_settings.gamma = 1.0

# Clean object display settings
tower.color = (0.42, 0.46, 0.52, 1.0)
tower.show_name = True
tower["Design"] = "Single Geometry Nodes modifier: slabs, columns, window insets"
tower["Controls"] = "Floors and Taper"

# Select tower for convenient inspection
bpy.context.view_layer.objects.active = tower
tower.select_set(True)

# Save complete scene
bpy.ops.wm.save_as_mainfile(filepath="//parametric_high_rise_tower.blend")
```