Circle Packing on surface in Python

Greetings,
I was looking for a Python solution for circle packing on surface, or at least on a boundary.
Most useful seems to be the VBscript written by Steven Janssen, which I translated to Python using ChatGPT. (maybe somone should include the translation on the developer page?)

But anyway, is there any Python that can solve circle packing on a surface or boundary? I don’t like to use Kangaroo.

Thank you!

# Original VBScript by Steven Janssen
# https://developer.rhino3d.com/samples/rhinoscript/circle-packing/
# Translated to Python by BC using ChatGPT, 241120

import rhinoscriptsyntax as rs
import random
import math


def main():
    # Get Input Circles
    arr_input_r = rs.GetObjects("Select Circles", rs.filter.curve)
    if not arr_input_r:
        return
    
    # Get Radii from Input Circles
    for i, curve in enumerate(arr_input_r):
        if rs.IsCircle(curve):
            arr_input_r[i] = rs.CircleRadius(curve)
    
    # Get Number of Circles
    int_circle_number = rs.GetInteger("Number of Circles", 1000)
    if int_circle_number is None:
        return
    int_circle_number -= 1

    arr_point = [None] * (int_circle_number + 1)
    arr_radius = [None] * (int_circle_number + 1)
    
    # Draw 1st Circle
    arr_radius[0] = random.choice(arr_input_r)
    arr_point[0] = rs.GetPoint("Centre of Circle")
    if not arr_point[0]:
        return
    str_current_circle_id = rs.AddCircle(arr_point[0], arr_radius[0])

    rs.EnableRedraw(False)
    
    # Draw 2nd Circle
    arr_radius[1] = random.choice(arr_input_r)
    arr_point[1] = list(arr_point[0])  # Convert to list for mutability
    arr_point[1][0] += arr_radius[0] + arr_radius[1]
    str_current_circle_id = rs.AddCircle(arr_point[1], arr_radius[1])

    int_current_centre = 0
    int_hole = 1

    # Draw other Circles
    for k in range(2, int_circle_number + 1):
        rs.StatusBarMessage(f"{k+1}/{int_circle_number+1}")
        arr_radius[k] = random.choice(arr_input_r)

        while True:
            marker = 0
            
            # Calculate the lengths of the sides
            arr_side = [0, 0, 0]
            arr_side[0] = rs.Distance(arr_point[int_current_centre], arr_point[k - int_hole])
            arr_side[1] = arr_radius[k] + arr_radius[int_current_centre]
            arr_side[2] = arr_radius[k] + arr_radius[k - int_hole]
            
            # Calculate Angle
            dbl_cos_a = (arr_side[0] ** 2 + arr_side[1] ** 2 - arr_side[2] ** 2) / (2 * arr_side[0] * arr_side[1])
            
            if dbl_cos_a > 1:
                marker = 1
            else:
                dbl_rot_a = math.degrees(math.acos(dbl_cos_a))
                
                # Create, rotate, and scale Vector
                vector = rs.VectorCreate(arr_point[k - int_hole], arr_point[int_current_centre])
                vector = rs.VectorRotate(vector, dbl_rot_a, [0, 0, 1])
                vector = rs.VectorScale(vector, arr_side[1] / arr_side[0])
                arr_point[k] = rs.VectorAdd(vector, arr_point[int_current_centre])
                
                # Check if Circle will Intersect with Existing Circles
                for checkloop in range(k - 1, -1, -1):
                    checkdistance_a = rs.Distance(arr_point[k], arr_point[checkloop]) + 0.001
                    checkdistance_b = arr_radius[k] + arr_radius[checkloop]
                    if checkdistance_a < checkdistance_b:
                        marker = 1
                        break
                
                if marker == 0:
                    str_current_circle_id = rs.AddCircle(arr_point[k], arr_radius[k])
            
            # Exit the loop if the Circle is Good
            if marker == 0:
                int_hole = 1
                break

            int_current_centre += 1

            if int_current_centre == k - int_hole:
                int_hole += 1
                int_current_centre = 0

    rs.EnableRedraw(True)

if __name__ == "__main__":
    main()