Search code examples
pythonpython-3.xtkintercjkkanji

Making a user-friendly input for subdividing a square into coordinates


I am working on a program (in Python) that involves cutting a square(s) into smaller pieces. The user has to enter a 'code', which the program will automatically convert into the coordinates for each individual rectangle. Each rectangle also has a value associated with it.

So far, I came up with the following code:

1                         # the entire square
0.5;0.5                   # square split in half from top to bottom
1:0.5,0.5                 # square split in half from side to side
0.5;0.5:0.15,0.35,0.5     # square split in half from top to bottom, with the right side being further subdivided

Crude representation of examples above drawn with MS Paint

The ; handles divisions from left to right, and the :+, handle subdividions from top to bottom.

The problem I am running into is that with this current method it is impossible to represent something relatively simple:

Impossible to represent figures

The only thing I can think of to solve this would be some kind of recursive algorithm that will go through some kind of code and generate the coordinates when prompted. It is a priority that this remains as user-friendly as possible, as these codes may be inputted many times and a confusing/lengthy code will defeat the purpose of it (could just enter a nested dict/list). I still cannot wrap my head around how something like this would even work...

Another solution would be to use sone kind of tkinter GUI. I am using it for my project and as the input for this code, so some kind of interactive box (like those sites where there is a slider in the middle and it shows you the before & after?) would also work.

Cheers!

NOTE 0:

This part of the program handles splitting Japanese Kanji (漢字) into constituent radicals/parts. Most Kanji can already be represented by my solution, but there are lots that cannot! The characters are displayed on a tkinter Canvas object, with rectangles drawn behind each radical. The user selects a number of these radicals with the mouse (I have already done this part).

NOTE 1:

As I mentioned, each rectangle has an assigned value to it which has a default value. This meant in my attempt before converting into coordinates I used a nested list to store values:

[[0.5,[[1,1]]],[0.5,[[0.15,1],[0.35,1],[0.5,1]]]] # generated from the fourth example
          ^ assigmed value ^        ^       ^

The coordinates were calculated based on the sidelength of the Canvas object.

NOTE 2:

Although not strictly necessary, but a way to combine these rectangles within the code/with the tkinter widget would be extremely useful, as some radicals can have weird shapes (like an L) or even wrap around everything (like 口).


Solution

  • This is kind of hard to explain so play around with the examples or give me more shapes to encode. Also you can save/load this encoding method using python's built-in json library.

    import tkinter as tk
    
    
    def cut(direction, cuts, left, right, top, bottom):
        if direction == "v":
            start, end = left, right
        elif direction == "h":
            start, end = top, bottom
        else:
            raise ValueError("Direction must be vertical/horizontal ('v' or 's')")
    
        total = 0
        last_loc = 0
        for fraction, sub_cuts in cuts:
            total += fraction
            loc = start + (end-start)*total
    
            # Calculate the (x1,y1), (x2,y2) of the line that will be displayed
            if direction == "v":
                x1 = x2 = loc
                y1, y2 = top, bottom
                cut("h", sub_cuts, last_loc, loc, top, bottom) # recursively cut
            elif direction == "h":
                y1 = y2 = loc
                x1, x2 = left, right
                cut("v", sub_cuts, left, right, last_loc, loc) # recursively cut
    
            canvas.create_line(x1, y1, x2, y2) # create the line
            last_loc = loc
    
    
    WIDTH = 400
    HEIGHT = 400
    
    root = tk.Tk()
    canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
    canvas.pack()
    
    
    # No cuts
    cuts = []
    
    # Cut it vertically 50%
    cuts = [(0.5,[])]
    
    # Cut it 50% vertically and for the left small rectangle cut it horizontally 50%
    cuts = [(0.5, [(0.5,[])])]
    
    # Cut it 50% vertically and leave the left part.
    # The right part gets cut twice more at 15% and at (15+35)%
    cuts = [(0.5,[]), (0.5, [(0.15,[]), (0.35,[])])]
    
    # Cut it vertically right at the end (does nothing - except now we can cut horizontally)
    # Then cut it hotizontally at 50%. The top part gets cut vertically at 50%
    cuts = [(1, [(0.5, [(0.5, [])])])]
    
    # Cut 50% vertically and do nothing right the left part.
    # The right part (other 50% of the total area) gets cut horizontally at 50%
    # And the top part of that gets cuts 50% vertically again
    cuts = [(0.5,[]), (0.5,[(0.5,[(0.5,[])])])]
    
    cut("v", cuts, 0, WIDTH, 0, HEIGHT)
    

    First, I noticed that you make (multiple) vertical cuts and then (multiple) horizontal cuts. You alternate between vertical and horizontal cuts. So for each cut, my list stores a fractions as well as a list of further cuts in the opposite direction.

    So if you want to cut it into 3rds (vertically), you would use [(1/3,[]), (1/3,[])] (making 2 cuts at 1/3 each and using an empty list to represent no further cuts).

    To make cuts into 3rds (horizontally), you would use the same but wrap it inside [(1, <list from previous example>)]. That makes a cut at the far right (basically doing nothing) and then cutting it horizontally.

    As I said, it's kind of hard to explain but feel free to give me more examples which I can convert so you can get it.

    Edit: There is a slight problem when you cut at the very end (vertically or horizontally), it still shows a line. That is because in the physical realm, you can't really make cuts right at the edge of something (unlike the mathematical realm). To fix this, you can encode those fractions using None (representing the last sub-rectangle) then just skip canvas.create_line after replacing the franction variable with 1-total.

    Edit 2: Extra examples:

    Cut into a grid of 9:

    # Cut vertically into 3rds and then in sub_cuts cut horizontally into 3rds again
    sub_cuts = [(1/3,[]), (1/3,[])]
    cuts = [(1/3,sub_cuts), (1/3,sub_cuts), (1/3,sub_cuts)]
    

    Cut into a 2x3 grid:

    # Cut vertically into 3rds and then in sub_cuts cut horizontally into 2 halfs
    sub_cuts = [(1/2,[])]
    cuts = [(1/3,sub_cuts), (1/3,sub_cuts), (1/3,sub_cuts)]
    

    Cut into a 3x2 grid:

    # Cut vertically into halfs and then in sub_cuts cut horizontally into 3rds
    sub_cuts = [(1/3,[]), (1/3,[])]
    cuts = [(1/2,sub_cuts), (1/2,sub_cuts)]
    

    A more complex example:

    third_sub_cuts = [(1/3,[])]*2
    sixth_sub_cuts = [(1/6,[])]*5
    cuts = [(0.25,sixth_sub_cuts), (0.5,third_sub_cuts), (0.25,sixth_sub_cuts)]
    

    Edit 3: using rectangles instead of lines:

    import tkinter as tk
    
    
    colours = iter(["red", "green", "blue", "cyan", "orange", "yellow",
                    "purple", "pink"])
    
    
    def cut(direction, cuts, left, right, top, bottom):
        if direction == "v":
            start, end = left, right
        elif direction == "h":
            start, end = top, bottom
        else:
            raise ValueError("Direction must be vertical/horizontal ('v' or 's')")
    
        total = 0
        last_loc = 0
        for fraction, sub_cuts in cuts:
            total += fraction
            loc = start + (end-start)*total
    
            # Calculate the (x1,y1), (x2,y2) of the line that will be displayed
            if direction == "v":
                x1, x2 = last_loc, loc
                y1, y2 = top, bottom
                cut("h", sub_cuts, last_loc, loc, top, bottom) # recursively cut
            elif direction == "h":
                y1, y2 = last_loc, loc
                x1, x2 = left, right
                cut("v", sub_cuts, left, right, last_loc, loc) # recursively cut
    
            id = canvas.create_rectangle(x1, y1, x2, y2, fill=next(colours),
                                         outline="")
            canvas.lower(id)
            last_loc = loc
    
    
    WIDTH = 400
    HEIGHT = 400
    
    root = tk.Tk()
    canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT)
    canvas.pack()
    
    cuts = [(0.5,[]), (0.5,[(0.5,[(0.5,[])])])]
    
    cut("v", cuts, 0, WIDTH, 0, HEIGHT)