add tag
Trevor
In MetaPost, I would like to fill in the region in between non-intersecting shapes with a color, similar to how a "bucket fill" tool works in an image editing program. Consider the following code:
```
beginfig(1);

path a,b,c;

a = fullcircle scaled 200;
b = fullcircle scaled 70 shifted (40,40);
c = fullcircle scaled 40 shifted (-20,20);

draw a;
draw b;
draw c;

endfig;
```
![mp_question_0.png](/image?hash=43463ae36a6ab5e886544841d72f5608cdd62e8a85e549e1c238641fe63a9223)

From this code, I would like to produce this:
![mp_question_1.png](/image?hash=b6365790dea80e397640395d6971185f35f316bbe104fe09bee1176afb837620)

I have tried using the `unfill` command (i.e., `fill a; unfill b; unfill c;`), but that command makes the smaller circles white rather than transparent. So if I open the image using a graphics editor program which shows transparency as a checkerboard pattern, I get this:

![mp_question_2.png](/image?hash=4e91b7645cfb5fc7380e426db2dbfe3da06b2e78647de7956a4a5ae214678e83)

I have also tried doing `fill a--reverse b--reverse c--cycle;` but that also does not make the negative space transparent, and also does not render the shapes properly because of how the paths are connected.

**How do I make this figure in a way so that the smaller circles are transparent?**
Top Answer
frougon
The problem has been addressed [in the comments section](https://topanswers.xyz/transcript?room=7411&id=174834#c174834) for the particular circles whose code was provided in the question.

It was subsequently [asked](https://topanswers.xyz/transcript?room=7411&id=174850#c174850) if there is a method that would work for, say, 100 holes cut into the large disc. Extending the trick used for two holes to a large number of holes doesn't seem straightforward nor elegant, and I can't see another way with MetaPost—which does not mean there is none. Therefore, I propose TikZ/expl3 code that does the job for an arbitrary number of circles (precisely 100 in the given example, but it is all parametrized).

![Screenshot_2024-04-01_00-44-55.png](/image?hash=d5ccce290a2d236903e3af4aec7914e9d8a2125eb7f48a3e6089df910a34b242)

The main function is `\computeHoleLocations`; it accepts the following parameters (a key-value interface would clearly be more convenient, but this is not the point of the exercise):

```
#1: node name for the center of the enclosing disc
#2: radius of the enclosing disc, in the xy-coordinate system
#3: minimum hole radius
#4: maximum hole radius
#5: number of holes
#6: minimum distance between holes, in the xy-coordinate system
#7: minimum distance between a hole and the outer circle, in the
    xy-coordinate system
```

Any two holes are guaranteed to have at least #6 of space between them. Any hole is guaranteed to lie at least #7 from the border of the background disc.

(If you ask for something difficult or just impossible, TeX will try very hard; compilation may take a long time in such cases!)

```
\documentclass[tikz,border=2mm]{standalone}

\makeatletter
\ExplSyntaxOn

\seq_new:N \l__my_centers_seq % {x1}{y1}, {x2}{y2}, ... with unit
\seq_new:N \l__my_radii_seq   % each element has the unit explicitly included

\dim_new:N \l__my_xunit_dim

% For parameters used by callees
\tl_new:N \l__my_disc_center_tl             % a \pgfpoint
\tl_new:N \l__my_disc_radius_tl
\tl_new:N \l__my_min_hole_radius_tl
\tl_new:N \l__my_max_hole_radius_tl
\tl_new:N \l__my_min_dist_between_holes_tl
\tl_new:N \l__my_min_dist_from_outer_circle_tl

% #1: node name for the center of the enclosing disc
% #2: radius of the enclosing disc, in the xy-coordinate system
% #3: minimum hole radius
% #4: maximum hole radius
% #5: number of holes
% #6: minimum distance between holes, in the xy-coordinate system
% #7: minimum distance between a hole and the outer circle, in the
%     xy-coordinate system
\cs_new_protected:Npn \my_compute_hole_locations:nnnnnnn #1#2#3#4#5#6#7
  {
    % Extract the length of the first vector (arbitrary choice) of the
    % xy-coordinate system.
    \pgfextractx { \l__my_xunit_dim } { \pgfpointxy { 1 } { 0 } }
    \seq_clear:N \l__my_centers_seq
    \seq_clear:N \l__my_radii_seq

    \pgf@process { \pgfpointanchor {#1} { center } }
    \tl_set:Ne \l__my_disc_center_tl
      { \exp_not:N \pgfpoint { \the\pgf@x } { \the\pgf@y } }
    \pgfmathsetmacro { \l__my_disc_radius_tl } {#2}
    \pgfmathsetmacro { \l__my_min_hole_radius_tl } {#3}
    \pgfmathsetmacro { \l__my_max_hole_radius_tl } {#4}
    \pgfmathsetlengthmacro { \l__my_min_dist_between_holes_tl }
      { (#6) * \l__my_xunit_dim }
    \pgfmathsetmacro { \l__my_min_dist_from_outer_circle_tl } {#7}

    \prg_replicate:nn {#5} { \__my_add_hole: }
  }

\dim_new:N \l__my_cand_center_x_dim
\dim_new:N \l__my_cand_center_y_dim
\tl_new:N \l__my_cand_radius_tl    % in pt
\tl_new:N \l__my_cand_radius_xy_tl % in units of the xy-coordinate system
\bool_new:N \l__my_hole_was_added_bool

\cs_new_protected:Npn \__my_add_hole:
  {
    \bool_set_false:N \l__my_hole_was_added_bool

    \bool_do_until:Nn \l__my_hole_was_added_bool
      {
        \__my_try_candidate_hole:
        { % This part is only executed when the candidate was far enough from
          % all already determined holes.
          \seq_put_right:Ne \l__my_centers_seq
            {
              { \dim_use:N \l__my_cand_center_x_dim }
              { \dim_use:N \l__my_cand_center_y_dim }
            }
          \seq_put_right:NV \l__my_radii_seq \l__my_cand_radius_tl
          \bool_set_true:N \l__my_hole_was_added_bool
        }
      }
  }

% Vector between the centers of two circles
\tl_new:N \l__my_vec_x_tl
\tl_new:N \l__my_vec_y_tl

\cs_new_protected:Npn \__my_try_candidate_hole:
  {
    % Pick a random radius
    \pgfmathsetmacro { \l__my_cand_radius_xy_tl }
      {
        \l__my_min_hole_radius_tl +
        rnd * ( \l__my_max_hole_radius_tl - \l__my_min_hole_radius_tl )
      }
    \pgfmathsetlengthmacro { \l__my_cand_radius_tl }
      { \l__my_cand_radius_xy_tl \l__my_xunit_dim }
    % Pick a random center location, just far enough from the outer circle
    \pgfpointadd { \l__my_disc_center_tl }
      {
        \pgfpointpolarxy { rnd*360 }
          {
            rnd * ( \l__my_disc_radius_tl  - \l__my_cand_radius_xy_tl -
                    \l__my_min_dist_from_outer_circle_tl )
          }
      }
    % Save it
    \dim_set_eq:NN \l__my_cand_center_x_dim \pgf@x
    \dim_set_eq:NN \l__my_cand_center_y_dim \pgf@y

    % Check distance from all holes that have been retained so far
    \seq_map_indexed_inline:Nn \l__my_centers_seq
      {
        \__my_candidate_if_too_close_to_other_hole:nnT {##1} {##2}
          { \seq_map_break:n { \use_none:nn } } % candidate rejected
      }
    \use:n                                      % accepted!
  }

\prg_new_protected_conditional:Npnn
  \__my_candidate_if_too_close_to_other_hole:nn #1#2 { T }
  {
    \pgfpointdiff
      { \pgfpoint { \l__my_cand_center_x_dim } { \l__my_cand_center_y_dim } }
      { \pgfpoint #2 }
    \pgfgetlastxy { \l__my_vec_x_tl } { \l__my_vec_y_tl }

    % Are the two circles too close to each other?
    \pgfmathless { veclen(\l__my_vec_x_tl, \l__my_vec_y_tl) }
      {
        \seq_item:Nn \l__my_radii_seq {#1} + \l__my_cand_radius_tl +
        \l__my_min_dist_between_holes_tl
      }
    \int_compare:nNnTF { \pgfmathresult } = { 1 }
      { \prg_return_true: }
      { \prg_return_false: }
  }

\cs_new:Npn \__my_hole_center:nn #1#2 { (#1,#2) }

% Write TikZ code for all holes defined by \l__my_centers_seq and
% \l__my_radii_seq to the specified tl var.
%
% #1: tl var
\cs_new_protected:Npn \my_set_to_tikz_code_for_holes:N #1
  {
    \cs_set_protected:Npn \__my_append_tikz_code_for_one_hole:nn ##1##2
      {
        \tl_put_right:No #1 { \__my_hole_center:nn ##1~ circle[radius=##2]~ }
      }

    \tl_clear:N #1
    % Append TikZ code for all holes defined by these sequence variables
    \seq_map_pairwise_function:NNN \l__my_centers_seq \l__my_radii_seq
      \__my_append_tikz_code_for_one_hole:nn
  }

\tl_new:N \l__my_ihcit_tikz_code_tl

\cs_new_protected:Npn \__my_ihcit_tmp_func:n #1 { }
\cs_generate_variant:Nn \__my_ihcit_tmp_func:n { V }

% Use the provided template, replacing #1 in it with the TikZ code for all
% holes.
%
% #1: template
\cs_new_protected:Npn \my_insert_holes_code_in_template:n #1
  {
    \my_set_to_tikz_code_for_holes:N \l__my_ihcit_tikz_code_tl

    \cs_set_protected:Npn \__my_ihcit_tmp_func:n ##1 {#1}
    \__my_ihcit_tmp_func:V \l__my_ihcit_tikz_code_tl
  }

\cs_new_eq:NN \computeHoleLocations \my_compute_hole_locations:nnnnnnn
\cs_new_eq:NN \insertHolesCodeInTemplate \my_insert_holes_code_in_template:n

\ExplSyntaxOff
\makeatother

\newdimen\myXUnit

%\pgfmathsetseed{12} % if you want to control the pseudo-RNG

\begin{document}
\begin{tikzpicture}
\pgfextractx{\myXUnit}{\pgfpointxy{1}{0}}
\def\myDiscRadius{9}
\def\myNbHoles{100}

\coordinate (O) at (2,3);
\computeHoleLocations{O}{\myDiscRadius}{0.08}{2.9}{\myNbHoles}{0.3}{0.3}

% This replaces #1 with TikZ code for all holes
\insertHolesCodeInTemplate{
  \fill[black, even odd rule] (O) circle[radius=\myDiscRadius\myXUnit] #1;
}
\end{tikzpicture}
\end{document}
```

Enter question or answer id or url (and optionally further answer ids/urls from the same question) from

Separate each id/url with a space. No need to list your own answers; they will be imported automatically.