When working on my personal spirograph app, I was always trying to code things so that I could reuse the same curve data for different image variations. I liked to see how and what would change for the same curve for each variation. It was just fun to do. And, it was necessary if wanted to save the specific image to file—couldn’t have anything change.

So, I would like to get something similar working here. I had considered adding a form field, but figured that might just be overkill. So, I am going to try refactoring the app code to reuse any curve parameters that are not actually specified when submitting the form again.

Reusing Curve Data

I am going to start by setting all the revelant global variables to None. I will also ensure all form fields return nothing if there is not something actually selected/specified by the user. Then when processing the submitted form data, I will only set values for global variables equal to None or for those with a value in the returned POST data.

Rethink

When I started working on the above, I realized that my code would, even if the number and shapes of wheels stayed the same, would generate new random variables for wheel radii and frequences. As well as for the k-fold symmetry and congruency values. Not exactly what I want to see. So, I have decided to use another global variable, u_again. Default is false, i.e. generate random values wherever appropriate. If however, the wheel number and shape fields return nothing, then I will set that global to true and not generate any new, random values for curve parameters. I.E. hopefully end up with the same exact curve.

New Function

In order to determine whether to use the same curve again, there is a bit of checking to be done. Don’t want to include that code in proc_curve_form(). And there is a chance I may need similar code elsewhere. So, added use_again() to sp_app_lib.py.

def use_again(f_data):
  # determine whether or not to use same curve again
  # using function as code is somewhat convoluted and
  # didn't want to include in proc_curve_form()
  do_agn = False
  # do relevant globals have values, if not can not use again
  can_do_agn = g.n_whl is not None and g.su is not None

  # if form fields not empty, do not use again
  if f_data['wheels'] != '':
    do_agn = False
  # need to deal with select and text fields a little differently
  if 'shape' in f_data or f_data['shp_mlt'] != '':
    do_agn = False

  # if okay to use again and form fields empty, use again
  if can_do_agn:
    do_agn = f_data['wheels'] == '' and \
             'shape' not in f_data and \
             f_data['shp_mlt'] == ''

  return do_agn

Refactor Form Processing and Image Generation Functions

In proc_curve_form(), call use_again() to determine whether or not the current curve is to be used again. Proceed accordingly. Function definitely not getting any shorter.

def proc_curve_form(f_data, i_typ):
    g.u_again = use_again(f_data)
    # print(f"f_data: {f_data}; u_again: {g.u_again}")

    u_whl = f_data['wheels']
    u_lw = f_data['ln_sz']
    if 'cmap' in f_data:
      u_cmap = f_data['cmap']
    else:
      u_cmap = None
    if 'shape' in f_data:
      u_shp = f_data['shape'].lower()
    else:
      u_shp = None
    if 'shp_mlt' in f_data:
      u_shps = f_data['shp_mlt'].lower()
    else:
      u_shps = None

    if not g.u_again:
      if u_whl.isnumeric():
        g.n_whl = int(u_whl)
      elif u_whl.lower() == 'r':
        g.n_whl = rng.integers(3, 13)
      else:
        g.n_whl = 3

      if u_shp:
        # had to use something other than 'r' which is used for the rhombus shaped wheel
        if u_shp == 'x':
          g.su = rng.choice(list(splt.shp_nm.keys()))
        elif u_shp in splt.shp_nm:
          g.su = u_shp
      elif u_shps:
        if u_shps == 'rnd':
          # all_shp = list(splt.shps.keys())
          # g.su = [np.random.choice(all_shp) for _ in c_whl]
          g.su = make_mult_shapes("")
        else:
          # !!! need to check for bad shapes, not enough shapes, etc.
          g.su = make_mult_shapes(u_shps)
      else:
        g.su = 'c'

    if u_lw.isnumeric():
      g.ln_w = int(u_lw)
    elif u_lw.lower() == 'r':
      g.ln_w = rng.integers(1, 10)
    # else:
    #   g.lw_w = 2

    # if user has not asked for colour repeat
    if u_cmap:
      if u_cmap in bgcm_mpl:
        g.rcm = u_cmap
      elif u_cmap == 'r':
        g.rcm = rng.choice(list(bgcm_mpl.keys()))
      # else: use the current colour again
    if g.rcm is None:
      g.rcm = rng.choice(list(bgcm_mpl.keys()))

    if i_typ == 'gnarly':
      # torb -> "", "top", "btm"; drows -> "r", "0-9"
      # r_skp = 0     # how many rows to drop for gnarly plots
      # drp_f = True  # which end, from front default
      if 'torb' in f_data and f_data['torb'] in ['top', 'btm']:
        g.drp_f = f_data['torb'] == 'top'
        if 'drows' in f_data:
          if f_data['drows'].isnumeric():
            t_rw = int(f_data['drows'])
            if g.n_whl - t_rw >= 2:
              g.r_skp = t_rw
            else:
              g.r_skp = g.n_whl // 4
          elif f_data['drows'] == 'r':
            g.r_skp = rng.integers(1,  g.n_whl // 4 + 1)
          else:
            if not g.u_again:
              g.r_skp = 1
      else:
        if not g.u_again:
          g.r_skp = 0

I also had to make init_curve() deal with the use again global variable. That’s where the other curve parameters get generated. So, need to turn that off if appropriate. I didn’t want to do that in the routing code. And, simple enough and one time. Not to mention that the routing code was employing the shape return value. In fact, this approach meant that nothing had to change in main.py. A nice bonus.

def init_curve():
  if not g.u_again:
    # intiialize and generate curve data
    # 'k' fold symmetry
    g.k_f = rng.integers(2, g.n_whl+1)
    # congruency vs k_f
    g.cgv = rng.integers(1, g.k_f)
    # widths of none circular shapes or diameter of circle
    g.wds = get_radii(g.n_whl)
    # heights of none circular shapes
    g.hts = get_radii(g.n_whl)
    # get rid of the imaginary unit if present
    g.r_wds = [max(np.real(rd), np.imag(rd)) for rd in g.wds]
    g.r_hts = [max(np.real(rd), np.imag(rd)) for rd in g.hts]
    # get the actually frequencies and congruency
    g.cgv, g.freqs = get_freqs(nbr_w=g.n_whl, kf=g.k_f, mcg=g.cgv)

    splt.set_spiro(g.freqs, g.wds, g.hts, nbr_t=g.t_pts)

  if len(g.su) == 1:
    t_xs, t_ys = splt.mk_curve(g.rds, shp=g.su)
    shape = f"{splt.shp_nm[g.su].lower()} ({g.su})"
  else:
    shape = g.su
    t_xs, t_ys, shape = splt.mk_rnd_curve(g.rds, su=g.su)

  return t_xs, t_ys, shape

Page Templates

Modified disp_image.html slightly to indicate when the same/previous curve data was being used again. Which of course did mean I had to modify the route code in main.py. Won’t bother showing the changes as, per normal, pretty simple and straightforward. I also added some text to get_curve.html to let user know how to reuse a curve’s data.

Example

Okay a few images to show the same curve drawn again with a different colour map. One set for basic and one for gnarly. Again just to show that the modification appears to work as desired.

Here’s the two basic images.

screen shot of basic spirograph image using a user selected colour map screen shot of basic spirograph image reusing the previous curve data for a new image

And, the gnarly variation (differnt curve).

screen shot of gnarly spirograph image using a user selected colour map screen shot of gnarly spirograph image reusing the previous curve data for a new image

And, two different variations using the same curve data: a basic spirograph followed by a gnarly variation.

screen shot of basic spirograph image using a user selected colour map screen shot of gnarly spirograph image reusing the previous curve data for a basic image

Whew! Wasn’t sure that would work. Not necessarily obvious those two are using the same underlying curve data. So, I generated another basic image using the same data, and it matched the first one. Well except for the colour and the line width.

Done

Looks like I am going to be publishing another short post. But, one post, one subject does make some sense. And, code plus images—a tasty treat.

Enjoy your coding time, may it be mostly peaceful.