-
SinterPixels Demos Part 5: A Tiling with Rotatable Square Tiles
My new app, SinterPixels, is in the App Store. For the fifth installment in the series of examples of what one can do with SinterPixels, I’ll show how simple it can be to use SinterPixels’ “layer” object to make a tiling. Here’s what will be produced by a 19 line script:

This image is a 8 by 10 array of tiles, each of which is a square 48 pixels. Each tile looks like this:

This is the script that makes this 48 by 48 pixel image in a document of its own, and makes a copy of its data in PNG format:
tell application “SinterPixels”
set tileDoc to make new document with properties {name:“Tile”, height:48, width:48}
tell tileDoc
set color of every pixel to green
make new circle with properties {position:{24, 24}, radius:24, color:blue, fill color:clear, line width:4}
make new circle with properties {position:{-24, -24}, radius:24, color:blue, fill color:clear, line width:4}
set tilePNG to get PNG data
end tell
end tell
Then we make a second document for assembling the 80 tiles that will make the pattern:
tell application “SinterPixels”
set mosaicDoc to make new document with properties {name:“Mosaic”, height:384, width:480}
end tell
Now we go through a double loop, 10 times horizontally for x, and 8 times vertically for y. Each time through the loop, the script makes a new layer with the PNG data we copied in the first step. A “layer” in SInterPixels might be different from the “layer” some might be familiar with from an app like Photoshop. In this case, the layer itself has a size of 48 by 48, and like any other shape, one can set its position and rotation. So, for each iteration, the tile (i.e., the layer) gets put in the right position, and, randomly, a rotation is applied.
tell application “SinterPixels”
b> new document with properties {name:“Mosaic”, height:384, width:480}repeat with x from 1 to 10
repeat with y from 1 to 8
set newPosition to {(x * 48) - 264, (y * 48) - 216}
set newRotation to some item of {0, 90}
tell mosaicDoc
set nthLayer to make new layer with properties {image data:tilePNG, rotation:newRotation, position:newPosition}
end tell
end repeat
end repeat
end tell
Here’s the whole whole 19 line script that makes an image like the one at the top of this post.
tell application “SinterPixels”
set tileDoc to make new document with properties {name:“Tile”, height:48, width:48}
tell tileDoc
set color of every pixel to green
make new circle with properties {position:{24, 24}, radius:24, color:blue, fill color:clear, line width:4}
make new circle with properties {position:{-24, -24}, radius:24, color:blue, fill color:clear, line width:4}
set tilePNG to get PNG data
end tell
set mosaicDoc to make new document with properties {name:“Mosaic”, height:384, width:480}
repeat with x from 1 to 10
repeat with y from 1 to 8
set newPosition to {(x * 48) - 264, (y * 48) - 216}
set newRotation to some item of {0, 90}
tell mosaicDoc
set nthLayer to make new layer with properties {image data:tilePNG, rotation:newRotation, position:newPosition}
end tell
end repeat
end repeat
end tell
-
SinterPixels Demos Part 4: A Randomly Asymmetric Leaf

My new app, SinterPixels, is in the App Store. For the fourth installment in the series of examples of what one can do with SinterPixels, let’s revisit yesterday’s post, where we explored how to draw a simple, symmetric leaf. Today, we’ll expand on that simple leaf, and make it a bit more organic, by introducing some randomness in the parameters we use for drawing the lines.
First, lets introduce a function that gets a random number between -1.0 and 1.0:
to randomFraction()
return (random number from -100 to 100) / 100.0
end randomFraction
Next, lets write a function that makes the anchor data and color for a single line that we use to draw a leaf – we’ll draw 11 of these
to leafPath(tip, px, py, slp, col)
using terms from application "SinterPixels"
set p1 to {position:{0, 0}, slope:slp}
set p2 to {position:{px, py}, slope:0}
set p3 to {position:{tip, 0}, slope:0}
return {pts:{p1, p2, p3}, clr:col}
end using terms from
end leafPath
When we call this function, we pass in 5 values. tip is the length of the leaf, in pixels. px is x value at the highest (or lowest) point on the arc – where the slope of the path is zero. py is the y coordinate at that point. slp is the slope of the line where the path begins, on the left. and col is the color. This function returns the basic information needed to draw a single path, of which we’ll draw 11 to make a leaf shape.
Now, let’s write a function that will generate all 11 of our paths for a leaf. This function will need a bit more info. The tip and px values are the same as in the previous function. But now, the topmost and the bottommost paths will have different values, a py (positive y) value and a ny (negative y) value. Yesterday, in our symmetric leaf, py and ny were both just the same as px. And, we need two slopes, one for the top path (pSlp) and one for the bottom path (nSlp) . Yesterday, the slopes were 1 and -1, which made things simple:
to allLeafPaths(tip, px, py, ny, pslp, nSlp)
set topLeafPath to leafPath(tip, px, py, pslp, {0.1, 0.7, 0.1})
set bottomLeafPath to leafPath(tip, px, ny, nSlp, {0.1, 0.7, 0.1})
set centerLeafPath to leafPath(tip, px, (ny + py) / 2, pslp + nSlp, {0.25, 0.8, 0.25})
set allPaths to {topLeafPath, centerLeafPath, bottomLeafPath}
repeat with n in {1, 2, 3, 4, 6, 7, 8, 9}
set m to 10 - n
set minorPath to leafPath(tip, px, (n * ny + m * py) / 10, (m * pslp + n * nSlp) / 10, {0.6, 1.9, 0.6})
set allPaths to allPaths & {minorPath}
end repeat
return allPaths
end allLeafPaths
Here, we make three paths special – the top, center and bottom paths. Then, in the loop, we create eight paths evenly spaced between them – four between top and center, four between center and bottom.
Next, we need a function that will call into that allLeafPaths() function, supplying values with a little bit of randomness tossed in. Here’s how we do that:
to leafPathsWithRandomParams(sz)
set quarterSize to sz * 0.25
set dpy to quarterSize * 0.3 * (my randomFraction())
set dny to quarterSize * 0.3 * (my randomFraction())
set dx to quarterSize * (my randomFraction())
set py to dpy + quarterSize
set ny to dny - quarterSize
set tip to sz + dx
set pSlope to py / quarterSize
set nSlope to ny / quarterSize
return my allLeafPaths(tip, quarterSize, py, ny, pSlope, nSlope)
end leafPathsWithRandomParams
The three values, dpy, dny and dx are ‘delta’ values – that’s how much we are going to change the path anchor positions by. The two slope values are set based on the randomized py and ny values.
And, almost at the end, we write a function that actually calls leafPathsWithRandomParams() and draws the paths. This function draws all the paths at the position specified
to makeALeaf(pos)
tell application "SinterPixels"
tell document 1
set myPaths to my leafPathsWithRandomParams(260)
repeat with nthPath in myPaths
make new path with properties {position:pos, anchor data:pts of nthPath, closed:false, fill color:clear, color:clr of nthPath}
end repeat
end tell
end tell
end makeALeaf
Now, to show off that each leaf is its own special someone, lets draw five of them, each at a different position, and see how they differ:
makeALeaf({-340, 120})
makeALeaf({20, 120})
makeALeaf({-160, 0})
makeALeaf({-340, -120})
makeALeaf({20, -120})

To generate the image like the one at the top of this post, just run that in a loop 5 times:
repeat 5 times
makeALeaf({-340, 120})
makeALeaf({20, 120})
makeALeaf({-160, 0})
makeALeaf({-340, -120})
makeALeaf({20, -120})
end repeat
-
SinterPixels Demos Part 3: A Leaf

My new app, SinterPixels, is in the App Store. For the third installment in the series of examples of what one can do with SinterPixels, I’ll go through what’s required to make this leaf.
A basic shape in SinterPixels is the path. This image is just eleven paths. Each path is composed of path anchors – a line, specifically a bezier path, is drawn to connect the anchors. In SinterPixels, each of the anchors can be specified by a position and a slope. The upper edge of the leaf is a single path, which we can make like this:

tell document 1 of application “SinterPixels”
set p1 to {position:{0, 0}, slope:1}
set p2 to {position:{50, 50}, slope:0}
set p3 to {position:{200, 0}, slope:0}
make new path with properties {position:{-100, 0}, anchor data:{p1, p2, p3}, closed:false, fill color:clear, color:{0, 0.7, 0}}
end tell
In this script, we are making a path with three anchor points – the first one, on the left, has a slope of 1. The second one, at the top of the curve, has a slope of zero. The third one, at the right, also has a slope of zero.To make the leaf, we’ll draw a similar path 11 times. The script goes through a loop from 5 to -5, and makes a new path each time through the loop. For each path, we change the slope at the first point and the position of the second point, and for the first, last and middle paths, we’ll draw the path in a darker color.
tell document 1 of application “SinterPixels”
set p3 to {position:{200, 0}, slope:0}
repeat with n from -5 to 5
set p1 to {position:{0, 0}, slope:n / 5}
set p2 to {position:{50, n * 10}, slope:0}
if n is in {-5, 0, 5} then
set col to {0, 0.7, 0}
else
set col to {0.4, 1.0, 0.4}
end if
make new path with properties {position:{-100, 0}, anchor data:{p1, p2, p3}, closed:false, fill color:clear, color:col}
end repeat
end tell
And that makes the image at the top of the post.
For the next installment, we’ll tweak this script just a little bit, so that it generates a slightly different leaf every time. I hope you check out SinterPixels in the app store, or look at all the user documentation at the SinterPixels User Docs Github
-
SinterPixels Demos, Part 2: A Gear Shape

My new app, SinterPIxels, is in the App Store. For the second installment in the series of examples of what one can do with SinterPixels, I’ll go through what’s required to make this gear shape.
One of the basic shapes in SinterPixels is the polygon. This gear is just a polygon, where the outer edge is roughly a circle, but with notches for each tooth.
A polygon can be defined by a sequence of radii, so for example, the sequence {50, 50, 50} just creates a triangle, three points each with a radius from the center of 50:
tell application "SinterPixels"
tell document 1
make new polygon with properties {radii:{50, 50, 50}}
end tell
end tell

If we use 15 vertexes, and alter the radii with the pattern “long, long, short”, it produces an asterisk:
tell application "SinterPixels"
tell document 1
make new polygon with properties {radii:{50, 50, 20, 50, 50, 20, 50, 50, 20, 50, 50, 20, 50, 50, 20}, position:{0, 0}}
end tell
end tell

Let’s extend that idea to make our gear, but to generate the list of radii, lets write a helper function that takes in a number of teeth, a radius and a tooth depth
to gearRadii(numberOfTeeth, rad, depth)
set rList to {}
repeat with s from 1 to numberOfTeeth
set rList to rList & {rad, rad, rad, rad}
set rList to rList & {(rad - depth), (rad - depth)}
end repeat
return rList
end gearRadii
Now, our script can get a list of radii for lots of different gears, and just make a polygon. Lihe the circles in our previous demo, polygons have color, fill color, and line width properties, so we can set exactly the colors we want:set radiiList to my gearRadii(9, 60, 12)
tell application "SinterPixels"
tell document 1
make new polygon with properties {radii:radiiList, color:blue, fill color:{0.8, 1.0, 0.8}, line width:3}
end tell
end tell
This script generates the shape at the top of the post!Hope you try our SinterPIxels on the App Store, and check out all the user documentation at the github repo.
-
SinterPixels Demos, Part 1: Growing Circles Movie
I have a new app in the App Store: SinterPIxels For the next few weeks, I’ll be writing posts with examples of what SinterPixels can do. Today is the first installment in the series: How to generate this movie:
Note: for some reason there's a gray overlay on the video player which I can't figure out. Here's a link to the generated .mov file
SinterPixels is all about writing a script to generate graphics. So for example, a new circle can be generated with:
make new circle
circles have properties like color, fill color, line width and radius, so these can be specified when you make one like this:
make new circle with properties { color: blue, fill color: clear, line width: 3, radius 20}
That will make a blue circle with no fill color of radius 20 with a line width of 3, If you don’t specify one of the properties, a random one will get used.
To make a circle grow just a little bit, we can get the previous radius and set it to a slightly larger value:
set oldRadius to (get radius of the first circle)
set the radius of the first circle to oldRadius + 1
One of the features of SinterPixels is the ability to record movies. There are custom commands start, record movie frame, and stop that define each frame precisely. When you stop filming, you can specify where to save the file and a filename.
Combining all these in a single script can result in this movie which shows circles growing and disappearing:
Here’s the complete 22 line script to generate this movie:
tell application "SinterPixels"
set theDoc to make new document with properties {width:480, height:320}
start filming of document 1
repeat 30 times
tell document 1
make new circle with properties {line width:4, fill color:clear}
set circleCount to count circles
repeat with c from circleCount to 1 by -1
set oldr to get radius of circle c
set radius of circle c to oldr + 1
set oldlw to line width of circle c
if oldlw is less than 0.5 then
delete circle c
else
set line width of circle c to (oldlw - 0.5)
end if
end repeat
record movie frame duration 2
end tell
end repeat
stop filming of document 1 saving in documents folder filename "Demo.mov"
end tell
For more examples and complete user documentation, see this link: SinterPixels UserDoocs Github
-
SinterPixels in the App Store
Big news today – my macOS scriptable app in on the App Store. It’s called SinterPixels.

The app icon is itself made in SinterPixels, and as an introduction to what the app does, here’s the script which makes it:
to newSquare(doca, len, pos)
tell application "SinterPixels"
tell doca
make new polygon with properties {radius:len / 1.414, position:pos, line width:0, fill color:white, rotation:45, vertex count:4}
end tell
end tell
end newSquare
to newCircle(doca, rad, pos)
tell application "SinterPixels"
tell doca
set newC to make new circle with properties {radius:rad, position:pos, line width:0, fill color:white}
end tell
end tell
return newC
end newCircle
to makeIconBackground()
my newSquare(iconDoc, 512, {256, 0})
my newSquare(iconDoc, 512, {-256, 0})
my newSquare(iconDoc, 512, {0, 256})
my newSquare(iconDoc, 512, {0, -256})
my newCircle(iconDoc, 256, {256, -256})
my newCircle(iconDoc, 256, {-256, -256})
my newCircle(iconDoc, 256, {256, 256})
my newCircle(iconDoc, 256, {-256, 256})
end makeIconBackground
tell application "SinterPixels"
close every document without saving
set iconDoc to make new document with properties {height:1024, width:1024}
my makeIconBackground()
tell iconDoc
set pPoly to make new polygon with properties {position:{0, 0}, vertex:{{-216, -472}, {394, -472}, {512, 150}, {-98, 150}}, fill color:{1.0, 0.29, 0.29}, line width:0}
end tell
set sCirc to my newCircle(iconDoc, 380, {-130, 110})
set fill color of sCirc to {0.31, 0.31, 0.93}
set sCirc to my newCircle(iconDoc, 200, {220, -220})
set fill color of sCirc to {1.0, 0.29, 0.29}
tell iconDoc
set sText to make new text shape with properties {position:{-124, 80}, fill color:black, line width:0, font:"Helvetica", bold:true, text content:"S", font size:542}
set pText to make new text shape with properties {position:{140, -91}, fill color:black, line width:0, font:"Helvetica", bold:true, italic:true, text content:"p", font size:542}
end tell
end tell
-
Karaoke videos
I am having a lot of fun making demos for my soon-to-be-released scriptable drawing app. Here’s a video that’s the output from a ~200 line AppleScript – its the lyrics for “Take me out to the ball game” in Karaoke format:
-
A Scriptable Drawing app
For many years, the most flexible scriptable drawing app was Intaglio, but it hasn’t been kept up.
Launching it for the first time in a while, it is showing its age:
And speaking of scriptable drawing apps, stay tuned…

-
Engineering the Arts
There’s some deep comedy imagining the software engineering process as applied to art or music or writing. Would an artist publish a draft of a new painting on GitHub and open it up to pull requests from the community? Would a fiction writer begin a new project by first writing the unit tests that the new novel needs to pass? Would a musician develop a battery of A/B tests on an album and ship “remastered” versions every two weeks? If you find these ideas absurd, it’s interesting to ponder why that is. And if you don’t find these ideas absurd, it’s a wonder why anyone would want to read your poetry / listen to your music / see your art.
This is a post about AI.
-
Silly Lightning Talk
Years ago I did a talk at Seattle Xcoders regarding variable naming and Noun Piles. After years of procrastination, here’s a cleaned up version of that talk:
-
AppleEvent Manager does not respect the kAEDontExecute flag of NSAppleEventDescriptor.SendOptions:
[UPDATE: I can’t reproduce this in a sample app, so the .dontExecute flag is sometimes respected, but something my app is doing is making things strange]
This is FB21695413:
My AppleScriptable macOS app is recordable. Occasionally, I have a UI action performed by the user that I handle internally, and I send a corresponding AppleEvent with the SendOptions flag .dontExecute (was kAEDontExecute) so that AppleScript recording will see the action and record it.
With Sequoia 15.7.3 and Tahoe 26.1, I am seeing events sent with the .dontExecute flag delivered back to the application invoking the appropriate AppleEvent handler AS IF the .dontExecute flag was not specified.
For example, if in my app the user does an action to create a new ‘widget’, I send an apple event of the form
make new widget at end of document 1with the .dontExecute option, but, the AppleEvent handler installed for core/crel gets called anyway, and as a result, 2 widgets get made.
I expect (and previously the behavior was) that if I specify .dontExecute, the AppleEvent will be forwarded to any registered recording agents, but not actually delivered to the app itself.
For the expected behavior, see Inside Macintosh: Interapplication Communication / Chapter 9 - Recording Apple Events / Factoring Your Application for Recording “Sending Apple Events Without Executing Them”
-
New Band discovery: Athena
This week’s music discovery: Athena, a Turkish ska/punk/polka-ish band
Their sound reminds me of Brave Combo’s “The Process” mixed with a little The Budos Band. Here’s the song that got me hooked: Yaşamak Var Ya
-
Sinterapt update
An update on Sinterapt, my APT data analysis app.
It’s at the point where it is ready to Beta test in TestFlight. Let me know if this kind of thing interests you, and I’ll add you to the TestFlight list.
An example of the kind of thing I’m working on now: I’ve just today hooked up UI elements to the “cylinder” and “plane” geometries to control their radius or thickness properties, and the UI gives real-time feedback. Here’s an example of the cylinder element and the real time feedback shown in the graphics window and the 1D profile:
-
America has always been screwed up
Sometimes the news is overwhelming and awful and directs me towards just checking out entirely, but a salve comes from an unexpected place and reminds me that things have always been terrible and today is not an aberration, and oddly, the knowledge that America has always been screwed up gives me a sort of vaccine to today’s sickness.
For whatever reason, two weeks ago, the town of Grimsby (Lincolnshire, England) came up in conversation with my family, and I offhandedly remembered that Elton John had a song about Grimsby on his album Caribou (1976), and today I decided to listen to the album while I was coding. Dang if one of the album tracks, “Ticking”, isn’t about a gun nut going berserk in the USA and leaving death in his wake.
And immediately, a litany of songs of senseless violence sprang to mind. Boomtown Rats’ “I Don’t Like Mondays” (1979). 10000 Maniacs’ “Jubilee” (1989)…. (perhaps you have your own memory to add to the list — please comment if you do).
I am conflicted to see in retrospect how I grew up in a stew of senseless violence and internalized it all as just another part of being American.
Fuck that.
-
JINX
Finished Matt Gemmell’s JINX a few weeks ago and forgot to write about it. Thoroughly enjoyed it.
Compared to the first two Kestrel books, Matt seems to have upped his writing to a level where I can really recommend this book to folks as a standout. The dialogue is witty and understated, often based in the contrasts between the the different personalities – when I wasn’t paying full attention, I found myself rereading a few lines to fully appreciate the banter between the characters.
I’m really looking forward to what comes next!
d
-
“Corporate Engineers”
Brent writes a love letter to his team at Audible: My Wildly Incorrect Bias About Corporate Engineers and Gus responds in turn very kindly: Brent on Biases and Retirement
I share Gus’ amusement, but with a few additional angles. As a longtime Microsoft developer, I was exactly the kind of “Corporate Engineer” Brent might have a bias about, and, I hope, exactly the kind of engineer who had some subset of talents that Brent might appreciate and enjoy working with. Hanging out with Brent and Gus and the rest of the Xcoders family has been an enjoyable and fulfilling part of my career, so this discussion really makes me smile.
And Gus’ words make me smile even more so, as he adds “Only crazy people are willing to put up with having to file business taxes, mess with social security, find healthcare, deal with all the stuff you have to handle to be indie.” Now that I am in the position of having to file business taxes, mess with social security, find healthcare, and deal with all the stuff, I feel even better about my decision to quit Microsoft. The indie side of my work now is much more satisfying.
Hope I see you all soon, I’m very sorry to miss your retirement party, Brent!
-
**Ehime-Ken mascot Mican spotted in Barcelona! ** (Asian Origins Muntaner)

-
Top 10 takeaways from #NSSpain2024
1) Logroño is an excellent place (Luis!)
2) AppleVisionPro accessibility: be prepared to handle “Reduce Motion” and “Avoid head-locked anchors” (Robin Kanatzar)
3) Apple Intelligence leans on AppIntents more than expected (Matthew Cassinelli)
4) SharePlay is not just for Play (Vanessa Furtado)
5) Apple’s Accelerate framework is more competitive with Metal than expected (Francesco Marini)
6) Swift Macros are more involved than expected, but also more powerful (Daniel Steinberg)
7) Vapor is ready for prime time (Adolfo Vera)
8) Avoiding overlapping compositing passes can really improve perf (Tim Oliver)
9) labeled loops allows targeted break statements, ‘case let’ can be useful in more contexts than expected (Nick Lockwood)
10) Seriously, Logroño is an excellent place (So many more great takeaways – Thanks everyone!)
-
#nsspain2024 みんなさん、なかまになりましょう!
-
For the first time ever, “Facebook Memories” produced something that actually made me laugh. I had forgotten about this post completely (from July 31, 2017):
“Did you know ‘English Breakfast’ means sausage, bacon, fried egg and beans. No wonder English Breakfast Tea is so disgusting – Those flavors don’t belong in tea!”
-
Iceland has the best libraries

-
Sabaki Girls at Uwajimayaya Bellevue!

-
I found the most Japanese product ever - Aggretsuko Junmai Sake

-
This weekend I am being an Mican / fish Girls / Ehime Food Fair groupie (grouper?)

-
Mican at Uwajimaya Seattle!

subscribe via RSS