Note: A real "Circle Packing" cannot be realized with Geometry Nodes alone, because essential nodes like e.g. Loops are missing.
Therefore, there is no way around an additional tool if you really want to solve it well.
I offer two solutions here, a simple one (which only partially leads to a useful result), and one that does exactly what you are looking for with the help of an external Python library.
Solution 1 (the simple variant):
Here, only by distorting a grid and the resulting area of a face, a possible value for a radius is derived. This is visually close to the solution and may be sufficient in some cases, but it is not really a nice solution and certainly not a "real" circle packing.


Solution 2 (with the help of Python):
If you want to implement real Circle Packing, you probably won't be happy with Geometry Nodes alone.
Basically, you would need to be able to measure the distance of each circle center to any other point (within a certain radius?), and probably repeat this process several times to get a reliable result.
As I said, this is actually what is meant by true "Circle Packing".
Presumably, though, it would be possible to program the algorithm in Python, and that would generate for you the circle centers and the radii of the circles needed for Geometry Nodes.
Search for "Circle Packing Delaunay Triangulation".
...and that's what I did, and I found a solution.
Thanks to a sufficiently stable but very lean implementation in Python, which I personally liked a lot and which was published by mhtchan under the MIT license on Github, I was now able to integrate this into Blender.
You can find the project here: packcircles
This Python library is simply passed an array with Radii, and it returns you the coordinates of the circles, which you then actually just continue to use in Blender with your geometry nodes.
The way to get there is as follows:
To be able to use additional Python libraries in Blender, you have to get them into your system (Windows). The easiest way might be to install the library with pip.
First open a terminal (cmd.exe) and enter the following (replace the example path used here with the one of your Blender installation!!):
cd "C:\Program Files\Blender Foundation\Blender 3.1\3.1\python\bin"
python -m ensurepip
python -m pip install packcircles
Another method to install packages directly from Blender is this one:
Using 3rd party Python modules
After installation the library is installed in the following path and usable in Blender:
C:\Program Files\Blender Foundation\Blender 3.1\3.1\python\lib\site-packages\
Next Start Blender create a new script and make sure the library is loaded and available:
import packcircles
If everything worked out, you only need to create the following script in the editor.
Replace the upper variables with the values you want and execute the script:
from bpy import context, data, ops
import packcircles as pc
import numpy
# Define your own values here
min = 0.5
max = 1.0
count = 50
objectName = 'CirclePack'
# Generate some random radii in the specified range
radii = numpy.random.uniform(min, max, size=(1, count))
# Run the CirclePacking (contains the coordinates and the radius)
points = list(pc.pack(radii[0]))
# Check if an object already exists and otherwise create a new one
if not context.scene.objects.get(objectName):
# Create a curve (starting with a single point, because we set the points later)
ops.curve.simple(
align = 'WORLD',
location = (0, 0, 0),
rotation = (0, 0, 0),
Simple_Type = 'Point',
use_cyclic_u = False
)
# Rename the object so that a easy repetition of this process is possible
for obj in context.selected_objects:
obj.name = objectName
obj.data.name = objectName
# Enter edit mode
ops.object.mode_set(mode = 'EDIT')
# Set curve parameters
ops.curve.select_all(action = 'SELECT')
ops.curve.delete(type = 'VERT')
ops.curve.simple(
Simple_Type = 'Line',
shape = '3D',
outputType = 'POLY',
use_cyclic_u = False
)
# Subdivide the curve so that the number of points equals the number of circles
ops.curve.subdivide(number_cuts = count - 2)
# Set curve as active object
curve = context.active_object
# Iterate through the circlepacking coordinates
for i, coordinates in enumerate(points):
x, y, r = coordinates
curve.data.splines[0].points[i].co = (x, y, 0, 1)
curve.data.splines[0].points[i].radius = r
# Leave edit mode
ops.object.mode_set(mode = 'OBJECT')
Here an array with Radii is passed to the CirclePacking at the beginning, and you get back the coordinates for it.
To make it easy to process all this in Geometry Nodes, a curve is created with the name you specified, whose points correspond to the positions of the individual circles. In order to be able to position the respective circle in the Geometry Nodes in the appropriate size, a value for the radius is also given to the individual points.
If you do not rename the created object, you can repeat the process as often as you like.
All existing points will be removed from the curve and new points will be created randomly.
If the script ran without errors, a new curve will be created. It looks a bit strange, so it needs Geometry Nodes now.

Add a modifier of type "Geometry Nodes" to the curve and do what ever you want to do with it or use the nodes I attached here in the blend file.

Essentially, I don't do much more in this example either. Only circles are instantiated at the points of the curve. Depending on how you additionally influence the radii, different results arise here. Of course, you can also instantiate and scale all unit circles first, and then convert the circles into meshes using the Curve to Mesh
node.

And here is the blend file:
