"""
Blender SVG Import Script v6 - Hierarchical SVG Import with Enhanced Features
Imports pre-processed SVG files with expanded strokes.
Handles staircase instancing and multi-floor buildings.

New in v6:
- Internal wall support (SVG line elements as rectangular prisms)
- Internal door/window detection with angle calculation from line coordinates
- External staircase cap support (staircase_external_cap collection)
- Internal staircase cap support (staircase_internal_cap collection)
- Duplicate instance detection (prevents overlapping instances at same location)
- Negative angle support for staircases
- Skip floor0_wall (building_offset is the correct floor 0 boundary)
- Improved boolean operations for staircases (cuts through all floors + roof)
- Updated FLOOR_THICKNESS to 0.2m (total floor height 4.2m)

Required Collections (create before running):
- tree: Tree objects to instance
- door: Door objects (with optional _subtractive geometry)
- window: Window objects (with optional _subtractive geometry)
- staircase_internal: Internal staircase objects (with optional _sub for subtractive geometry)
- staircase_internal_cap: Cap objects placed at top of internal staircase stacks
- staircase_external: External staircase objects (with optional _subtractive geometry)
- staircase_external_cap: Cap objects placed at top of external staircase stacks

Usage in Blender:
1. Create the required collections above with your objects
2. Open Blender Scripting tab
3. Open this file or paste the code
4. Run the script (Alt+P)
"""

import bpy
import xml.etree.ElementTree as ET
import math
from pathlib import Path
import re
import random


# ============================================================================
# SVG PATH PARSING
# ============================================================================

class Point:
    """Simple 2D point"""
    def __init__(self, x, y):
        self.x = float(x)
        self.y = float(y)

    def __repr__(self):
        return f"Point({self.x}, {self.y})"


def parse_svg_path_to_points(path_data, num_samples=100):
    """
    Parse SVG path data and return list of point lists (one list per closed loop)
    Supports basic M, L, H, V, C, Q, Z commands
    Returns: List of point lists [[loop1_points], [loop2_points], ...]
    """
    all_loops = []  # List of separate closed loops
    current_loop = []  # Current loop being built
    current_pos = Point(0, 0)
    path_start = Point(0, 0)

    # Simple regex-based parser for common SVG commands
    commands = re.findall(r'([MLHVCQZmlhvcqz])([^MLHVCQZmlhvcqz]*)', path_data)

    for cmd, args in commands:
        args = args.strip()
        if not args and cmd.upper() != 'Z':
            continue

        # Parse numeric arguments
        coords = [float(x) for x in re.findall(r'-?\d*\.?\d+', args)]

        if cmd in 'Mm':  # Move to
            # If we have an existing loop, save it before starting a new one
            if current_loop:
                all_loops.append(current_loop)
                current_loop = []

            if cmd == 'M':  # Absolute
                current_pos = Point(coords[0], coords[1])
            else:  # Relative
                current_pos = Point(current_pos.x + coords[0], current_pos.y + coords[1])
            path_start = Point(current_pos.x, current_pos.y)
            current_loop.append(current_pos)

        elif cmd in 'Ll':  # Line to
            if cmd == 'L':  # Absolute
                current_pos = Point(coords[0], coords[1])
            else:  # Relative
                current_pos = Point(current_pos.x + coords[0], current_pos.y + coords[1])
            current_loop.append(current_pos)

        elif cmd in 'Hh':  # Horizontal line
            if cmd == 'H':
                current_pos = Point(coords[0], current_pos.y)
            else:
                current_pos = Point(current_pos.x + coords[0], current_pos.y)
            current_loop.append(current_pos)

        elif cmd in 'Vv':  # Vertical line
            if cmd == 'V':
                current_pos = Point(current_pos.x, coords[0])
            else:
                current_pos = Point(current_pos.x, current_pos.y + coords[0])
            current_loop.append(current_pos)

        elif cmd in 'Cc':  # Cubic Bezier
            # Sample bezier curve
            for i in range(0, len(coords), 6):
                if i + 5 < len(coords):
                    if cmd == 'C':
                        p1 = Point(coords[i], coords[i+1])
                        p2 = Point(coords[i+2], coords[i+3])
                        p3 = Point(coords[i+4], coords[i+5])
                    else:
                        p1 = Point(current_pos.x + coords[i], current_pos.y + coords[i+1])
                        p2 = Point(current_pos.x + coords[i+2], current_pos.y + coords[i+3])
                        p3 = Point(current_pos.x + coords[i+4], current_pos.y + coords[i+5])

                    # Sample the bezier curve
                    for t in [x / 10.0 for x in range(1, 11)]:
                        # Cubic bezier formula
                        t1 = 1 - t
                        x = (t1**3 * current_pos.x +
                             3 * t1**2 * t * p1.x +
                             3 * t1 * t**2 * p2.x +
                             t**3 * p3.x)
                        y = (t1**3 * current_pos.y +
                             3 * t1**2 * t * p1.y +
                             3 * t1 * t**2 * p2.y +
                             t**3 * p3.y)
                        current_loop.append(Point(x, y))

                    current_pos = p3

        elif cmd in 'Zz':  # Close path
            if current_loop and (current_loop[-1].x != path_start.x or current_loop[-1].y != path_start.y):
                current_loop.append(path_start)
            current_pos = path_start

    # Add the final loop if it exists
    if current_loop:
        all_loops.append(current_loop)

    # If only one loop, return it in old format for backwards compatibility
    # Otherwise return list of loops
    return all_loops if len(all_loops) > 1 else (all_loops[0] if all_loops else [])


# Configuration
# Automatically find the latest svgoutput_*.svg file in Downloads
import glob
import os

DOWNLOADS_PATH = r"C:\Users\Reference\Downloads"

# Find all svgoutput_*.svg files
svg_files = glob.glob(os.path.join(DOWNLOADS_PATH, "svgoutput_*.svg"))

if svg_files:
    # Get the latest file by modification time
    SVG_PATH = max(svg_files, key=os.path.getmtime)
    print(f"Using latest SVG file: {SVG_PATH}")
else:
    # Fallback
    SVG_PATH = r"C:\Users\Reference\Downloads\svgoutput_07112025_004232.svg"
    print(f"No svgoutput_*.svg files found, using: {SVG_PATH}")

# Scale factor: 1 SVG unit = 1 meter in Blender
SVG_SCALE = 1.0  # Adjust this if your SVG units need different scaling
SVG_Y_FLIP = -1.0  # Flip Y axis (SVG has Y down, Blender has Y up)

# Path simplification threshold (in meters after scaling)
MERGE_THRESHOLD = 0.2  # Merge points within 0.2 meters

# Building extrusion settings
WALL_HEIGHT = 4.0  # Height of walls within a floor (meters)
FLOOR_THICKNESS = 0.2  # Thickness of floor slab (meters)
FLOOR_HEIGHT = WALL_HEIGHT + FLOOR_THICKNESS  # Total height per floor: 4.2m
SIDEWALK_HEIGHT = 0.2  # Height of sidewalks/parks in meters
PARK_PATH_HEIGHT = 0.15  # Height of park paths in meters
ROOF_THICKNESS = 0.2  # Thickness of roof elements in meters


def create_collection(name, parent=None):
    """Create or get a collection, optionally parented to another collection"""
    if name in bpy.data.collections:
        collection = bpy.data.collections[name]
    else:
        collection = bpy.data.collections.new(name)

    # Link to parent or scene
    if parent:
        if collection.name not in parent.children:
            parent.children.link(collection)
    else:
        if collection.name not in bpy.context.scene.collection.children:
            bpy.context.scene.collection.children.link(collection)

    return collection


def parse_svg_color(color_str):
    """
    Parse SVG color to RGB tuple (0-1 range)
    Supports: #RRGGBB, rgb(r,g,b), named colors
    """
    if not color_str or color_str.lower() == 'none':
        return None

    color_str = color_str.strip().lower()

    # Hex color: #RRGGBB
    if color_str.startswith('#'):
        hex_color = color_str.lstrip('#')
        if len(hex_color) == 6:
            r = int(hex_color[0:2], 16) / 255.0
            g = int(hex_color[2:4], 16) / 255.0
            b = int(hex_color[4:6], 16) / 255.0
            return (r, g, b)
        elif len(hex_color) == 3:
            r = int(hex_color[0] * 2, 16) / 255.0
            g = int(hex_color[1] * 2, 16) / 255.0
            b = int(hex_color[2] * 2, 16) / 255.0
            return (r, g, b)

    # rgb(r, g, b) format
    if color_str.startswith('rgb'):
        match = re.search(r'rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)', color_str)
        if match:
            r = int(match.group(1)) / 255.0
            g = int(match.group(2)) / 255.0
            b = int(match.group(3)) / 255.0
            return (r, g, b)

    # Common named colors
    named_colors = {
        'black': (0, 0, 0),
        'white': (1, 1, 1),
        'red': (1, 0, 0),
        'green': (0, 1, 0),
        'blue': (0, 0, 1),
        'yellow': (1, 1, 0),
        'cyan': (0, 1, 1),
        'magenta': (1, 0, 1),
        'gray': (0.5, 0.5, 0.5),
        'grey': (0.5, 0.5, 0.5),
        'orange': (1, 0.647, 0),
        'purple': (0.5, 0, 0.5),
    }

    if color_str in named_colors:
        return named_colors[color_str]

    return None


def get_or_create_material(color_rgb, material_type='fill'):
    """
    Get or create a material based on RGB color
    Material name: Color_RRR_GGG_BBB_type
    """
    if not color_rgb:
        return None

    r, g, b = color_rgb
    # Create material name
    mat_name = f"Color_{int(r*255):03d}_{int(g*255):03d}_{int(b*255):03d}_{material_type}"

    # Check if material already exists
    if mat_name in bpy.data.materials:
        return bpy.data.materials[mat_name]

    # Create new material
    mat = bpy.data.materials.new(name=mat_name)
    mat.use_nodes = True

    # Get the principled BSDF node
    nodes = mat.node_tree.nodes
    bsdf = nodes.get('Principled BSDF')

    if bsdf:
        # Set base color
        bsdf.inputs['Base Color'].default_value = (r, g, b, 1.0)

        # Optional: adjust material properties based on type
        if material_type == 'stroke':
            bsdf.inputs['Roughness'].default_value = 0.5
        else:  # fill
            bsdf.inputs['Roughness'].default_value = 0.7

    return mat


def parse_svg_transform(transform_str):
    """Parse SVG transform attribute (basic matrix/translate support)"""
    if not transform_str:
        return (0, 0), 0

    # This is simplified - full SVG transform parsing is more complex
    # For now, return defaults
    return (0, 0), 0


def calculate_polygon_centroid(points):
    """Calculate the centroid (center) of a polygon from its points"""
    if not points:
        return None

    sum_x = sum(p.x for p in points)
    sum_y = sum(p.y for p in points)
    count = len(points)

    return Point(sum_x / count, sum_y / count)


def calculate_polygon_area(points):
    """
    Calculate the area of a polygon using the shoelace formula
    Returns the absolute area value
    """
    if not points or len(points) < 3:
        return 0.0

    area = 0.0
    n = len(points)

    for i in range(n):
        j = (i + 1) % n
        area += points[i].x * points[j].y
        area -= points[j].x * points[i].y

    return abs(area) / 2.0


def simplify_points(points, threshold):
    """
    Simplify a list of points by merging points within threshold distance

    Args:
        points: List of Point objects
        threshold: Distance threshold for merging points

    Returns:
        List of simplified Point objects
    """
    if not points or len(points) < 2:
        return points

    simplified = [points[0]]  # Always keep first point

    for i in range(1, len(points)):
        current = points[i]
        last_kept = simplified[-1]

        # Calculate distance from last kept point
        dx = current.x - last_kept.x
        dy = current.y - last_kept.y
        distance = math.sqrt(dx * dx + dy * dy)

        # Only add point if it's far enough from the last kept point
        if distance > threshold:
            simplified.append(current)

    # Always keep the last point if it's different from what we have
    if len(simplified) > 1:
        last = points[-1]
        last_kept = simplified[-1]
        dx = last.x - last_kept.x
        dy = last.y - last_kept.y
        distance = math.sqrt(dx * dx + dy * dy)
        if distance > threshold:
            simplified.append(last)

    return simplified


def get_element_centroid(element, scale=1.0):
    """
    Get the centroid of any SVG element (circle, path, etc.)
    Returns Point or None
    """
    element_id = element.get('id')
    tag = element.tag.split('}')[-1]  # Remove namespace

    # Handle circle elements
    if tag == 'circle':
        cx = float(element.get('cx', 0))
        cy = float(element.get('cy', 0))
        return Point(cx * scale, cy * scale * SVG_Y_FLIP)

    # Handle rectangle elements
    if tag == 'rect':
        x = float(element.get('x', 0))
        y = float(element.get('y', 0))
        width = float(element.get('width', 0))
        height = float(element.get('height', 0))
        # Calculate center of rectangle
        cx = x + width / 2.0
        cy = y + height / 2.0
        return Point(cx * scale, cy * scale * SVG_Y_FLIP)

    # Handle path elements
    if tag == 'path':
        path_data = element.get('d')
        if path_data:
            points = parse_svg_path_to_points(path_data, num_samples=100)
            if points:
                centroid = calculate_polygon_centroid(points)
                if centroid:
                    return Point(centroid.x * scale, centroid.y * scale * SVG_Y_FLIP)

    return None


def create_rectangular_prism_along_line(p1, p2, width, height, name="Wall"):
    """
    Create a rectangular prism mesh extruded along a line segment

    Args:
        p1: Start point (x, y, z) tuple
        p2: End point (x, y, z) tuple
        width: Width/thickness of the prism (perpendicular to line)
        height: Height of the prism (Z direction)
        name: Name for the mesh object

    Returns:
        Blender mesh object
    """
    import math

    # Calculate line direction vector (in XY plane)
    dx = p2[0] - p1[0]
    dy = p2[1] - p1[1]
    length = math.sqrt(dx * dx + dy * dy)

    if length < 0.0001:
        # Degenerate line, return None
        return None

    # Normalized direction vector
    dir_x = dx / length
    dir_y = dy / length

    # Perpendicular vector (rotated 90 degrees)
    perp_x = -dir_y
    perp_y = dir_x

    # Half width offset
    half_width = width / 2.0

    # Calculate the 8 vertices of the rectangular prism
    # Bottom face (4 vertices)
    verts = [
        # Start edge (p1)
        (p1[0] - perp_x * half_width, p1[1] - perp_y * half_width, p1[2]),
        (p1[0] + perp_x * half_width, p1[1] + perp_y * half_width, p1[2]),
        # End edge (p2)
        (p2[0] + perp_x * half_width, p2[1] + perp_y * half_width, p2[2]),
        (p2[0] - perp_x * half_width, p2[1] - perp_y * half_width, p2[2]),
        # Top face (4 vertices)
        # Start edge (p1) + height
        (p1[0] - perp_x * half_width, p1[1] - perp_y * half_width, p1[2] + height),
        (p1[0] + perp_x * half_width, p1[1] + perp_y * half_width, p1[2] + height),
        # End edge (p2) + height
        (p2[0] + perp_x * half_width, p2[1] + perp_y * half_width, p2[2] + height),
        (p2[0] - perp_x * half_width, p2[1] - perp_y * half_width, p2[2] + height),
    ]

    # Define faces (quads)
    faces = [
        # Bottom face
        (0, 1, 2, 3),
        # Top face
        (4, 5, 6, 7),
        # Side faces
        (0, 1, 5, 4),  # Front
        (1, 2, 6, 5),  # Right
        (2, 3, 7, 6),  # Back
        (3, 0, 4, 7),  # Left
    ]

    # Create mesh
    mesh = bpy.data.meshes.new(name)
    mesh.from_pydata(verts, [], faces)
    mesh.update()

    # Create object
    obj = bpy.data.objects.new(name, mesh)

    return obj


def import_svg_line_as_curve(element, collection, scale=1.0):
    """
    Import an SVG line element as a Blender mesh (for internal walls)
    Creates a rectangular prism along the line segment

    Args:
        element: SVG line element to import
        collection: Blender collection to add object to
        scale: Scale factor to apply (1.0 = 1 SVG unit = 1 Blender unit/meter)

    Returns:
        tuple: (object, centroid) - The created object and its centroid point (scaled)
    """
    element_id = element.get('id')

    # Get line coordinates
    x1 = float(element.get('x1', 0))
    y1 = float(element.get('y1', 0))
    x2 = float(element.get('x2', 0))
    y2 = float(element.get('y2', 0))

    # Get stroke width (this will be the thickness of the wall)
    stroke_width = float(element.get('stroke-width', 1.0))

    # Calculate centroid
    centroid_x = (x1 + x2) / 2.0
    centroid_y = (y1 + y2) / 2.0
    centroid = Point(centroid_x * scale, centroid_y * scale * SVG_Y_FLIP)

    # Check if this is a door, window, or staircase marker - if so, skip geometry creation
    # These will be handled later as collection instances
    if parse_door_window_id(element_id):
        # This is a door or window marker, return centroid only
        return None, centroid

    if parse_staircase_id(element_id):
        # This is a staircase marker, return centroid only
        return None, centroid

    # Check if this is an internal door/window (embedded in wall line)
    # Pattern: tile_X_lotY_building_wall_N_door or tile_X_lotY_building_wall_N_window
    # or with floor: tile_X_lotY_building_floorN_wall_M_door
    if '_door' in element_id or '_window' in element_id:
        # This is an internal door/window marker, return centroid only
        # The angle will be calculated from line coordinates later
        return None, centroid

    # Skip floor0_wall - building_offset already represents the floor 0 external wall
    import re
    if re.search(r'_floor0_wall$', element_id):
        # print(f"  Skipping {element_id} (building_offset is the floor 0 external wall)")
        return None, centroid

    # Check if this is a wall element - determine floor number and extrusion
    floor_number = parse_floor_number(element_id)
    z_position = 0
    wall_height = WALL_HEIGHT

    if '_wall' in element_id:
        # Wall element - determine Z position based on floor
        if floor_number is not None:
            # Floor-specific wall
            z_position = floor_number * FLOOR_HEIGHT
            print(f"    Internal wall: floor {floor_number} at Z={z_position}m to {z_position + wall_height}m")
        else:
            # Generic wall, assume floor 0
            print(f"    Internal wall: extruded {wall_height}m")
    else:
        # Non-wall line, default to small height
        wall_height = 0.1

    # Create start and end points for the prism (with scale and Y-flip)
    p1 = (x1 * scale, y1 * scale * SVG_Y_FLIP, z_position)
    p2 = (x2 * scale, y2 * scale * SVG_Y_FLIP, z_position)

    # Create rectangular prism mesh
    # Wall thickness is half the stroke width for internal walls
    wall_thickness = (stroke_width * scale) / 2.0
    obj = create_rectangular_prism_along_line(
        p1, p2,
        width=wall_thickness,
        height=wall_height,
        name=element_id
    )

    if not obj:
        print(f"    Warning: Failed to create wall mesh for {element_id}")
        return None, centroid

    # Link to collection
    collection.objects.link(obj)

    # Assign material based on stroke color
    stroke_color = element.get('stroke')
    if stroke_color:
        color_rgb = parse_svg_color(stroke_color)
        if color_rgb:
            material = get_or_create_material(color_rgb, 'stroke')
            if material:
                if len(obj.data.materials) == 0:
                    obj.data.materials.append(material)
                else:
                    obj.data.materials[0] = material

    return obj, centroid


def import_svg_element_as_curve(element, collection, scale=1.0):
    """
    Import an SVG element as a Blender curve
    Supports path elements (filled polygons) and line elements (internal walls)

    Args:
        element: SVG element to import
        collection: Blender collection to add object to
        scale: Scale factor to apply (1.0 = 1 SVG unit = 1 Blender unit/meter)

    Returns:
        tuple: (object, centroid) - The created object and its centroid point (scaled)
    """
    element_id = element.get('id')
    tag = element.tag.split('}')[-1]  # Remove namespace

    # Handle line elements (internal walls)
    if tag == 'line':
        return import_svg_line_as_curve(element, collection, scale)

    # Handle rect elements (pillars, etc.) by converting to path
    if tag == 'rect':
        # Extract rectangle attributes
        x = float(element.get('x', 0))
        y = float(element.get('y', 0))
        width = float(element.get('width', 0))
        height = float(element.get('height', 0))

        # Convert rectangle to a closed path (clockwise from top-left)
        rect_path = f"M {x} {y} L {x + width} {y} L {x + width} {y + height} L {x} {y + height} Z"

        # Create a new path element with same namespace and attributes
        import xml.etree.ElementTree as ET
        namespace = element.tag.split('}')[0] + '}' if '}' in element.tag else ''
        path_element = ET.Element(f"{namespace}path")

        # Copy all attributes from rect to path
        for key, value in element.attrib.items():
            path_element.set(key, value)

        # Set the path data
        path_element.set('d', rect_path)

        # Process as a path element
        return import_svg_element_as_curve(path_element, collection, scale)

    # Only import path elements as geometry
    if tag != 'path':
        # For non-path elements (circles, etc.), just return centroid without importing
        centroid = get_element_centroid(element, scale)
        return None, centroid

    # Skip paths that end with _park or _building (without suffix like _offset, _roof, etc.)
    # Also skip floor0_fill since building_roof is the offset version of it
    if element_id:
        import re
        # Match _park or _building at the end of the ID (no underscore after)
        if re.search(r'_(park|building)$', element_id):
            # print(f"  Skipping {element_id} (base park/building without suffix)")
            return None, None
        # Skip floor0_fill (building_roof is the offset version)
        if re.search(r'_floor0_fill$', element_id):
            # print(f"  Skipping {element_id} (building_roof is the offset version)")
            return None, None
        # Skip floor0_wall (building_offset is the floor 0 external wall)
        if re.search(r'_floor0_wall$', element_id):
            # print(f"  Skipping {element_id} (building_offset is the floor 0 external wall)")
            return None, None

    path_data = element.get('d')
    if not path_data:
        # print(f"  Skipping {element_id} (no path data)")
        return None, None

    # print(f"  Importing filled path: {element_id}")

    # Parse path to points (may return a single loop or multiple loops)
    parsed_result = parse_svg_path_to_points(path_data, num_samples=100)

    # Handle both single loop and multiple loops
    if not parsed_result:
        # print(f"    Warning: No points parsed")
        return None, None

    # Check if we got multiple loops or a single loop
    is_multiple_loops = isinstance(parsed_result[0], list)
    loops = parsed_result if is_multiple_loops else [parsed_result]

    # print(f"    Path contains {len(loops)} loop(s)")

    # Check if this is a small building that should be solid (no inner loops)
    if 'building' in element_id and len(loops) > 1:
        # Calculate area of outer loop (first loop)
        outer_loop = loops[0]
        area = calculate_polygon_area(outer_loop)

        if area < 100:
            # print(f"    Small building (area={area:.1f} < 100), using solid core (outer contour only)")
            loops = [outer_loop]  # Only use outer loop

    # Calculate centroid from first (outer) loop before scaling
    first_loop = loops[0]
    if len(first_loop) < 3:
        # print(f"    Warning: Not enough points in first loop")
        return None, None

    centroid = calculate_polygon_centroid(first_loop)
    if centroid:
        # Apply scale and Y-flip to centroid
        centroid = Point(centroid.x * scale, centroid.y * scale * SVG_Y_FLIP)

    # Simplify all loops by merging nearby points (before scaling)
    threshold_svg_units = MERGE_THRESHOLD / scale
    simplified_loops = []
    total_original = sum(len(loop) for loop in loops)

    for loop_idx, loop in enumerate(loops):
        original_count = len(loop)
        simplified_loop = simplify_points(loop, threshold_svg_units)
        simplified_count = len(simplified_loop)

        if simplified_count < 3:
            # print(f"    Warning: Loop {loop_idx} has too few points after simplification")
            continue

        simplified_loops.append(simplified_loop)

    total_simplified = sum(len(loop) for loop in simplified_loops)
    if total_simplified < total_original:
        # print(f"    Simplified: {total_original} → {total_simplified} points across {len(simplified_loops)} loop(s)")
        pass

    if not simplified_loops:
        # print(f"    Warning: No valid loops after simplification")
        return None, None

    # Create 2D curve
    curve_data = bpy.data.curves.new(name=element_id, type='CURVE')
    curve_data.dimensions = '2D'

    # Create a spline for each loop
    for loop_idx, loop_points in enumerate(simplified_loops):
        spline = curve_data.splines.new('POLY')
        spline.points.add(len(loop_points) - 1)

        # Apply scale factor and Y-flip when setting point coordinates
        for i, p in enumerate(loop_points):
            spline.points[i].co = (p.x * scale, p.y * scale * SVG_Y_FLIP, 0, 1)

        # All loops should be closed
        spline.use_cyclic_u = True

    # Set fill mode to handle holes properly
    curve_data.fill_mode = 'BOTH'

    # Create object
    obj = bpy.data.objects.new(element_id, curve_data)
    collection.objects.link(obj)

    # Check if this element needs extrusion or special positioning
    extrude_height = None
    z_position = 0  # Default Z position

    # Check for multi-floor building - this applies to ALL building elements
    floor_number = parse_floor_number(element_id)

    if '_pillar_' in element_id:
        # Pillar element - extrude from floor to ceiling (check FIRST before 'building' check)
        if floor_number is not None:
            # Floor-specific pillar
            extrude_height = WALL_HEIGHT
            z_position = floor_number * FLOOR_HEIGHT
            print(f"    Pillar: floor {floor_number} at Z={z_position}m to {z_position + extrude_height}m")
        else:
            # Generic pillar, assume floor 0
            extrude_height = WALL_HEIGHT
            print(f"    Pillar: extruded {extrude_height}m")
    elif '_roof' in element_id:
        # Roof element - extrude roof thickness and position at top of building
        extrude_height = ROOF_THICKNESS
        if floor_number is not None:
            # Position roof at the top of this floor (above the floor slab)
            z_position = floor_number * FLOOR_HEIGHT + WALL_HEIGHT + FLOOR_THICKNESS
            # print(f"    Extruded {extrude_height}m at Z={z_position}m (floor {floor_number} roof)")
        else:
            z_position = WALL_HEIGHT + FLOOR_THICKNESS
            # print(f"    Extruded {extrude_height}m at Z={z_position}m (roof)")
    elif '_grass' in element_id or 'grass_' in element_id:
        # Grass element - position slightly above ground
        z_position = 0.1
        # print(f"    Positioned at Z={z_position}m (grass)")
    elif '_wall' in element_id:
        # Wall element - extrude to wall height
        if floor_number is not None:
            # Floor-specific wall (including floor 0)
            extrude_height = WALL_HEIGHT
            z_position = floor_number * FLOOR_HEIGHT
            print(f"    Wall: floor {floor_number} at Z={z_position}m to {z_position + extrude_height}m")
        else:
            # Generic wall, assume floor 0
            extrude_height = WALL_HEIGHT
            # print(f"    Extruded {extrude_height}m (wall)")
    elif '_offset' in element_id or 'building' in element_id:
        # Building elements (with or without _offset)
        if 'building' in element_id:
            if floor_number is not None:
                # This is a floor slab element (ceiling)
                # Floor slabs sit ON TOP of walls, not at the base
                # Floor 0 slab: Z = 4.0m to 4.1m (top of floor 0 walls)
                # Floor 1 slab: Z = 8.1m to 8.2m (top of floor 1 walls)
                # Floor 2 slab: Z = 12.2m to 12.3m (top of floor 2 walls)
                # Floor N slab: Z = N*4.1 + 4.0 to N*4.1 + 4.1
                extrude_height = FLOOR_THICKNESS
                z_position = floor_number * FLOOR_HEIGHT + WALL_HEIGHT
                # print(f"    Floor slab: Extruded {extrude_height}m at Z={z_position}m to {z_position + extrude_height}m (floor {floor_number} slab)")
            else:
                # Building footprint without floor number - extrude full floor height
                extrude_height = FLOOR_HEIGHT
                # print(f"    Extruded {extrude_height}m (building floor 0)")
        elif 'park' in element_id:
            # Park/sidewalk - extrude to sidewalk height
            extrude_height = SIDEWALK_HEIGHT
            # print(f"    Extruded {extrude_height}m (park/sidewalk)")
        else:
            # Default offset - no extrusion specified in ID
            extrude_height = FLOOR_HEIGHT
            # print(f"    Extruded {extrude_height}m (default)")
    elif 'park_path' in element_id:
        # Park path - extrude to park path height
        extrude_height = PARK_PATH_HEIGHT
        # print(f"    Extruded {extrude_height}m (park path)")
    elif 'source_tile' in element_id:
        # Source tile - position slightly below ground
        z_position = -0.01
        # print(f"    Positioned at Z={z_position}m (source tile)")
    elif floor_number is not None:
        # ANY element with a floor number should be a floor slab
        # Position at top of that floor's walls
        extrude_height = FLOOR_THICKNESS
        z_position = floor_number * FLOOR_HEIGHT + WALL_HEIGHT
        # print(f"    Floor slab at Z={z_position}m to {z_position + FLOOR_THICKNESS}m (floor {floor_number})")

    # Apply extrusion and positioning
    if extrude_height is not None:
        curve_data.extrude = extrude_height / 2.0
        # Position at z_position (if set) plus half the extrusion height
        obj.location = (0, 0, z_position + extrude_height / 2.0)
    else:
        # Set Z position for non-extruded objects
        obj.location = (0, 0, z_position)

    # Assign material based on fill color
    fill_color = element.get('fill')
    if fill_color:
        color_rgb = parse_svg_color(fill_color)
        if color_rgb:
            material = get_or_create_material(color_rgb, 'fill')
            if material:
                if len(obj.data.materials) == 0:
                    obj.data.materials.append(material)
                else:
                    obj.data.materials[0] = material
                # print(f"    Material: {material.name}")

    # print(f"  ✓ Imported filled: {element_id}")
    return obj, centroid


def instance_tree_at_location(location, tree_collection, target_collection, tree_name):
    """
    Create a collection instance of trees at the specified location with random rotation

    Args:
        location: Point with x, y coordinates
        tree_collection: Source collection containing tree objects
        target_collection: Collection to place the instance in
        tree_name: Name for the instance object
    """
    if not tree_collection:
        return None

    # Create an empty object at the tree location
    empty = bpy.data.objects.new(tree_name, None)
    empty.location = (location.x, location.y, 0)
    empty.instance_type = 'COLLECTION'
    empty.instance_collection = tree_collection

    # Apply random Z-axis rotation (0-360 degrees)
    random_angle = random.uniform(0, 2 * math.pi)
    empty.rotation_euler = (0, 0, random_angle)

    target_collection.objects.link(empty)

    # print(f"    ✓ Placed tree instance at ({location.x:.2f}, {location.y:.2f}) rotation: {math.degrees(random_angle):.0f}°")
    return empty


def convert_curve_to_mesh(obj):
    """
    Convert a curve object to mesh if needed (in-place)
    Returns the same object (now with mesh data) or original object
    """
    if obj.type != 'CURVE':
        return obj

    # Get all collections this object is in
    collections = list(obj.users_collection)

    # Store object properties
    obj_name = obj.name
    obj_location = obj.location.copy()
    obj_rotation = obj.rotation_euler.copy()
    obj_scale = obj.scale.copy()

    # Store materials
    materials = [mat for mat in obj.data.materials]

    # Convert curve to mesh data
    mesh_data = bpy.data.meshes.new_from_object(obj)
    mesh_data.name = obj.data.name

    # Store old curve data reference
    old_curve_data = obj.data

    # Create new mesh object
    mesh_obj = bpy.data.objects.new(obj_name, mesh_data)
    mesh_obj.location = obj_location
    mesh_obj.rotation_euler = obj_rotation
    mesh_obj.scale = obj_scale

    # Copy materials to new mesh
    for mat in materials:
        mesh_obj.data.materials.append(mat)

    # Link to same collections
    for col in collections:
        col.objects.link(mesh_obj)
        col.objects.unlink(obj)

    # Remove old object and curve data
    bpy.data.objects.remove(obj)
    bpy.data.curves.remove(old_curve_data)

    return mesh_obj


def instance_object_at_location(location, angle, collection, target_collection, obj_name, building_obj=None, subtractive_collection=None, lot_objects=None, building_key=None, z_offset=0):
    """
    Create a collection instance at the specified location with rotation
    Also handles subtractive geometry for doors/windows

    Args:
        location: Point with x, y coordinates
        angle: Rotation angle in degrees
        collection: Source collection containing objects
        target_collection: Collection to place the instance in
        obj_name: Name for the instance object
        building_obj: Optional building object to apply boolean subtraction to
        subtractive_collection: Collection to place subtractive geometry in
        lot_objects: Optional dict to track converted objects (for updating building reference)
        building_key: Optional key to update in lot_objects if building is converted to mesh
        z_offset: Z offset to apply to both instance and subtractive geometry (for multi-floor)

    Returns:
        tuple: (instance_empty, subtractive_obj) - The instance and any subtractive geometry created
    """
    if not collection:
        return None, None

    # Check for duplicate instances at the same location (within tolerance)
    POSITION_TOLERANCE = 0.01  # 1cm tolerance for position matching
    target_pos = (location.x, location.y, z_offset)

    for existing_obj in target_collection.objects:
        # Only check collection instances from the same source collection
        if existing_obj.instance_type == 'COLLECTION' and existing_obj.instance_collection == collection:
            existing_pos = existing_obj.location
            # Calculate distance in XYZ
            dx = existing_pos[0] - target_pos[0]
            dy = existing_pos[1] - target_pos[1]
            dz = existing_pos[2] - target_pos[2]
            distance = math.sqrt(dx*dx + dy*dy + dz*dz)

            if distance < POSITION_TOLERANCE:
                print(f"    ⚠ Skipping duplicate instance at ({location.x:.2f}, {location.y:.2f}, {z_offset:.2f}) - already exists as {existing_obj.name}")
                return None, None

    # Create an empty object at the location for the visible instance
    empty = bpy.data.objects.new(obj_name, None)
    empty.location = (location.x, location.y, z_offset)
    empty.instance_type = 'COLLECTION'
    empty.instance_collection = collection

    # Apply rotation (convert degrees to radians, rotate around Z axis)
    # Negate angle due to Y-axis flip
    empty.rotation_euler = (0, 0, math.radians(-angle))

    target_collection.objects.link(empty)

    # print(f"    ✓ Placed {collection.name} instance at ({location.x:.2f}, {location.y:.2f}, {z_offset:.2f}) angle: {angle}° (flipped: {-angle}°)")

    # Look for subtractive objects in the collection
    subtractive_obj = None
    for obj in collection.objects:
        # Check if this object is marked as subtractive (e.g., window_sub, door_sub)
        if any(keyword in obj.name.lower() for keyword in ['_subtractive', '_remove', '_sub']):
            print(f"    → Found subtractive: {obj.name} in {collection.name}")

            # Duplicate the subtractive object
            subtractive_copy = obj.copy()
            if obj.data:
                subtractive_copy.data = obj.data.copy()

            # Rename to include instance info
            subtractive_copy.name = f"{obj_name}_subtractive"

            # Apply the same transform as the instance (including Z offset for multi-floor)
            # Negate angle due to Y-axis flip
            subtractive_copy.location = (location.x, location.y, z_offset)
            subtractive_copy.rotation_euler = (0, 0, math.radians(-angle))

            # print(f"      → Positioned subtractive geometry at Z={z_offset}m (floor level)")

            # Add to subtractive collection if provided, otherwise target collection
            if subtractive_collection:
                subtractive_collection.objects.link(subtractive_copy)
            else:
                target_collection.objects.link(subtractive_copy)

            # Apply boolean modifier to building if provided
            if building_obj:
                # Check if building_obj still exists (hasn't been deleted)
                try:
                    _ = building_obj.name  # Test if object is still valid
                except ReferenceError:
                    print(f"      ⚠ Warning: Building object was deleted, skipping boolean")
                    subtractive_obj = subtractive_copy
                    continue

                # Check if building is a curve - if so, convert to mesh first
                original_type = building_obj.type
                new_building_obj = convert_curve_to_mesh(building_obj)

                if original_type == 'CURVE':
                    # print(f"      → Converted building curve to mesh for boolean operations")
                    # Update the lot_objects dict if provided so the calling code tracks the new mesh object
                    if lot_objects is not None and building_key:
                        # Update the specific key we were given
                        lot_objects[building_key] = new_building_obj
                        # print(f"      → Updated {building_key} reference to mesh object")
                    elif lot_objects is not None:
                        # Fallback: search for a matching key
                        key_to_update = None
                        for key, obj_ref in list(lot_objects.items()):
                            # Check if this was the building object
                            if 'building_offset' in key or 'wall' in key:
                                # Update to new mesh object
                                lot_objects[key] = new_building_obj
                                key_to_update = key
                                break
                        # if key_to_update:
                            # print(f"      → Updated {key_to_update} reference to mesh object")

                # Use the new building object (which might be the same if it was already a mesh)
                building_obj = new_building_obj

                # Create boolean modifier
                modifier = building_obj.modifiers.new(name=f"Boolean_{obj_name}", type='BOOLEAN')
                if modifier:
                    modifier.operation = 'DIFFERENCE'
                    modifier.object = subtractive_copy
                    modifier.solver = 'EXACT'
                    print(f"      → Applied boolean subtraction to {building_obj.name}")
                else:
                    print(f"      ⚠ Warning: Could not create boolean modifier on {building_obj.name}")

            subtractive_obj = subtractive_copy
            break  # Only handle first subtractive object found

    return empty, subtractive_obj


def parse_door_window_id(element_id):
    """
    Parse door/window ID to extract position and angle
    Format: tile_X_lotY_type_posX_posY_angle
           OR tile_X_lotY_building_floorN_type_posX_posY (floor-specific, no angle)

    Returns:
        tuple: (type, x, y, angle) or None if not a door/window
    """
    import re

    # Match pattern with angle: ..._(door|window)_X_Y_ANGLE
    match = re.search(r'_(door|window)_(\d+)_(\d+)_(-?\d+)$', element_id)
    if match:
        obj_type = match.group(1)
        x = int(match.group(2))
        y = int(match.group(3))
        angle = int(match.group(4))
        return (obj_type, x, y, angle)

    # Match pattern without angle (floor-specific): ..._(door|window)_X_Y$
    match = re.search(r'_(door|window)_(\d+)_(\d+)$', element_id)
    if match:
        obj_type = match.group(1)
        x = int(match.group(2))
        y = int(match.group(3))
        angle = 0  # Default to 0 if no angle
        return (obj_type, x, y, angle)

    return None


def parse_staircase_id(element_id):
    """
    Parse staircase ID to extract type, position, and rotation
    Format: tile_X_lotY_staircase_TYPE_INDEX_X_Y_ANGLEdeg
           OR tile_X_lotY_building_staircase_TYPE_INDEX_X_Y_ANGLEdeg

    Examples:
        tile_6_lot3_building_staircase_external_3_100_200_180deg
        tile_14_lot4_building_staircase_external_0_50_75_90deg
        tile_7_lot0_staircase_internal_0_10_20_0deg

    Returns:
        tuple: (stair_type, index, x, y, angle) or None if not a staircase
            stair_type: 'internal' or 'external'
            index: integer index (0, 1, 2, ...)
            x, y: position coordinates
            angle: rotation angle in degrees (0 if not specified)
    """
    import re

    # Check if this contains "staircase_" anywhere in the ID
    if 'staircase_' not in element_id:
        return None

    # Match pattern: staircase_(internal|external)_INDEX_X_Y_ANGLEdeg
    # Note: angle can be negative (e.g., -90deg)
    match = re.search(r'staircase_(internal|external)_(\d+)_(\d+)_(\d+)_(-?\d+)deg$', element_id)
    if match:
        stair_type = match.group(1)
        index = int(match.group(2))
        x = int(match.group(3))
        y = int(match.group(4))
        angle = int(match.group(5))
        return (stair_type, index, x, y, angle)

    # Also match pattern without angle: staircase_(internal|external)_INDEX_X_Y
    match = re.search(r'staircase_(internal|external)_(\d+)_(\d+)_(\d+)$', element_id)
    if match:
        stair_type = match.group(1)
        index = int(match.group(2))
        x = int(match.group(3))
        y = int(match.group(4))
        angle = 0  # Default to 0 if no angle
        return (stair_type, index, x, y, angle)

    return None


def parse_floor_number(element_id):
    """
    Parse floor number from building element ID
    Format: tile_X_lotY_building_floorN_...
           OR tile_X_lotY_building_floorN

    Examples:
        tile_9_lot7_building_floor0  (ground floor)
        tile_9_lot7_building_floor1
        tile_9_lot7_building_floor1_offset
        tile_9_lot7_building_floor2_roof

    Returns:
        int: floor number (0, 1, 2, 3, ...) or None if no floor specified
        Note: floor0 is ground level, floor1 is first floor above ground, etc.
    """
    import re

    # Match pattern: ..._building_floor(\d+)...
    match = re.search(r'_building_floor(\d+)', element_id)
    if match:
        return int(match.group(1))

    # Also check for _floor(\d+) pattern without "building_" prefix
    match = re.search(r'_floor(\d+)', element_id)
    if match:
        return int(match.group(1))

    return None


def import_svg_with_hierarchy(svg_path):
    """
    Import SVG with nested loop structure:
    For all tiles -> For all lots -> For all elements
    Also places tree instances where tree polygons are found
    """

    if not Path(svg_path).exists():
        print(f"ERROR: SVG file not found: {svg_path}")
        return

    print(f"Importing SVG: {svg_path}")

    # Parse SVG
    tree = ET.parse(svg_path)
    root = tree.getroot()

    # Handle SVG namespace
    ns = {'svg': 'http://www.w3.org/2000/svg'}

    # Create main collection for the entire import
    main_collection = create_collection("SVG_Import")

    # Look for object collections in the scene
    print(f"\nLooking for object collections...")
    print(f"Available collections: {list(bpy.data.collections.keys())}")

    tree_collection = None
    door_collection = None
    window_collection = None
    staircase_internal_collection = None
    staircase_internal_cap_collection = None
    staircase_external_collection = None
    staircase_external_cap_collection = None

    if "tree" in bpy.data.collections:
        tree_collection = bpy.data.collections["tree"]
        print(f"✓ Found 'tree' collection with {len(tree_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'tree' collection found - tree instances will not be created")

    if "door" in bpy.data.collections:
        door_collection = bpy.data.collections["door"]
        print(f"✓ Found 'door' collection with {len(door_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'door' collection found - door instances will not be created")

    if "window" in bpy.data.collections:
        window_collection = bpy.data.collections["window"]
        print(f"✓ Found 'window' collection with {len(window_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'window' collection found - window instances will not be created")

    if "staircase_internal" in bpy.data.collections:
        staircase_internal_collection = bpy.data.collections["staircase_internal"]
        print(f"✓ Found 'staircase_internal' collection with {len(staircase_internal_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'staircase_internal' collection found - internal staircase instances will not be created")

    if "staircase_internal_cap" in bpy.data.collections:
        staircase_internal_cap_collection = bpy.data.collections["staircase_internal_cap"]
        print(f"✓ Found 'staircase_internal_cap' collection with {len(staircase_internal_cap_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'staircase_internal_cap' collection found - internal staircase caps will not be created")

    if "staircase_external" in bpy.data.collections:
        staircase_external_collection = bpy.data.collections["staircase_external"]
        print(f"✓ Found 'staircase_external' collection with {len(staircase_external_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'staircase_external' collection found - external staircase instances will not be created")

    if "staircase_external_cap" in bpy.data.collections:
        staircase_external_cap_collection = bpy.data.collections["staircase_external_cap"]
        print(f"✓ Found 'staircase_external_cap' collection with {len(staircase_external_cap_collection.objects)} objects")
    else:
        print(f"⚠ Warning: No 'staircase_external_cap' collection found - external staircase caps will not be created")

    # Create collections for instances
    trees_instance_collection = create_collection("Trees", parent=main_collection)
    doors_instance_collection = create_collection("Doors", parent=main_collection)
    windows_instance_collection = create_collection("Windows", parent=main_collection)
    staircases_instance_collection = create_collection("Staircases", parent=main_collection)
    subtractive_collection = create_collection("Subtractive_Geometry", parent=main_collection)

    # Import main layers first
    main_layers = ['svgContainer', 'offsetlayer', 'scatter', 'source',
                   'source-offset', 'source-union', 'subdivision']

    print("\nImporting main layers...")
    source_collection = None
    for layer_id in main_layers:
        layer_element = root.find(f".//*[@id='{layer_id}']")
        if layer_element:
            layer_collection = create_collection(layer_id, parent=main_collection)
            print(f"  Created collection: {layer_id}")

            # Keep reference to source collection
            if layer_id == 'source':
                source_collection = layer_collection

    # Import source group curves
    if source_collection:
        print("\nImporting source group curves...")
        source_element = root.find(f".//*[@id='source']")
        if source_element:
            # Find all direct child path elements (with or without IDs)
            source_children = []
            for child in source_element:
                tag = child.tag.split('}')[-1]  # Remove namespace
                if tag == 'path':
                    source_children.append(child)

            source_curve_count = 0

            for idx, child_element in enumerate(source_children):
                child_id = child_element.get('id')

                # If no ID, generate a name based on the group
                if not child_id:
                    child_id = f"source_tile_{idx}"
                    child_element.set('id', child_id)  # Set ID so import function can use it

                # Import each curve in the source group
                obj, centroid = import_svg_element_as_curve(child_element, source_collection, scale=SVG_SCALE)
                if obj:
                    source_curve_count += 1

            print(f"  Imported {source_curve_count} curves from source group")

    # Debug: Show all IDs in the SVG
    print("\nAnalyzing SVG structure...")
    all_elements = root.findall(".//*[@id]")
    all_ids = [elem.get('id') for elem in all_elements]
    print(f"Found {len(all_ids)} elements with IDs")

    # Count element types
    element_types = {}
    path_elements = []
    for elem in all_elements:
        tag = elem.tag.split('}')[-1]  # Remove namespace
        element_types[tag] = element_types.get(tag, 0) + 1
        if tag == 'path':
            path_elements.append(elem.get('id'))

    print(f"\nElement types found:")
    for tag, count in element_types.items():
        print(f"  - {tag}: {count}")

    # Show first 20 IDs as sample
    if all_ids:
        print("\nSample IDs (first 20):")
        for id in all_ids[:20]:
            print(f"  - {id}")

    # Show first 10 path elements specifically
    if path_elements:
        print(f"\nPath elements (first 10 of {len(path_elements)}):")
        for id in path_elements[:10]:
            print(f"  - {id}")

    # Process all elements directly (no tile/lot container elements exist)
    print("\nProcessing elements...")
    imported_count = 0
    tree_count = 0
    door_count = 0
    window_count = 0
    staircase_count = 0

    # Get all elements with IDs
    all_elements = root.findall(".//*[@id]")

    # Organize elements by tile and lot
    tiles_dict = {}  # tile_num -> {lot_num -> [elements]}

    for element in all_elements:
        element_id = element.get('id')

        # Match pattern: tile_X_lotY_...
        import re
        match = re.match(r'tile_(\d+)_lot(\d+)_(.+)', element_id)
        if match:
            tile_num = int(match.group(1))
            lot_num = int(match.group(2))

            if tile_num not in tiles_dict:
                tiles_dict[tile_num] = {}
            if lot_num not in tiles_dict[tile_num]:
                tiles_dict[tile_num][lot_num] = []

            tiles_dict[tile_num][lot_num].append(element)

    print(f"Found {len(tiles_dict)} tiles with elements")

    # Process each tile and lot
    for tile_num in sorted(tiles_dict.keys()):
        tile_id = f"tile_{tile_num}"
        print(f"\nTile {tile_num}")

        # Create collection for this tile
        tile_collection = create_collection(tile_id, parent=main_collection)

        for lot_num in sorted(tiles_dict[tile_num].keys()):
            lot_id = f"tile_{tile_num}_lot{lot_num}"
            elements = tiles_dict[tile_num][lot_num]

            print(f"  Lot {lot_num} - {len(elements)} elements")

            # Create collection for this lot
            lot_collection = create_collection(lot_id, parent=tile_collection)

            # Track building object for this lot (for boolean operations)
            building_offset_obj = None

            # First pass: Import geometry and find building offset object
            lot_objects = {}  # element_id -> obj mapping
            for element in elements:
                element_id = element.get('id')

                # Skip door/window/staircase markers (we'll handle them in second pass)
                if parse_door_window_id(element_id) or parse_staircase_id(element_id):
                    continue

                # Import this element with scale
                obj, centroid = import_svg_element_as_curve(element, lot_collection, scale=SVG_SCALE)
                if obj:
                    imported_count += 1
                    lot_objects[element_id] = obj

                    # Check if this is the building offset object
                    if 'building_offset' in element_id:
                        building_offset_obj = obj
                        print(f"    → Identified building offset for boolean operations")

                # Check if this is a tree element (has "_tree_" in ID)
                if '_tree_' in element_id:
                    # print(f"    → Found tree element: {element_id}")
                    if tree_collection and centroid:
                        # Place a tree instance at this location
                        instance_tree_at_location(
                            centroid,
                            tree_collection,
                            trees_instance_collection,
                            f"{element_id}_instance"
                        )
                        tree_count += 1
                    # else:
                        # if not tree_collection:
                        #     print(f"    ⚠ Skipping: no tree collection available")
                        # if not centroid:
                        #     print(f"    ⚠ Skipping: centroid not calculated")

            # Determine the number of floors in this building (for staircase duplication)
            max_floor = 0
            for element in elements:
                element_id = element.get('id')
                floor_num = parse_floor_number(element_id)
                if floor_num is not None and floor_num > max_floor:
                    max_floor = floor_num

            if max_floor > 0:
                print(f"    → Building has {max_floor + 1} floors (0-{max_floor})")

            # Second pass: Handle doors, windows, and staircases
            for element in elements:
                element_id = element.get('id')

                # Check if this is a staircase (parse type, index, x, y, and angle from ID)
                staircase_info = parse_staircase_id(element_id)
                if staircase_info:
                    stair_type, index, x, y, angle = staircase_info
                    print(f"    → Detected {stair_type} staircase: {element_id} at ({x}, {y}) angle: {angle}°")

                    # Use coordinates directly from ID (with scale and Y-flip)
                    location = Point(x * SVG_SCALE, y * SVG_SCALE * SVG_Y_FLIP)
                    print(f"    → Position: ({location.x:.2f}, {location.y:.2f})")

                    # Select the appropriate staircase collection
                    staircase_collection = None
                    if stair_type == 'internal' and staircase_internal_collection:
                        staircase_collection = staircase_internal_collection
                        print(f"    → Using staircase_internal collection")
                    elif stair_type == 'external' and staircase_external_collection:
                        staircase_collection = staircase_external_collection
                        print(f"    → Using staircase_external collection")
                    else:
                        print(f"    ⚠ No {stair_type} staircase collection found!")

                    if staircase_collection:
                        # For external staircases, place on ALL floors
                        # For internal staircases, place only once (or on specified floor)
                        if stair_type == 'external' and max_floor > 0:
                            print(f"    → Placing external staircase on floors 0-{max_floor}")

                            # Create instance on each floor
                            for floor_num in range(max_floor + 1):
                                z_offset = floor_num * FLOOR_HEIGHT

                                # Collect all objects to apply boolean to (wall, floor slab, roof)
                                objects_to_subtract = []

                                # 1. Find wall object for this floor
                                wall_key = f"building_floor{floor_num}_wall"
                                for key, obj in list(lot_objects.items()):
                                    if wall_key in key:
                                        try:
                                            _ = obj.name
                                            objects_to_subtract.append((obj, key))
                                        except ReferenceError:
                                            continue

                                # 2. Find floor slab for this floor (sits on top of walls)
                                floor_patterns = [
                                    f"floor{floor_num}_fill",
                                    f"building_floor{floor_num}_fill",
                                    f"building_floor{floor_num}_offset",
                                    f"building_floor{floor_num}",
                                    f"floor{floor_num}_offset",
                                    f"floor{floor_num}"
                                ]
                                for key, obj in list(lot_objects.items()):
                                    for pattern in floor_patterns:
                                        if pattern in key and '_wall' not in key and '_roof' not in key:
                                            try:
                                                _ = obj.name
                                                # Check if not already added
                                                if not any(o[0] == obj for o in objects_to_subtract):
                                                    objects_to_subtract.append((obj, key))
                                                    print(f"    → Will subtract from floor slab: {key}")
                                            except ReferenceError:
                                                continue

                                # 3. Find building_offset (base building/ground floor)
                                if floor_num == 0:
                                    for key, obj in list(lot_objects.items()):
                                        if 'building_offset' in key:
                                            try:
                                                _ = obj.name
                                                # Check if not already added
                                                if not any(o[0] == obj for o in objects_to_subtract):
                                                    objects_to_subtract.append((obj, key))
                                                    print(f"    → Will subtract from building base: {key}")
                                            except ReferenceError:
                                                continue

                                # 4. Find the roof (building_roof should be subtracted on ALL floors for external staircases)
                                # External staircases go through all floors including the roof
                                roof_patterns = [
                                    'building_roof',
                                    '_roof'
                                ]
                                for key, obj in list(lot_objects.items()):
                                    # Match building_roof or any key ending with _roof that contains building
                                    is_building_roof = False
                                    if 'building' in key:
                                        for roof_pattern in roof_patterns:
                                            if roof_pattern in key:
                                                is_building_roof = True
                                                break

                                    if is_building_roof:
                                        try:
                                            _ = obj.name
                                            # Check if not already added
                                            if not any(o[0] == obj for o in objects_to_subtract):
                                                objects_to_subtract.append((obj, key))
                                                print(f"    → Will subtract from roof: {key} (floor {floor_num})")
                                        except ReferenceError:
                                            continue

                                # Create ONE visible instance for this floor
                                # Pass the first object for boolean (if any)
                                first_obj = objects_to_subtract[0] if objects_to_subtract else (None, None)

                                instance_empty, _ = instance_object_at_location(
                                    location,
                                    angle,
                                    staircase_collection,
                                    staircases_instance_collection,
                                    f"{element_id}_floor{floor_num}_instance",
                                    building_obj=first_obj[0] if first_obj[0] else None,
                                    subtractive_collection=subtractive_collection,
                                    lot_objects=lot_objects,
                                    building_key=first_obj[1] if first_obj[0] else None,
                                    z_offset=z_offset
                                )

                                # Apply boolean to remaining objects (if more than one)
                                if len(objects_to_subtract) > 1:
                                    # Find subtractive object in the collection
                                    subtractive_obj_template = None
                                    for obj in staircase_collection.objects:
                                        if any(keyword in obj.name.lower() for keyword in ['_subtractive', '_remove', '_sub']):
                                            subtractive_obj_template = obj
                                            break

                                    if subtractive_obj_template:
                                        # Apply to remaining objects (skip first one, already done)
                                        for target_obj, target_key in objects_to_subtract[1:]:
                                            # Duplicate the subtractive object
                                            subtractive_copy = subtractive_obj_template.copy()
                                            if subtractive_obj_template.data:
                                                subtractive_copy.data = subtractive_obj_template.data.copy()

                                            subtractive_copy.name = f"{element_id}_floor{floor_num}_{target_key}_subtractive"
                                            subtractive_copy.location = (location.x, location.y, z_offset)
                                            subtractive_copy.rotation_euler = (0, 0, math.radians(-angle))

                                            # Add to subtractive collection
                                            subtractive_collection.objects.link(subtractive_copy)

                                            # Convert target to mesh if needed
                                            if target_obj.type == 'CURVE':
                                                target_obj = convert_curve_to_mesh(target_obj)
                                                lot_objects[target_key] = target_obj

                                            # Create boolean modifier
                                            modifier = target_obj.modifiers.new(name=f"Boolean_{element_id}_floor{floor_num}", type='BOOLEAN')
                                            if modifier:
                                                modifier.operation = 'DIFFERENCE'
                                                modifier.object = subtractive_copy
                                                modifier.solver = 'EXACT'
                                                print(f"    → Applied staircase boolean to {target_key}")

                                staircase_count += 1

                            # After placing all floors, add the cap at the top
                            if staircase_external_cap_collection:
                                cap_z_offset = (max_floor + 1) * FLOOR_HEIGHT
                                cap_instance, _ = instance_object_at_location(
                                    location,
                                    angle,
                                    staircase_external_cap_collection,
                                    staircases_instance_collection,
                                    f"{element_id}_cap_instance",
                                    building_obj=None,  # No boolean for cap
                                    subtractive_collection=None,
                                    lot_objects=None,
                                    building_key=None,
                                    z_offset=cap_z_offset
                                )
                                print(f"    ✓ Placed external staircase cap at Z={cap_z_offset}m (top of floor {max_floor})")

                            print(f"    ✓ Placed external staircase on {max_floor + 1} floors")
                        elif stair_type == 'internal' and max_floor > 0:
                            # Internal staircase - place on ALL floors (similar to external)
                            print(f"    → Placing internal staircase on floors 0-{max_floor}")

                            # Create instance on each floor
                            for floor_num in range(max_floor + 1):
                                z_offset = floor_num * FLOOR_HEIGHT

                                # Collect all objects to apply boolean to (floor slabs)
                                objects_to_subtract = []

                                # Find floor slab for this floor (sits on top of walls)
                                floor_patterns = [
                                    f"floor{floor_num}_fill",
                                    f"building_floor{floor_num}_fill",
                                    f"building_floor{floor_num}_offset",
                                    f"building_floor{floor_num}",
                                    f"floor{floor_num}_offset",
                                    f"floor{floor_num}"
                                ]
                                for key, obj in list(lot_objects.items()):
                                    for pattern in floor_patterns:
                                        if pattern in key and '_wall' not in key and '_roof' not in key:
                                            try:
                                                _ = obj.name
                                                # Check if not already added
                                                if not any(o[0] == obj for o in objects_to_subtract):
                                                    objects_to_subtract.append((obj, key))
                                                    print(f"    → Will subtract from floor slab: {key}")
                                            except ReferenceError:
                                                continue

                                # For floor 0, also include building_offset (base building/ground floor)
                                if floor_num == 0:
                                    for key, obj in list(lot_objects.items()):
                                        if 'building_offset' in key:
                                            try:
                                                _ = obj.name
                                                # Check if not already added
                                                if not any(o[0] == obj for o in objects_to_subtract):
                                                    objects_to_subtract.append((obj, key))
                                                    print(f"    → Will subtract from building base: {key}")
                                            except ReferenceError:
                                                continue

                                # Find the roof (building_roof should be subtracted on ALL floors for internal staircases)
                                roof_patterns = [
                                    'building_roof',
                                    '_roof'
                                ]
                                for key, obj in list(lot_objects.items()):
                                    # Match building_roof or any key ending with _roof that contains building
                                    is_building_roof = False
                                    if 'building' in key:
                                        for roof_pattern in roof_patterns:
                                            if roof_pattern in key:
                                                is_building_roof = True
                                                break

                                    if is_building_roof:
                                        try:
                                            _ = obj.name
                                            # Check if not already added
                                            if not any(o[0] == obj for o in objects_to_subtract):
                                                objects_to_subtract.append((obj, key))
                                                print(f"    → Will subtract from roof: {key} (floor {floor_num})")
                                        except ReferenceError:
                                            continue

                                # Create ONE visible instance for this floor
                                # Pass the first object for boolean (if any)
                                first_obj = objects_to_subtract[0] if objects_to_subtract else (None, None)

                                instance_empty, _ = instance_object_at_location(
                                    location,
                                    angle,
                                    staircase_collection,
                                    staircases_instance_collection,
                                    f"{element_id}_floor{floor_num}_instance",
                                    building_obj=first_obj[0] if first_obj[0] else None,
                                    subtractive_collection=subtractive_collection,
                                    lot_objects=lot_objects,
                                    building_key=first_obj[1] if first_obj[0] else None,
                                    z_offset=z_offset
                                )

                                # Apply boolean to remaining objects (if more than one)
                                if len(objects_to_subtract) > 1:
                                    # Find subtractive object in the collection
                                    subtractive_obj_template = None
                                    for obj in staircase_collection.objects:
                                        if any(keyword in obj.name.lower() for keyword in ['_sub', '_subtractive', '_remove']):
                                            subtractive_obj_template = obj
                                            break

                                    if subtractive_obj_template:
                                        # Apply to remaining objects (skip first one, already done)
                                        for target_obj, target_key in objects_to_subtract[1:]:
                                            # Duplicate the subtractive object
                                            subtractive_copy = subtractive_obj_template.copy()
                                            if subtractive_obj_template.data:
                                                subtractive_copy.data = subtractive_obj_template.data.copy()

                                            subtractive_copy.name = f"{element_id}_floor{floor_num}_{target_key}_subtractive"
                                            subtractive_copy.location = (location.x, location.y, z_offset)
                                            subtractive_copy.rotation_euler = (0, 0, math.radians(-angle))

                                            # Add to subtractive collection
                                            subtractive_collection.objects.link(subtractive_copy)

                                            # Convert target to mesh if needed
                                            if target_obj.type == 'CURVE':
                                                target_obj = convert_curve_to_mesh(target_obj)
                                                lot_objects[target_key] = target_obj

                                            # Create boolean modifier
                                            modifier = target_obj.modifiers.new(name=f"Boolean_{element_id}_floor{floor_num}", type='BOOLEAN')
                                            if modifier:
                                                modifier.operation = 'DIFFERENCE'
                                                modifier.object = subtractive_copy
                                                modifier.solver = 'EXACT'
                                                print(f"    → Applied staircase boolean to {target_key}")

                                staircase_count += 1

                            # After placing all floors, add the cap at the top
                            if staircase_internal_cap_collection:
                                cap_z_offset = (max_floor + 1) * FLOOR_HEIGHT
                                cap_instance, _ = instance_object_at_location(
                                    location,
                                    angle,
                                    staircase_internal_cap_collection,
                                    staircases_instance_collection,
                                    f"{element_id}_cap_instance",
                                    building_obj=None,  # No boolean for cap
                                    subtractive_collection=None,
                                    lot_objects=None,
                                    building_key=None,
                                    z_offset=cap_z_offset
                                )
                                print(f"    ✓ Placed internal staircase cap at Z={cap_z_offset}m (top of floor {max_floor})")

                            print(f"    ✓ Placed internal staircase on {max_floor + 1} floors")
                        else:
                            # Single-floor building (external or internal staircase)
                            # Check if this staircase is for a specific floor
                            floor_number = parse_floor_number(element_id)
                            z_offset = 0
                            if floor_number is not None:
                                z_offset = floor_number * FLOOR_HEIGHT

                            # Get the building object for boolean operations
                            current_building = None
                            target_key = None

                            # Try to find wall object for this floor (if floor-specific)
                            if floor_number is not None:
                                wall_key = f"building_floor{floor_number}_wall"
                                for key, obj in list(lot_objects.items()):
                                    if wall_key in key:
                                        try:
                                            _ = obj.name
                                            current_building = obj
                                            target_key = key
                                            break
                                        except ReferenceError:
                                            continue

                            # Fall back to building_offset if no wall found
                            if not current_building:
                                for key, obj in list(lot_objects.items()):
                                    if 'building_offset' in key or 'wall' in key:
                                        try:
                                            _ = obj.name
                                            current_building = obj
                                            target_key = key
                                            break
                                        except ReferenceError:
                                            continue

                            # Create instance with floor offset and boolean support
                            instance_empty, _ = instance_object_at_location(
                                location,
                                angle,
                                staircase_collection,
                                staircases_instance_collection,
                                f"{element_id}_instance",
                                building_obj=current_building,
                                subtractive_collection=subtractive_collection,
                                lot_objects=lot_objects,
                                building_key=target_key,
                                z_offset=z_offset
                            )

                            staircase_count += 1
                            print(f"    ✓ Placed {stair_type} staircase (single floor)")
                    else:
                        print(f"    ⚠ No {stair_type} staircase collection: {element_id}")
                    continue

                # Check if this is an internal door/window (line element with _door or _window)
                # Pattern: tile_X_lotY_building_wall_N_door or tile_X_lotY_building_floorN_wall_M_window
                tag = element.tag.split('}')[-1]  # Remove namespace
                if tag == 'line' and ('_door' in element_id or '_window' in element_id):
                    # Determine if it's a door or window
                    obj_type = 'door' if '_door' in element_id else 'window'

                    # Get line coordinates to calculate angle and position
                    x1 = float(element.get('x1', 0))
                    y1 = float(element.get('y1', 0))
                    x2 = float(element.get('x2', 0))
                    y2 = float(element.get('y2', 0))

                    # Use first point (x1, y1) as position for corner-oriented doors
                    location = Point(x1 * SVG_SCALE, y1 * SVG_SCALE * SVG_Y_FLIP)

                    # Calculate angle from line direction using atan2
                    # atan2(dy, dx) gives angle in radians from positive X axis
                    dx = x2 - x1
                    dy = y2 - y1
                    angle_rad = math.atan2(dy, dx)
                    angle = math.degrees(angle_rad)

                    print(f"    → Detected internal {obj_type}: {element_id} at ({location.x:.2f}, {location.y:.2f}) angle: {angle:.1f}°")

                    # Check if door/window is on a specific floor
                    floor_number = parse_floor_number(element_id)
                    z_offset = 0
                    if floor_number is not None:
                        z_offset = floor_number * FLOOR_HEIGHT
                        print(f"    → {obj_type} on floor {floor_number} at Z={z_offset}m")

                    # Get the current building object (may have been converted to mesh)
                    # For inset doors, look for inset_wall; for regular doors, look for exterior wall
                    current_building = None
                    target_key = None

                    # Check if this is an inset door (should boolean from inset_wall)
                    is_inset_door = '_inset_door' in element_id

                    # Check if this door belongs to a numbered wall (e.g., wall_3_door)
                    # Extract wall number from pattern: ...wall_N_door
                    import re
                    wall_number_match = re.search(r'_wall_(\d+)_door', element_id)
                    wall_number = wall_number_match.group(1) if wall_number_match else None

                    if floor_number is not None:
                        if is_inset_door:
                            # Look for the inset_wall on this floor
                            inset_wall_key = f"building_floor{floor_number}_inset_wall"
                            for key, obj in list(lot_objects.items()):
                                if inset_wall_key in key and '_door' not in key:
                                    try:
                                        _ = obj.name  # Check if object is still valid
                                        current_building = obj
                                        target_key = key
                                        print(f"    → Using {key} for boolean operation (inset wall)")
                                        break
                                    except ReferenceError:
                                        continue
                        elif wall_number is not None:
                            # Look for the specific numbered wall (e.g., wall_3 for wall_3_door)
                            numbered_wall_key = f"building_floor{floor_number}_wall_{wall_number}"
                            for key, obj in list(lot_objects.items()):
                                if numbered_wall_key in key and '_door' not in key and '_window' not in key:
                                    try:
                                        _ = obj.name  # Check if object is still valid
                                        current_building = obj
                                        target_key = key
                                        print(f"    → Using {key} for boolean operation (numbered wall)")
                                        break
                                    except ReferenceError:
                                        continue
                        else:
                            # Try to find the generic exterior wall object for this specific floor
                            wall_key = f"building_floor{floor_number}_wall"
                            for key, obj in list(lot_objects.items()):
                                if wall_key in key and '_door' not in key and '_window' not in key and '_inset' not in key:
                                    try:
                                        _ = obj.name  # Check if object is still valid
                                        current_building = obj
                                        target_key = key
                                        print(f"    → Using {key} for boolean operation")
                                        break
                                    except ReferenceError:
                                        continue

                    # Fall back to building_offset if no wall found
                    if not current_building:
                        for key, obj in list(lot_objects.items()):
                            if 'building_offset' in key:
                                try:
                                    _ = obj.name
                                    current_building = obj
                                    target_key = key
                                    break
                                except ReferenceError:
                                    continue

                    # Instance the door or window
                    if obj_type == 'door' and door_collection:
                        instance_empty, _ = instance_object_at_location(
                            location,
                            angle,
                            door_collection,
                            doors_instance_collection,
                            f"{element_id}_instance",
                            building_obj=current_building,
                            subtractive_collection=subtractive_collection,
                            lot_objects=lot_objects,
                            building_key=target_key,
                            z_offset=z_offset
                        )
                        door_count += 1
                    elif obj_type == 'window' and window_collection:
                        instance_empty, _ = instance_object_at_location(
                            location,
                            angle,
                            window_collection,
                            windows_instance_collection,
                            f"{element_id}_instance",
                            building_obj=current_building,
                            subtractive_collection=subtractive_collection,
                            lot_objects=lot_objects,
                            building_key=target_key,
                            z_offset=z_offset
                        )
                        window_count += 1
                    continue

                # Check if this is a door or window (parse position and angle from ID)
                door_window_info = parse_door_window_id(element_id)
                if door_window_info:
                    obj_type, x, y, angle = door_window_info
                    location = Point(x * SVG_SCALE, y * SVG_SCALE * SVG_Y_FLIP)

                    # Check if door/window is on a specific floor
                    floor_number = parse_floor_number(element_id)
                    z_offset = 0
                    if floor_number is not None:
                        # Position at the base of this floor
                        # Floor 0 = Z:0, Floor 1 = Z:4.1, Floor 2 = Z:8.2, etc.
                        z_offset = floor_number * FLOOR_HEIGHT
                        # print(f"    → {obj_type} on floor {floor_number} at Z={z_offset}m")

                    # Get the current building object (may have been converted to mesh)
                    # For floor-specific walls, look for wall object on that floor
                    current_building = None
                    target_key = None

                    if floor_number:
                        # Try to find the wall object for this specific floor
                        wall_key = f"building_floor{floor_number}_wall"
                        for key, obj in list(lot_objects.items()):
                            if wall_key in key:
                                try:
                                    _ = obj.name  # Check if object is still valid
                                    current_building = obj
                                    target_key = key
                                    # print(f"    → Using {key} for boolean operation")
                                    break
                                except ReferenceError:
                                    # print(f"    ⚠ Warning: {key} object was deleted")
                                    continue

                    # Fall back to building_offset if no wall found
                    if not current_building:
                        for key, obj in list(lot_objects.items()):
                            if 'building_offset' in key:
                                try:
                                    _ = obj.name
                                    current_building = obj
                                    target_key = key
                                    break
                                except ReferenceError:
                                    continue

                    # Skip if no valid building object found
                    if not current_building:
                        # print(f"    ⚠ Warning: No valid building/wall object found for {obj_type}")
                        # Still place the instance, just no boolean operation
                        current_building = None
                        target_key = None

                    if obj_type == 'door' and door_collection:
                        instance_empty, _ = instance_object_at_location(
                            location,
                            angle,
                            door_collection,
                            doors_instance_collection,
                            f"{element_id}_instance",
                            building_obj=current_building,
                            subtractive_collection=subtractive_collection,
                            lot_objects=lot_objects,
                            building_key=target_key,
                            z_offset=z_offset
                        )
                        door_count += 1
                    elif obj_type == 'window' and window_collection:
                        instance_empty, _ = instance_object_at_location(
                            location,
                            angle,
                            window_collection,
                            windows_instance_collection,
                            f"{element_id}_instance",
                            building_obj=current_building,
                            subtractive_collection=subtractive_collection,
                            lot_objects=lot_objects,
                            building_key=target_key,
                            z_offset=z_offset
                        )
                        window_count += 1
                    continue

    # Apply boolean modifiers and clean up subtractive geometry
    print(f"\nApplying boolean modifiers and cleaning up...")
    if subtractive_collection and len(subtractive_collection.objects) > 0:
        # Collect all objects with boolean modifiers
        objects_with_booleans = []
        for obj in bpy.data.objects:
            if obj.type == 'MESH' and any(mod.type == 'BOOLEAN' for mod in obj.modifiers):
                objects_with_booleans.append(obj)

        # Apply all boolean modifiers using depsgraph (no operators needed)
        applied_count = 0
        for obj in objects_with_booleans:
            # Get the evaluated mesh with all modifiers applied
            depsgraph = bpy.context.evaluated_depsgraph_get()
            obj_eval = obj.evaluated_get(depsgraph)

            # Create new mesh from evaluated object
            mesh = bpy.data.meshes.new_from_object(obj_eval)

            # Store old mesh
            old_mesh = obj.data

            # Replace object's mesh with the evaluated one
            obj.data = mesh
            obj.data.name = old_mesh.name

            # Clear all modifiers (they're now baked into the mesh)
            obj.modifiers.clear()

            # Remove old mesh data
            bpy.data.meshes.remove(old_mesh)

            # Enable smooth shading with auto smooth angle
            # Use shade_smooth() to set smooth shading on all faces
            for poly in obj.data.polygons:
                poly.use_smooth = True

            # Enable auto smooth (sharp edges based on angle)
            obj.data.use_auto_smooth = True
            obj.data.auto_smooth_angle = 0.523599  # 30 degrees in radians

            applied_count += 1
            # print(f"  ✓ Applied modifiers and added smooth shading (30°) on {obj.name}")

        print(f"Applied boolean modifiers on {applied_count} object(s)")

        # Remove all subtractive objects
        subtractive_objects = list(subtractive_collection.objects)
        for obj in subtractive_objects:
            # Remove from all collections
            for col in obj.users_collection:
                col.objects.unlink(obj)
            # Remove object and its data
            if obj.data:
                data = obj.data
                bpy.data.objects.remove(obj)
                # Remove mesh data if no other users
                if data.users == 0:
                    bpy.data.meshes.remove(data)
            else:
                bpy.data.objects.remove(obj)

        print(f"Removed {len(subtractive_objects)} subtractive object(s)")

        # Remove the empty subtractive collection
        bpy.data.collections.remove(subtractive_collection)
        # print(f"  Removed subtractive geometry collection")

    print(f"\n{'='*60}")
    print(f"Import complete! Imported {imported_count} elements")
    print(f"Placed {tree_count} tree instances")
    print(f"Placed {door_count} door instances")
    print(f"Placed {window_count} window instances")
    print(f"Placed {staircase_count} staircase instances")
    print(f"{'='*60}")


def import_using_blender_native():
    """
    Alternative: Use Blender's native SVG import with collections
    This is simpler but gives less control over hierarchy
    """

    if not Path(SVG_PATH).exists():
        print(f"ERROR: SVG file not found: {SVG_PATH}")
        return

    # Import SVG using Blender's built-in importer
    bpy.ops.import_curve.svg(filepath=SVG_PATH)

    print(f"Imported SVG using Blender's native importer: {SVG_PATH}")
    print("Note: Native import may not preserve custom ID hierarchy")

    # Organize imported objects into collections by ID
    organize_by_hierarchy()


def organize_by_hierarchy():
    """
    Post-process: Organize imported objects into hierarchical collections
    based on their names (assuming names match IDs)
    """

    main_collection = create_collection("SVG_Organized")

    # For all tiles
    for tile_num in range(33):
        tile_id = f"tile_{tile_num}"
        tile_objects = [obj for obj in bpy.context.scene.objects if obj.name.startswith(tile_id)]

        if not tile_objects:
            continue

        tile_collection = create_collection(tile_id, parent=main_collection)

        # For all lots
        lot_num = 0
        while True:
            lot_id = f"tile_{tile_num}_lot{lot_num}"
            lot_objects = [obj for obj in tile_objects if obj.name.startswith(lot_id)]

            if not lot_objects:
                break

            lot_collection = create_collection(lot_id, parent=tile_collection)

            # Move objects to lot collection
            for obj in lot_objects:
                # Unlink from all collections
                for col in obj.users_collection:
                    col.objects.unlink(obj)
                # Link to lot collection
                lot_collection.objects.link(obj)

            lot_num += 1

    print("Organized objects into hierarchical collections")


# Main execution
if __name__ == "__main__":
    print("\n" + "="*60)
    print("BLENDER SVG IMPORT SCRIPT")
    print("="*60)

    # Method 1: Custom import with full control (includes tree instancing)
    import_svg_with_hierarchy(SVG_PATH)

    # Method 2: Use Blender's native import then organize (no tree instancing)
    # import_using_blender_native()

    print("\nDone!")
