### Author Topic: Happy Easter!  (Read 484 times)

0 Members and 1 Guest are viewing this topic.

#### Yacine

• Hero Member
• Posts: 2952
##### Happy Easter!
« on: April 19, 2022, 08:29:19 AM »
Happy Easter!
Feel happy for this title, my first idea was "He died for your sins". This would not have matched the mood of my topic.

So I felt like I ought to do something for Easter and having began being "artsy" with my Piet Mondrian series, I thought I continue on this path.
So the topic today are ornaments. I googled a lot of pictures for inspiration.

The first element needed would be a nice spline with smooth curvature transitions. The builtin tool for splines is not only difficult to handle, but the documentation of splines and bezier curves is bad. I haven't for instance found a definition of the knot cell in a spline row.
So I built a helper shape consisting of a straight line, with control points that I used as array with the drawspline function.

Since the sort order of the points matters I wanted to show their indices. I used smarttags. They display the hint text only when you hover over them, well better than no info at all.
Code
``def draw_constructor():    x1 = W/2 - W/10    x2 = W/2 + W/10    y1 = y2 = H/2    shp = vPg.DrawLine(x1,y1,x2,y2)    shp.AddSection(visSectionProp)    shp.AddSection(visSectionControls)    shp.AddSection(visSectionSmartTag)    row = shp.AddNamedRow(visSectionProp, 'num_points', visTagDefault)    N = 10    shp.Cells('prop.num_points.Label').Formula = chr(34) + 'num_points' + chr(34)    shp.Cells('prop.num_points.Type').Formula = 1    shp.Cells('prop.num_points.Format').Formula = chr(34) + ';'.join([str(i+1) for i in range(N)]) + chr(34)    shp.Cells('prop.num_points').Formula = 1    for i in range(N):        shp.AddRow(visSectionControls, visRowLast, visTagDefault)        shp.Cells('Controls.Row_' + str(i+1) + '.XCon').Formula = 'if(prop.num_points>=' + str(i+1) + ';0;6)'        shp.AddRow(visSectionSmartTag, visRowLast, visTagDefault)        shp.Cells('SmartTags.Row_' + str(i+1)).Formula = 'Controls.Row_' + str(i+1)        shp.Cells('SmartTags.Row_' + str(i+1) + '.Y').Formula = 'Controls.Row_' + str(i+1) + '.Y'        shp.Cells('SmartTags.Row_' + str(i+1) + '.DisplayMode').Formula = 2        shp.Cells('SmartTags.Row_' + str(i+1) + '.Disabled').Formula = 'if(prop.num_points>=' + str(i+1) + ';False;True)'        shp.Cells('SmartTags.Row_' + str(i+1) + '.Description').Formula = chr(34) + str(i+1) + chr(34)    return shp``

And the whole exercise makes only sense if you can randomize the points, so I added an according function.
Code
``def randomize_points(shp, N = 10, dy = None):    len_ = ((shp.Cells('EndX').ResultIU - shp.Cells('BeginX').ResultIU) ** 2 + (shp.Cells('EndY').ResultIU - shp.Cells('BeginY').ResultIU) ** 2) ** 0.5    d = len_ / N    if not dy:        dy = d    distribute_points(shp, N)    for i in range(N):        shp.Cells('Controls.Row_' + str(i+1)).Formula = shp.Cells('Controls.Row_' + str(i+1)).ResultIU + d * (random.uniform(0,1))        shp.Cells('Controls.Row_' + str(i+1) + '.Y').Formula =  dy * (0.5 - random.uniform(0,1))``

The distribute_points function does what its name suggests. I place first the points in equal distances on the line, then I randomize their position.
Code
``def distribute_points(shp, N = 10):    len_ = ((shp.Cells('EndX').ResultIU - shp.Cells('BeginX').ResultIU) ** 2 + (shp.Cells('EndY').ResultIU - shp.Cells('BeginY').ResultIU) ** 2) ** 0.5    d = len_ / N    for i in range(N):        shp.Cells('Controls.Row_' + str(i+1)).Formula = d * i        shp.Cells('Controls.Row_' + str(i+1) + '.Y').Formula = d``

Now you may want to not use all the 10 points provided. So you chose the desired number in the props of the helper shape and write a function that returns only the points that are within the chosen range.
(You will recall from the code above, that the points not in use get a "XCon" value of 6, so checking if their value is zero means they are visible.)

Code
``def active_points(shp):    N = 10    L = []    for i in range(N):        if shp.Cells('Controls.Row_' + str(i+1) + '.XCon').ResultStr('') == '0':            L.append([shp.Cells('Controls.Row_' + str(i+1)).ResultIU, shp.Cells('Controls.Row_' + str(i+1) + '.Y').ResultIU])    return L``

The coordinates you get back from your points are relative to their parent - the helper shape.
We need to translate them into absolute ones.
As long as I kept the line horizontal the conversion was easy.
But as soon you inclined it, the formulas broke. I needed to refer to matrix transformations.
Nice topic: https://en.wikipedia.org/wiki/Transformation_matrix
But I cheated and found a ready formula.

You will furthermore need a function to flatten the list of lists, as Visio expects an array of consecutive values xi,yi.

Code
``def ar_points(L):    return [item for sublist in L for item in sublist]def absolutify(shp):    x1 = shp.Cells('BeginX').ResultIU    y1 = shp.Cells('BeginY').ResultIU    x2 = shp.Cells('EndX').ResultIU    y2 = shp.Cells('EndY').ResultIU    a = shp.Cells('Angle').Result(visRadians)    L1 = active_points(shp)    for i,pt in enumerate(L1):        x_ = pt[0] * math.cos(a) - pt[1] * math.sin(a)        y_ = pt[0] * math.sin(a) + pt[1] * math.cos(a)        L1[i] = [x_,y_]    L1 = [[x + x1, y + y1] for [x,y] in L1]            L = [x1,y1] + ar_points(L1) + [x2,y2]    return L``

That was a long introduction. Only now we come to drawing the spline.
And I made two versions of the drawing function. One draws the spline as you prepared it with its control points.
The second scrambles the points before drawing.

Code
``def spline_1(shp):    pts = absolutify(shp)    shp2 = shp.Parent.DrawSpline(pts,0.1,1)    vApp.ActiveWindow.DeselectAll()    vApp.ActiveWindow.Select(shp, visSelect)    return shp2def random_spline_1(shp, dy = None):    randomize_points(shp, len(active_points(shp)),dy)    pts = absolutify(shp)    shp2 = vPg.DrawSpline(pts,0.1,1)    vWin.DeselectAll()    vWin.Select(shp, visSelect)    return shp2``
« Last Edit: April 20, 2022, 11:54:44 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #1 on: April 19, 2022, 08:30:30 AM »

A long journey until this point.
Now we'll draw a bunch of these lines.
We decide to have a unique source (starting point) of the splines.
And it would be nice to randomize the position. for both x and y the randomizer shall decide left/center/right respectively bottom/center/top of the page. That's the "x_dir = random.randint(-1,1)", (same for y).

The source shall also have a distance from the border of the page. That's the offset. "offset = 20 / 25.4", and "x_source = offset + (W-2*offset) * (1+x_dir)/2"

You'll then calculate how long the splines are allowed to be. That's the "amp" stuff.
The allowed angles of the helper shape defining the spline are defined in a kind of decision matrix. Way too complicated, ... too lazy to find a more elegant solution.

You'll then determine a random number of splines (N = random.randint(5,30) ) and draw accordingly.
Determine a new end of the helper shape, draw the splines.
At each drawing of the spline decide randomly whether to add a decoration at the end (a flower).
To influence the ratio of flower to no flower a list of zeros and ones is used (b_draw = random.choice([0]*2+[1]) = 2 zeros and 1 flower = in 1/3 of the cases a spline ends with a flower).

Almost done. We finish by formatting the splines.
The shape is defined by line styles. I've prepared two of them. Pointy with a smaller base (a triangle) and second one as very large and flat oval.
The colours are set by means of randomized values of hue, saturation and luminosity and the use of the HSL function.

Code
``# Anglesangs = [[0,0,0,2*math.pi],       [0,-1,0,math.pi],       [0,1,math.pi,2*math.pi],       [-1,1,-math.pi/2,0],       [-1,0,-math.pi/2,math.pi/2],       [-1,-1,0,math.pi/2],       [1,-1,math.pi/2,math.pi],       [1,0,math.pi/2,math.pi*3/2],       [1,1,math.pi,math.pi*3/2]]def bouquet():    x_dir = random.randint(-1,1)    y_dir = random.randint(-1,1)    print(x_dir,y_dir)    offset = 20 / 25.4    x_source = offset + (W-2*offset) * (1+x_dir)/2    y_source = offset + (H-2*offset) * (1+y_dir)/2    print(x_source,y_source)    # Amplitude    if x_dir == 0:        x_amp = (W-2*offset) / 2    else:        x_amp = W-2*offset    if y_dir == 0:        y_amp = (H-2*offset) / 2    else:        y_amp = H-2*offset    amp = min(x_amp,y_amp)    print('Amplitude ', amp)    ang = [[i[2],i[3]] for i in angs if i[0] == x_dir and i[1] == y_dir][0]    print('Angle ',ang)    N = random.randint(5,30)       shp0.Cells('BeginX').Formula = x_source    shp0.Cells('BeginY').Formula = y_source    vWin.SelectAll()    if vWin.Selection.Count > 0:        vWin.Selection.Delete()        shps = []    shp0.Cells('prop.num_points').Formula = random.randint(1,3)    for i in range(N):        ang2 = random.uniform(ang[0],ang[1])        dr = random.uniform(0.5, 1.0)        x2 = x_source + dr * amp * math.cos(ang2)        y2 = y_source + dr * amp * math.sin(ang2)        #print(ang2,x2,y2)        shp0.Cells('EndX').Formula = x2        shp0.Cells('EndY').Formula = y2        shp = random_spline_1(shp0, dy = random.uniform(0.1,1.5))        shp.Cells('LayerMember').Formula = chr(34) + '1' + chr(34)        # decoration        b_draw = random.choice([0]*2+[1])        if b_draw == 1:            flower(shp0)                shps.append(shp)    shp0.Cells('BeginX').Formula = shp0.Cells('EndX').Formula = W    shp0.Cells('BeginY').Formula = 0    shp0.Cells('EndY').Formula = H    # Format the shapes    hue_med = 65    hue_d = 30    for shp in shps:        pat = random.choice(['leaf','branch'])        shp.Cells('LinePattern').Formula = f'USE("{pat}")'        hue = random.randint(hue_med-hue_d,hue_med+hue_d)        sat = random.randint(100,220)        lum = random.randint(90,180)        hsl = f'HSL({hue};{sat};{lum})'        #print(hsl)        shp.Cells('LineColor').Formula = hsl        shp.SendToBack()``
« Last Edit: April 20, 2022, 11:40:21 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #2 on: April 19, 2022, 08:31:12 AM »
Some words about the flower function.
That's something I added last and is accordingly rudimentary.
I draw some circles around another one.
The center is colored "yellowish", the petals come in two ranges of color - red-orange and bluish. I needed to avoid the greens which are in the middle of the saturation range.
A better solution to circles would have been to prepare petals as masters and insert them. Well, I wanted to finish the job.

Code
``def flower(shp0):    d0 = random.uniform(.2,1.)    d1 =  d0 * random.uniform(0.2,1) #random.uniform(0,10/25.4)    offset = d0    x1 = shp0.Cells('BeginX').ResultIU    y1 = shp0.Cells('BeginY').ResultIU    x2 = shp0.Cells('EndX').ResultIU    y2 = shp0.Cells('EndY').ResultIU        # position of flower    ang = math.atan((x2-x1)/(y2-y1))    x3 = x2 + d0 * math.sin(ang)    y3 = y2 + d0 * math.cos(ang)    center_shp = vPg.DrawOval(x3-d0/2,y3-d0/2,x3+d0/2,y3+d0/2)    grp = center_shp.Group()    vWin.DeselectAll()    num_petals = int((d0 + d1/2)*math.pi / d1)*2    print('num_petals',num_petals)    rad = d0 + d1/2    x5 = grp.Cells('Width').ResultIU / 2    y5 = grp.Cells('Height').ResultIU / 2    petals = []    for i in range(num_petals):        ang4 = 2*math.pi/num_petals * i        x4 =  x5 + rad * math.cos(ang4) * 0.5        y4 =  y5 + rad * math.sin(ang4) * 0.5        shp = grp.DrawOval(x4-d1/2,y4-d1/2,x4+d1/2,y4+d1/2)        shp.Cells('LayerMember').Formula = chr(34) + '1' + chr(34)                        petals.append(shp)    center_shp.BringToFront()    # Formatting    hue = random.randint(30,40)    sat = random.randint(220,240)    lum = random.randint(120,180)    hsl = f'HSL({hue};{sat};{lum})'    center_shp.Cells('FillForegnd').Formula = hsl    center_shp.Cells('LinePattern').Formula = 0    hue_rand = random.randint(0,1)    if hue_rand == 0:        hue = random.randint(0,46)    else:        hue = random.randint(145,239)    sat = random.randint(190,240)    lum = random.randint(110,180)    hsl = f'HSL({hue};{sat};{lum})'    for petal in petals:        petal.Cells('Fillforegnd').Formula = hsl        petal.Cells('LineColor').Formula = 'tint(FillForegnd;-30)'        petal.Cells('LineColorTrans').Formula = '20%'``

As decorations I played also with the idea to draw a spiral at the end of the splines.
Need to get the right end angle of the splines before doing this step.
I was also not happy with the way the size and the number of loops is defined.
So this is a thing for future work.
Code
``d = 0.1a = a_prev = 0da = math.pi / 5loops = 0n_loops = 3max_ = 10000i = 1L = [[0,0]]while a < n_loops * 2 * math.pi:        a += da     r = a * 0.009 * i**2/1000    #print(a)    x = L[i-1][0] + r * math.cos(a)    y = L[i-1][1] + r * math.sin(a)    L.append([x,y])    if a_prev > a:        loops += 1    a_prev = a        i += 1    if i > max_:        breakprint('Loops ', loops)#print(L)B = []for i in L:    B.append(i[0])    B.append(i[1])shp = vPg.DrawPolyline(B,8)shp.Cells('Rounding').Formula = '10mm'vWin.Select(shp, visSelect)vWin.Selection.Join()shp = list(vPg.Shapes)[-1]wh_ratio = shp.Cells('Width').ResultIU / shp.Cells('Height').ResultIUshp.Cells('LockAspect').Formula = 1shp.Cells('Width').Formula = '50 mm'shp.Cells('Height').Formula = shp.Cells('Width').ResultIU / wh_ratioshp.Cells('PinX').Formula = 0shp.Cells('PinY').Formula = 0``
« Last Edit: April 19, 2022, 09:27:55 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #3 on: April 19, 2022, 08:31:31 AM »
And last but not least, you may want to save the results.
Code
``def save_():    rect = vPg.DrawRectangle(0,0,W,H)    rect.SendToBack()    FullPath = vDoc.Path + vDoc.Name.split('.')[0] + '_' + strftime('%Y_%m_%d_%H_%M_%S',localtime()) + '.png'    FullPath=FullPath.replace('"','')    print(FullPath)    visServiceVersion140 = 7    visServiceVersion150 = 8    DiagramServices = vDoc.DiagramServicesEnabled    vDoc.DiagramServicesEnabled = visServiceVersion140 + visServiceVersion150    visRasterUseCustomResolution = 3    visRasterPixelsPerInch = 0    visRasterFitToSourceSize = 2    visRasterInch = 2    visRasterInterlace = 0    visRaster24Bit = 3    visRasterNoRotation = 0    visRasterNoFlip = 0    #------------    vSet = vApp.Settings    vSet.SetRasterExportResolution(visRasterUseCustomResolution, 100, 100, visRasterPixelsPerInch)    vSet.SetRasterExportSize(visRasterFitToSourceSize, 6, 4, visRasterInch)    vSet.RasterExportDataFormat = visRasterInterlace    vSet.RasterExportColorFormat = visRaster24Bit    vSet.RasterExportRotation = visRasterNoRotation    vSet.RasterExportFlip = visRasterNoFlip    vSet.RasterExportBackgroundColor = 16777215    vSet.RasterExportTransparencyColor = 16777215    vSet.RasterExportUseTransparencyColor = False    vWin.Page.Export(FullPath)    #------------    vDoc.DiagramServicesEnabled = DiagramServices            rect.Delete()    return FullPath``

A final thought, I disliked the fact that I re-draw the whole drawing each time. I would have prefered to have an option to keep the parts I like and regenerate only the ones I disliked (eg number of flowers, colors, etc.).

So far for my Easter greetings.
« Last Edit: April 19, 2022, 09:08:12 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #4 on: April 19, 2022, 08:33:08 AM »
My easter bouquets to the members of the forum ...
« Last Edit: April 20, 2022, 11:46:56 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #5 on: April 19, 2022, 08:34:35 AM »
A neat animation of the build process.
Click the image and wait 2 seconds to get the animation started ... (it's a GIF with imperfect start - sorry)
« Last Edit: April 20, 2022, 11:48:55 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #6 on: April 19, 2022, 08:38:50 AM »
A part of my motivation collection from the internet ... and the target of my work.
« Last Edit: April 19, 2022, 09:01:05 AM by Yacine »
Yacine

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #7 on: April 19, 2022, 08:42:07 AM »
... and for those willing to try by themselves, here is the juypter notebook.
« Last Edit: April 19, 2022, 09:00:36 AM by Yacine »
Yacine

#### wapperdude

• Global Moderator
• Hero Member
• Posts: 4373
• Ideas Visio-lized into solutions
##### Re: Happy Easter!
« Reply #8 on: April 19, 2022, 09:06:19 AM »
Happy Easter.

Seems like a lot of coding.  That has to be a labor of love!
Visio 2019 Pro

#### Yacine

• Hero Member
• Posts: 2952
##### Re: Happy Easter!
« Reply #9 on: April 19, 2022, 09:17:57 AM »
Happy Easter.

Seems like a lot of coding.  That has to be a labor of love!

It is indeed, but working with jupyter lab is so fast, it feels like merely slower than your mouse.
If VBA would require 1 time unit, C# would be guessed at 3-5 and Jupyter lab is at 0.1 or less.
Very enjoyable.

You never work at a whole program, but always at small snippets. This keeps you at task.

PS: re-thought your message ... No it is not a lot of coding!
It sums up to less than 100 lines of code. It is very little compared to the results achieved.
« Last Edit: April 20, 2022, 11:59:24 AM by Yacine »
Yacine