Fun

CablePipeSnake


iStrain

Instead of incorporating any of the existing methods of fixing their long-standing problem with poor strain relief on cables, Apple patented a new one.

In other DifferentThink news, Apple has declared the word ’Asian’ out of bounds for kid-safe browsing on iPads and iPhones. No, seriously.

A Simple, Well-Made Thing

It’s a clone of a well-known and much-copied commercial product, but this folding pipe holder is correctly and cleanly adapted to 3D printing. No supports required, and the print-in-place hinge didn’t even require any break-in to work.

(and with the revised acceleration/jerk settings I mentioned yesterday, the actual print time was 99.5% of the slicer’s estimate)

I don’t currently have any fancy filament that would look cool for showing off some of my dad’s pipes, but it was refreshing to see someone do the job right.

Unlike the item I was looking at yesterday that had all kinds of curves and angles and overhangs running in all directions, forcing you to add 50% to the time and materials to support it all. It’s a complete replacement for an injection-molded part, with bits added to partially solve a problem that doesn’t come up that often. It would have been faster and easier to design something that attached to the existing part rather than replacing it, and the result would have been easier to print. Sigh.

I Speak Gcode To My Dremel, Python Edition

(script significantly updated and reformatted)

#!/usr/bin/env python3
"""
Connect to a networked Dremel 3D45 to manage print jobs and query
for job status and printer information.

Commands:
    info, status, preheat, print, cancel, pause, resume
"""

import sys
import os
import argparse
import configparser
import datetime
import requests

COMMAND = "http://%s/command"
UPLOAD = "http://%s/print_file_uploads"
WEBCAM = "http://%s:10123/?action=stream"
IPADDR = "192.168.0.1"


def api_cmd(command):
    try:
        r = requests.post(COMMAND % IPADDR, data=command, timeout=5)
        r.raise_for_status()
    except requests.exceptions.HTTPError as http_err:
        sys.exit("Printer %s (%s): %s" % (PRINTERNAME, IPADDR, http_err))
    except Exception as err:
        sys.exit("Printer %s (%s) offline or unhappy: %s" % (PRINTERNAME, IPADDR, err))
    return r.json()


def api_upload(file, jobname):
    try:
        gcode = {"print_file": (jobname, open(file, "rb").read())}
    except Exception as err:
        sys.exit("Printer %s (%s): %s" % (PRINTERNAME, IPADDR, err))
    try:
        r = requests.post(UPLOAD % IPADDR, files=gcode, timeout=300)
        r.raise_for_status()
    except HTTPError as http_err:
        sys.exit("Printer %s (%s): %s" % (PRINTERNAME, IPADDR, http_err))
    except Exception as err:
        sys.exit("Printer %s (%s) offline or unhappy: %s" % (PRINTERNAME, IPADDR, err))
    return r.json()


parser = argparse.ArgumentParser(
    description="Network utility for Dremel 3D45 printers",
    formatter_class=argparse.RawTextHelpFormatter,
    epilog="""
Requires a file ~/.pydremel containing at least one entry like this:
    [default]
    name=Autobot
    [Autobot]
    ip_address=192.168.0.200
(or you can change IPADDR at the top of the script)

* If you print the same filename twice in a row with different
  contents, the printer will remember some metadata from the first
  job, and calculate completion progress incorrectly.
""",
)
parser.add_argument(
    "-p",
    "--printer",
    default="default",
    help="named printer in your .pydremel config file",
)
parser.add_argument(
    "-r",
    "--raw",
    action="store_true",
    help="print raw JSON for info or status output",
)
parser.add_argument(
    "command",
    choices=["info", "status", "preheat", "print", "cancel", "pause", "resume"],
)
parser.add_argument("filename", nargs="?", help="gcode file to be printed")
args = parser.parse_args()

# load printer info from config unless user edited the script
if IPADDR == "192.168.0.1":
    config = configparser.RawConfigParser()
    config_file = os.path.join(os.path.expanduser("~"), ".pydremel")
    try:
        config.read(config_file)
    except:
        sys.exit("No config file ~/.pydremel")

    if args.printer == "default":
        if config.has_section("default"):
            PRINTERNAME = config.get("default", "name")
        else:
            sys.exit("No default printer in ~/.pydremel")
    else:
        PRINTERNAME = args.printer
    if config.has_section(PRINTERNAME):
        IPADDR = config.get(PRINTERNAME, "ip_address")
    else:
        sys.exit("No IP address for printer %s" % PRINTERNAME)
else:
    PRINTERNAME = "3D45"

if args.command == "info":
    s = api_cmd("GETPRINTERINFO")
    if args.raw:
        print(str(s).replace("'", '"'))
    else:
        print("%s" % s["machine_type"])
        print(
            "(SN=%s, firmware=%s, API=%s)"
            % (s["SN"], s["firmware_version"], s["api_version"])
        )
        if s["ethernet_connected"] == 1:
            print("IP Address %s (wired)" % s["ethernet_ip"])
        if s["wifi_connected"] == 1:
            print("IP Address %s (wireless)" % s["wifi_ip"])

elif args.command == "status":
    # note that 'layer' and 'fanSpeed' do not contain valid data
    s = api_cmd("GETPRINTERSTATUS")
    if args.raw:
        print(str(s).replace("'", '"'))
    else:
        if s["message"] == "success":
            print(
                "%.1f%% %s %s\n  %s/%s; %d°/%d° (chamber %d°)"
                % (
                    s["progress"],
                    datetime.timedelta(seconds=s["remaining"]),
                    s["jobname"],
                    s["status"],
                    s["jobstatus"],
                    s["temperature"],
                    s["platform_temperature"],
                    s["chamber_temperature"],
                )
            )
            # 'elaspedtime' (sic) starts counting when the nozzle
            # reaches temperature and moves to start printing, and
            # stops when jobstatus == 'completed'
            if s["totalTime"] > 0 and s["jobstatus"] == "completed":
                delta = s["elaspedtime"] - s["totalTime"]
                if abs(delta) > s["totalTime"] * 0.01:
                    print(
                        "  (estimate %s, actual %s%s (%.1f%%))"
                        % (
                            datetime.timedelta(seconds=s["totalTime"]),
                            "+" if delta > 0 else "-",
                            datetime.timedelta(seconds=abs(delta)),
                            abs(delta) / s["totalTime"] * 100
                        )
                    )

# there's no point in preheating the nozzle, since it will cool
# down by the time the auto-leveling is finished.
elif args.command == "preheat":
    s = api_cmd("PLATEHEAT")
    if s["message"] == "success":
        print("Bed heating to preset temperature (printer will beep twice)")

# jobname passed to printer cannot contain path or space characters
# and must end in '.gcode'
elif args.command == "print":
    if not args.filename:
        sys.exit("usage: %s print filename.gcode" % sys.argv[0])
    filename = args.filename
    if not os.path.isfile(filename):
        sys.exit("Error: file '%s' not found" % filename)
    jobname = os.path.basename(filename).replace(" ", "_")
    if os.path.isfile(filename):
        s = api_upload(filename, jobname)
    if s["message"] == "success":
        s = api_cmd("PRINT=%s" % jobname)
    if s["message"] == "success":
        print("Success! Watch your job print at:")
        print(WEBCAM % IPADDR)
    else:
        sys.exit("Error: couldn't print '%s': %s" % (filename, s["message"]))

# cancel will not take effect until after auto-leveling completes
elif args.command == "cancel":
    s = api_cmd("GETJOBSTATUS")
    if s["message"] == "success":
        jobname = s["jobname"]
    if jobname == "":
        print("No active job")
    else:
        s = api_cmd("CANCEL=%s" % jobname)
        if s["message"] == "success":
            print("Print job %s canceled" % jobname)
        else:
            print("Cancel failed: %s" % s["message"])

elif args.command == "pause":
    s = api_cmd("GETJOBSTATUS")
    if s["message"] == "success":
        jobname = s["jobname"]
    if jobname == "":
        print("No active job")
    else:
        s = api_cmd("PAUSE=%s" % jobname)
        if s["message"] == "success":
            print("Print job %s paused" % jobname)
        else:
            print("Pause failed: %s" % s["message"])

elif args.command == "resume":
    s = api_cmd("GETJOBSTATUS")
    if s["message"] == "success":
        jobname = s["jobname"]
    if jobname == "":
        print("No active job")
    else:
        s = api_cmd("RESUME=%s" % jobname)
        if s["message"] == "success":
            print("Print job %s resumed" % jobname)
        else:
            print("Resume failed: %s" % s["message"])

Oxy Morons in the House

Dear Dems, either they’re facts or they’re allegations. Calling them “factual allegations” kind of makes Trump’s point that you’re just blowing smoke up our asses.

Tactical DungeonScript


Tactical Stealth Police, you say?

Pretty sure there’s no tactics involved in opening a beer.

Women In Engineering?

Wow, Japan was really ahead of the curve in getting women into STEM!

Yes, my Kindle recommendations are currently filled with old samurai and ninja books, randomly categorized.

Can you spin a good yarn?

It’s all fun and games until someone beats the weft.

Monster Manual

I’ve always suspected Javascript was an Aberration…

“Thank you for the mental picture!”

Actual headline:

Pornhub Announces ‘Biometric Technology’ to Verify Users

DNA sample optional.

Dialing It In

After tinkering with acceleration and jerk settings in both Cura and PrusaSlicer (dropping them to 1000 and 5, respectively), I kicked off a job with an estimated runtime of 22,873 seconds. Actual completion time: 23,478 seconds. 2.6% over is plenty good enough for me, so hopefully it will be pretty consistent from now on.

(the actual before/after print times were almost identical in my testing, so this is just adjusting the slicer to match the actual behavior of the printer; next step: tweaking overall speed up and down, to see if the new estimates are still in the ballpark)

Reminder: early reports are almost never true

It’s extremely common for “on-the-scene” reports of an event to be false or misleading, and the few surviving serious journalists know this, but the convenient lie will always get more coverage than the pedestrian truth. It must have sickened a CNN “journalist” to print these words about Teh Insurrection:

“Medical examiners did not find signs that the officer sustained any blunt force trauma, so investigators believe that early reports that he was fatally struck by a fire extinguisher are not true.”

Still no word on why Ashli Babbitt was shot dead by a cop.

What's the nicest thing I can say about...


High School DxD

I made it farther (5 episodes) than I did with Senran Kagura (2/3), only because the fan-service was so overwhelming and badly done that it was funny, particularly the completely out-of-character stripperific ED animation. Unfortunately, that’s not enough to make the show watchable, especially given how annoying Our Hero is. But what really killed it for me was Sailor Goon showing up in episode 6; pick a direction, guys.

Apple

They’re really working hard to make Microsoft look like a well-run company with a solid operating system and decent QA. Surprisingly, so is Microsoft.

Corona-chan

The masks coming off, and I don’t mean the sneeze-guards people have been forced to wear for most of the past year.

Google/Twitter/Facebook

Well, they’re not serial killers. Pretty Sure. Okay, kinda sure. Maybe?

LinkedIn

They haven’t completely lost focus on being a professional networking tool, although they sure have buried it under a mountain of irrelevant social-network/data-harvesting/certification bullshit. And they accidentally helped a new job get me last year.

DirecTV/AT&T

Their efforts to fraudulently extract money from former customers probably aren’t felonious. If only because they have really good contract lawyers.

Democrats

They’re not all career criminals. I mean, there’s Tulsi Gabbard, who I disagree with on almost every issue but am willing to trust when it counts because she actually likes America.

And, um, give me a minute, I’m sure I’ll think of another one.

No soup for you!


No pie for you!

Corona-chan apparently took down the local Pizza Hut a few weeks ago. Permanently? Still showed as closed on Friday night at dinnertime. If I wanted good pizza I’d make it myself, but first I’d have to go buy pepperoni and cheese. I tend to order from PH if I don’t want to go out, because it’s better than Domino’s, half the price of Round Table, and won’t make me sick like Mountain Mike’s does.

No cut for you!

The local chain haircut joint was shut down by the governor (again) at the beginning of December, and despite him opening things back up a little in a likely-futile effort to stop the petition to shitcan his l’il-dictator ass, they haven’t reopened yet. It looks like their location in Seaside is back online, and I know the folks from the Salinas shop were working shifts over there, so maybe I’ll make the 20-mile drive today or tomorrow.

No kids for you!

Episode 4 of That Spider Show was a bit more interesting, because it didn’t advance the B ArkArc.

(spider is unrelated)

No shows for you!

I haven’t found anything else worth watching for days. Last thing I managed to make it through was Matt Smith’s final arc in Doctor Who.

bash dose not exists


On CallCallCallCall

Yesterday was the beginning of my first week of on-call since I started the new job at the end of August (pause for hysterical laughter at the thought of only one week at a time). When Baby’s First Alert went off, PagerDuty simultaneously sent email, SMS, a push notification, and a phone call; it sounded like my phone was having an audiogasm. My first priority was trimming it to one alert at a time.

And, yes, one of the Nagios errors I’ve been paged over was a log-scanner that flagged an unfamiliar error message. It turns out that “X dose not exists” is a surprisingly popular typo, in a variety of contexts.

Technically not a lens!

Despite the high risk of higher taxes this year, I decided to stretch my no-new-cameras-or-lenses rule slightly to buy a Nikon-to-Sony tilt/shift adapter. +/-10mm of shift, 10° of tilt, and 360° of rotation with click-stops so you can use them in any direction. It’s not a true view camera adapter, but it also doesn’t cost $850 and weigh considerably more than the camera.

A quick test with my really old Nikon 35mm f/2.8 and 50mm f/1.4 allowed full movements with no vignetting on my A6500. It’s something I’d only pack on days where I was planning to visit a castle (or if I ever made it waaay out to the Yamato museum in Kure to get some pics of the 1:10 scale model), but it does something software simply can’t.

(and, yes, there are half a dozen 3d-printed tilt/shift adapter designs out there, but most of them have the wrong mount on one or both sides, and are clunky and/or hard to print. Also, I’m not really willing to trust my lenses to their design skills. I’ve had a camera fall four feet onto concrete due to a defective strap; trusting a 3d-printed connector seems worse)

(and, yes, my current straps are very sturdy)

Also not a lens!

Somewhere I have a very nice pinhole adapter for my 4x5 camera, which would be great if I had a darkroom, or a high-end digital back. Since I don’t, I was idly looking at the various body-cap adapters, which range from actual body caps to nicely-made adapters that take lens caps and filters.

That Amazon listing didn’t include the effective aperture size, which is kinda useful information, so I went to their official site, and the very first sample picture from the product was:

Because nothing says “infinite depth of field” like a giant replica of a masturbation device.

Monster Girl Doctor

Sometime last year I read the first two light novels for this and lost interest, so the anime wasn’t high on my list. After idly watching the first few episodes, I’m not terribly interested in more, unless they focus on the cyclops girl Memé.

Rottin’ to the core...


Recommendations

You know what the real difference is between Pixiv and Amazon when it comes to recommendations? No, it’s not that Pixiv does a better job of selecting new things based on things I like, it’s that they don’t show me things I’ve already tagged.

On a good day, when Amazon manages to recommend products that are actually relevant, about a third of them will be things that I’ve already purchased from Amazon. And I don’t just mean consumables like coffee or 3d-printing filament, or things like toaster ovens or watches that theoretically someone could buy 3-4 of every year, or duplicate listings with different SKUs, I mean books and movies that when you click on them, show up as “you purchased this $months_or_years_ago”.

Today is actually one of the rare days when my recommendations consist primarily of consumable items that I’ve bought before and will likely buy again. Never mind that many of them are subscribe-and-save items that I’m already buying again.

File under peculiar the fact that the “Hunting and Fishing Equipment” tile consists of a 4-pack of pointy self-defense keychains in basic black, a 9-pack of the same item in a rainbow of colors, a 3-pack of brand-x pepper-spray, and a 150-pack of fishing hooks.

Three Little, Too Late

MacRumors is reporting that for the next MacBook Pro, Apple is planning to bring back MagSafe, function keys, and a useful array of ports.

There are, however, no rumors that they’ll be bringing back QA.

The Long Con


Connnnnnnnnnnnnn!

Dear Amazon, why is it so easy for “marketplace” “dealers” to auto-generate frauds like this?

  1. Indie publisher puts up pre-orders for Kindle edition of a brand-new book.

  2. Scammers put up phony listings for new and used hardcover and paperback editions of this as-yet-unpublished book, at insane prices.

  3. Amazon profits.

My kind of Minion

I didn’t even know I had this fetish…

Dear Reddit,

Pedo much? Do you really think that a 3D printer forum is the right place for this kind of thing?

“Why does it hurt when I pee?”

New at CES, for people who’ve been frightened into submission, the touchless doorbell. Because nothing spreads viruses like hard plastic exposed to fresh air and sunlight that gets touched maybe twice a week.

The Naming Of Thingies


The Jack Vance Mindset

A while back, I made fun of the insane overload of boggle-dice names in the 1979 fantasy novel The Alien.

Yesterday, I picked up the Kindle edition of the first book in Jack Vance’s classic Alastor novels, set waaay off in space and time. I… apologize to Victor Besaw…

starments, Connatic, Lusz, Numenes, Oman Irsht, Idite, Whelm, starmenter, Primarchic, Erdic, Rubrimar, Trullion, Merlank, Trill, hussade, pulsor, paray, cauch, Trevanyi, prutanshyr, Gaw, Kerubian, Maheul, Vayamenda, Welgen, menas, jerdine, Saurkash, Rabendary, Jut Hulden, Marucha, semprissima, Ambal, Gilweg, Saur, fanzaneels, sheirl, Shira, Glinnes, Glay, Sharue, merlings

That’s just the first few pages, and many of these terms are explained in footnotes or lengthy asides, further reducing the amount of actual narrative.

Fuck Macintouch, too

Another fuckwit who doesn’t know what the word “seditious” means. And who slept through the last five years of unhinged Leftist character assassinations, trespassing (including Capitol buildings, hinthint), occupation, violent assaults, rioting, looting, pillaging, arson, murder, and outright domestic terrorism, much of it perpetrated by an organization that proudly displays its Communist Party heritage.

(side note: it occurs to me that this was the first appearance of the modern incel)

Apple QA, still wet behind the ears

Apple’s fondness for closed systems has resulted in their top-of-the-line headphones filling up with moisture. Best comment: “You’re holding it wrong”.

And another thing I hate about Cults3D…

…is the sensory overload of all the damn animated GIFs that model “creators” (I use the term loosely) use as thumbnail images. They’re spending more time trying to attract attention to their uploads than they spend making or testing them. Honestly, I’d almost be willing to pay a monthly subscription to hide the animated GIFs and weed out (coughcough) all the 420 and fraud.

MyMiniFactory is slightly less frenetic, flipping through the thumbnails for each upload every few seconds as you page through search results. On the bright side, while it has its share of inane and pirated products, most of the non-free items appear to have actually been made by their uploaders.

And they host Scan The World, a useful archive of actual cool stuff gathered from museums and collections around the world. Many of them are difficult or impossible to print, but there are some real gems in the collection. I can’t find Darmok or Jalad, though.

Potential filament filter

This is an interesting design. Huge compared to most of them, and specifically sized to fit a Prusa printer, but designed to use microfiber cleaning cloth for low drag, and sensibly printable with chamfered edges and rounding for the filament path. I’d prefer a locking connector to the “pause print and insert magnets” trick, but I can just tape it shut for the initial test; my goal is to get back to printing things other than printer parts.

Japan, maybe?

My sister sent me an update indicating that when the current restrictions are lifted in Japan at the end of January, the new rules for entering foreigners will be A) recent COVID test before boarding the plane, B) mandatory COVID test when you arrive. I need to look up the details to see if there’s also a quarantine while you wait for the results.

I expect they’ll be trying to streamline this as spring progresses, in order to shore up the tourist economy and possibly hold the delayed 2020 Olympics. There’s a lot of money on the line.

Included, Batteries

The other set of third-party batteries I ordered recently, a pair of NP-BX1 and a charger from Powerextra, distinguished themselves by actually charging. No idea yet how their working life compares to real Sony batteries, but even if they’re only half as good, they’ll still keep me from running dry during day trips when I get back to Japan.

Even without shooting video, the little WX800 has the potential to drain the batteries faster, because the long zoom range and slow lens means that I’ll be leaning more heavily on using the optical image stabilization and multi-shot noise/blur reduction.

Sony does have an awesome new body that goes up to ISO 409,600, but…

Things I Won’t Buy in 2021

  1. new camera or lenses

  2. new computer

  3. new car

  4. new oven, stovetop, and range hood

  5. non-emergency home improvements

Why not? Because with Democrats desperate to pay off a frankenhooker coalition that has nothing in common but hate, I expect massive spending and tax increases. Drunken sailors will stare in shock.

It won’t appease their mobs, who’ve learned that there are no consequences to destroying cities, but they’ll do it anyway, because it’s all they know, and they can skim a percentage off the top for themselves.

“Need a clue, take a clue,
 got a clue, leave a clue”