tomchuk

a little place for me to write things down so I don't forget them

Pipa Numero Uno

I spent a long time looking for the right cutty. There aren't that many out there, and what I was looking for was fairly specific. The closest thing I found was Stanwell's 1992 Pipe of the Year:

However, I wanted something a little beefier, perhaps a nice oval shank, like my Castello #33:

After trying in vain to find the right pipe, I decided I may as well make exactly what I wanted. I got in touch with Mark Tinsky and asked him for a tapered bowl drilled at around 115° with an uncut 24mm vulcanite rod with drilled airway and turned tenon. He was very helpful, and for a few extra dollars happily and quickly fulfilled my order.

Here's the drilled block of briar he sent me, sanded and wetted on one side to show the grain:

I planned out the shape, going off a printout of the Stanwell pipe:

... and got to cutting:

After about six hours of rasping, filing and sanding, here's the stummel, starting to take shape (note the vulcanite rod in the background):

It took a whole bunch of sanding to get the shape uniform and everything smoothed out. I should add that this was all by hand. I had a Porter Cable belt/disc sander in my cart at Lowes, and my beautiful wife gave me that look: Where the hell are we going to keep that thing in our apartment? So hand sanding it was.

After getting the stummel into pretty decent shape, I threw on the stem, broke out the file, and started working at it:

After a few hours, I had something pretty close in appearance to a proper stem:

After a bunch of fine filing and sanding, the stem and button really started taking shape:

Then came the job of finish sanding: Getting the stem and shank even and smooth and getting every last nick, gouge, ridge and flat spot out of the stummel. Unfortunately, by the time I reached this stage, the sun had set, so no more photos. I will describe the following steps:

  1. Sand the stem and stummel with 120 grit until everything was smooth and uniform.
  2. Give it a once over with 320 grit, paying particularly close attention to the button.
  3. Wipe it down with alcohol to get rid of excess dust, and to highlight imperfections.
  4. More sanding with 320 grit, wiping down with alcohol, to get everything perfectly smooth.
  5. Apply a straight black leather stain and let dry.
  6. Sand down the entire pipe so that the only stain left is highlighting the grain.
  7. Apply a mixture of ~20 parts alcohol with 5 parts black, 1 part red and 1 part yellow stain.
  8. Work through an entire set of 3M Polishing Paper
  9. Hit it with a linen rag loaded with Tripoli compound, then white diamond, then carnauba.
  10. Fire up the heat gun, soften the stem and give it the proper bend.

All that was left was to bask in the glory of my very first pipe:

Starting A VMware guest at logon and mounting a network share

So I've been struggling trying to get my development VM started and the Samba share it exports mapped when I boot up. Last time I set this up, I'd just run the VM through vmrun.exe with a shortcut in Start Menu -> Startup. The problem is that on logon, Windows would attempt to mount the share before the VM was started. Also, I'm not entirely sure what happens to a running VM when you shut down Windows without halting/suspending it first, though it never seemed to be an issue.

The following will start the VM and mount the share in non-persistent mode (won't automatically try and re-mount at logon). It will also remove the mount and suspend the VM on logoff, leading to a faster start-up next time.

Fist create two scripts. I dropped them right in the VM's folder.

dev-start.cmd:

@echo off
"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" -T ws -u thomas start "D:\vm\Dev\Dev.vmx" nogui
ping 10.0.1.199 -n 1 -w 2000 > NUL
net use Y: \\dev\thomas /persistent:no

dev-stop.cmd:

@echo off
net use Y: /delete /Y
ping 10.0.1.199 -n 1 -w 2000 > NUL
"C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe" -T ws -u thomas suspend "D:\vm\Dev\Dev.vmx" soft

In both of the scripts, change the drive letter (Y:), the share (\\dev\thomas), the location of the VMX file (D:\vm\Dev\Dev.vmx) and the username (thomas). This of course depends on a share exported from the guest, DNS or static networking+hosts file entry and bridged networking in VMware. It also requires the same credentials for your Windows account and the share, if this isn't the case, take a peek at the net use docs. Here's the vmrun docs (PDF) as well.

Now to setup these scripts to be run at logon and logoff. We'll do this through Group Policy Editor, as it also allows you to set up logoff scripts. Navigate to: Start -> Run -> gpedit.msc -> User Configuration -> Scripts (Logon/Logoff). Double click Logon, click Add and navigate to where you saved your start script. Do the same for Logoff. This will also work in the Computer Configuration section, but I haven't found a way to allow one to bring the running VM to the foreground.

Enjoy your automatically started VM and mounted network share every time you logon to your computer.

Changing the Default Playback Device on Windows 7 (with less clicking)

I use speakers to listen to music while I work during the day, and a headset to play a little Battlefield 3 in the evening. I've been looking to an alternative to opening up the playback devices control panel every time I make the switch. I found that Dave Amenta had done all the hard work of uncovering the undocumented APIs and writing a console application. I did make a couple changes to his code so that the default playback device would be applied across all roles. Here's the modified code.

Download the above and drop Release\EndPointController.exe somewhere on your %PATH% (I chose C:\Windows\System32) and run it from a command prompt. It'll give you a list of devices and their IDs, remember the IDs for the devices you want shortcuts to.

Right-click and drag EndPointController.exe to your desktop and select Create Shortcuts Here. Right-click the shortcut and choose properties and add a space and the device ID to the end of the Target. You can change the icon as well (Grab audio device icons from %SystemRoot%\system32\mmres.dll). You might as well change Run: to Minimized so the console window doesn't flash when you run the shortcut.

I've also set up AutoHotKey (download AutoHotkey_L, not Basic) to change devices with a keyboard shortcut:

; Cycle through playback devices with Ctrl+Alt+S
^!s::
if not TotalEndPoints {
    TotalEndPoints = 0
    RunWait, %comspec% /c EndPointController > %A_Temp%\endpoints.txt,, Hide
    Loop, Read, %A_Temp%\endpoints.txt
        TotalEndPoints++
    FileDelete, %A_Temp%\endpoints.txt
}
CurrentEndPoint++
CurrentEndPoint := Mod(CurrentEndPoint, TotalEndPoints)
RunWait, %comspec% /c EndPointController %CurrentEndPoint% ,, Hide
return

; Switch to playback device 0 with Ctrl+Alt+0
^!0::
RunWait, %comspec% /c EndPointController 0 ,, Hide
return

; Switch to playback device 1 with Ctrl+Alt+2
^!1::
RunWait, %comspec% /c EndPointController 1 ,, Hide
return

; Switch to playback device 2 with Ctrl+Alt+2
^!2::
RunWait, %comspec% /c EndPointController 2 ,, Hide
return

Solving Mazes with Python

I stumbled across the following ridiculous maze on Reddit. It's been a few years since my last AI class, so I decided to brush of the ol' pathfinding algorithms and take a crack at it. It may look like a gray square, but click it --- you'll see how crazy it really is.

Here's the code. You'll need progressbar and Pillow.

#!/usr/bin/env python

import math
import struct
import sys
from collections import deque
from progressbar import ProgressBar
from PIL import Image

class Maze():

    def __init__(self, image_file):
        self.image = Image.open(image_file)
        self.image = self.image.convert("RGB")
        self.w, self.h = self.image.size
        self.maze = self.image.load()

        self.start = self.end = None
        self._last_was_wall = True
        def check_cell(x,y):
            if any(self.maze[x,y]):
                if self._last_was_wall:
                    if not self.start:
                        self.start = (x,y)
                    elif not self.end:
                        self.end = (x,y)
                    else:
                        raise Exception('More than two openings')
                self._last_was_wall = False
            else:
                self._last_was_wall = True
        for y in range(self.h):        check_cell(0,y) # Left
        for x in range(self.w):        check_cell(x,self.h-1) # Bottom
        for y in range(self.h, 0, -1): check_cell(self.w-1,y-1) # Right
        for x in range(self.w, 0, -1): check_cell(x-1,0) # Top
        del self._last_was_wall

        self.search_depth = self.image.convert("1").histogram()[255]

        self.metadata = {}

    def adjacent(self, xy):
        rv = {}
        x,y = xy
        for x1,y1 in [(x,y-1), (x+1,y), (x,y+1), (x-1,y)]:
            try:
                if any(self.maze[x1,y1]):
                    rv[x1,y1] = self.maze[x1,y1]
            except IndexError:
                pass
        return rv

    def geometric_distance(self, c1, c2):
        return math.sqrt(abs(c1[0]-c2[0])**2 + abs(c1[1]-c2[1])**2)

    def get_heuristic(self, cell):
        h = 0
        try:
            x,y,h = struct.unpack('HHH', self.metadata[cell])
        except:
            pass
        return h

    def get_parents(self, cell):
        rv = []
        try:
            x,y,h = struct.unpack('HHH', self.metadata[cell])
        except (KeyError, struct.error):
            return rv

        while True:
            rv.append((x,y))
            try:
                x,y,h = struct.unpack('HHH', self.metadata[x,y])
            except (KeyError, struct.error):
                return rv
        return rv

    def save(self):
        for xy in self.path:
            self.maze[xy] = (255,0,0)
        self.image.save('solution.png')
        print 'Found path of length: %d' % len(self.path)

    def solve_astar(self):

        search_cell = self.start
        open_set = set()
        open_set.update([search_cell])
        closed_set = set()
        self.astar_done = 0

        pbar = ProgressBar().start()
        while len(open_set) > 0:

            if self.astar_done % 100 == 0:
                pbar.update(100.0*(float(len(closed_set))/float(self.search_depth)))
            self.astar_done += 1

            for cell in self.adjacent(search_cell):
                if cell == self.end:
                    x,y = search_cell
                    self.metadata[cell] = struct.pack('HHH', x, y, 0)
                    self.path = self.get_parents(cell)
                    pbar.finish()
                    self.save()
                    return

                if cell not in closed_set and cell not in open_set:
                    open_set.update([cell])
                    if cell not in self.metadata: self.metadata[cell] = []
                    self.metadata[cell] = struct.pack('HHH', search_cell[0],
                        search_cell[1], len(self.get_parents(cell)) + self.geometric_distance(cell, search_cell))

            closed_set.update([search_cell])
            try:
                open_set.remove(search_cell)
            except KeyError:
                pass
            search_cell = None

            for cell in open_set:
                if search_cell is None:
                    search_cell = cell
                    continue
                if self.get_heuristic(search_cell) > self.get_heuristic(cell):
                    search_cell = cell
        print 'No Solution'

    def _bfs(self):
        queue, enqueued = deque([(None, self.start)]), set([self.start])
        while queue:
            parent, n = queue.popleft()
            self.bfs_done += 1
            yield parent, n
            new = set(self.adjacent(n)) - enqueued
            enqueued |= new
            queue.extend([(n, child) for child in new])

    def solve_bfs(self):
        pbar = ProgressBar().start()
        self.bfs_done = 0
        parents = {}
        for parent, child in self._bfs():
            pbar.update(100.0*(float(self.bfs_done)/float(self.search_depth)))
            parents[child] = parent
            if child == self.end:
                revpath = [self.end]
                while True:
                    parent = parents[child]
                    revpath.append(parent)
                    if parent == self.start:
                        break
                    child = parent
                self.path = list(reversed(revpath))
                pbar.finish()
                self.save()
                return
        print 'No Solution'

if __name__ == '__main__':
    try:
        maze = Maze(sys.argv[1])
    except (IndexError, IOError):
        print "Specify file"
        sys.exit(1)
    else:
        if sys.argv[2] == 'b':
            maze.solve_bfs()
        else:
            maze.solve_astar()

Who Steals Tree Sweaters?

So last week some asshole decided to steal the (somewhat) famous tree sweaters from the trees on my block. Not only did the thief make off with Laurie Russell's originals, but he also took the little white "branch sweater" S.B.K. added to the collection a few weeks ago. Not the type to take tree sweater theft lying down, the residents of 16th Street (or maybe just S.B.K.?) have re-clothed the trees.