3D Printed Earrings

I thought I’d try my luck at making some costume jewelry (at least for my daughters, if not my wife as well). I still may use a metal printing service like Shapeways for nicer jewelry for my wife, but the homemade 3D printed jewelry has the kid-advantage of being cheap, very light, and a fun project for them.

For these earrings, I grabbed the model from Thingiverse, printed them with black ABS filament, and then used silver nail polish to finish them off.

3D Printed Earrings

Speeding Up Slic3r

I’m fairly new to 3D printing, but one of the first things I noticed was how slow Slic3r can be when generating gcode files to print. After some quick Googling it seems as though Slic3r grew in popularity back when it was faster but has since slowed down with each subsequent release. There’s no doubt that people are posting and printing more complex and higher resolution models which I’m sure adds to this.

Fortunately, the “fix” is really easy. I say “fix” in quotes because it is more of a workaround and with most things there is a tradeoff. The magic happens in the Print Settings tab in Slic3r. Simply uncheck the “Avoid crossing perimeters” setting and you’ll notice a vast performance improvement!

Slic3r crossing settings

So what is this setting and what is the tradeoff? From the Slic3r docs:

Will force the nozzle to follow perimeters as much as possible to minimise the number of times it must cross them when moving around, and between, islands. This has a negative impact on both G-code generation and print times.

Slic3r manual

Basically, this feature tries to make sure that when moving around the print area, the extruder never (as much as possible) crosses over the perimiter of an object and instead mvoes around it. This keeps an potentially oozing extruder from leaking extra plastic onto the clean outer shells of your object. When it is absolutely necessary to cross over a perimeter it tries to do so at a vertex instead of a flat surface to minimize any noticable artifacts.

So far I have not noticed any major issues from turning this setting off. Occassionally I will see a few extra nibs or bumps on my flat surfaces but it’s nothing that I can’t sand off - something I do to make the surfaces look nicer on my prints anyway.

Creating Weekday-only iOS Reminders

Here’s a quick tip; have you ever wanted to create a daily, but weekday-only, repeating task in iOS Reminders? The UI is rather limited but the functionality does exist.

Siri to the rescue!

Simply tell Siri something like:

Remember to pack a lunch on weekdays

Siri will offer to create the reminder for you and it will have a custom repeat setting. Once created you wont be able to view/edit the repeat settings in Reminders, but thats not a huge deal since it’s simple enough to ask Siri to create you a new one!

Bacon-Apple-Cheddar Burgers

Because recipes are yet another form of data and good food makes the fleshy part of our being very happy, here’s a rough recipe I made up that I loosely follow when we need something easy-yet-special on the grill.

Ingredients

  • 1 medium apple, chopped to 1/4-inch cubes
  • 1 lb ground beef
  • 1 small onion - chopped
  • 1 egg
  • 5 slices bacon, chopped
  • 3/4 cup shredded cheddar, the sharper the better
  • parsley

Directions

The directions are wicked-easy: thoroughly mix all the indredients, and make into burger patties. I find that making the burgers concave (as opposed to perfectly level) helps make them come out normal on the grill instead of turning into convex UFOs.

Grill and enjoy!

Most of the time I’ll eat them as-is with no extra condiments. If you must have something extra, I find that a nice honey mustard compliments the apples well.

Implementing the QS Data Spec on iOS

As part of implementing the Quantified Self Data Spec (look here for an introduction), I’ve started implementing it on iOS so that I’m living with it every day. The implementation I’m about to outline leverages the following technologies:

Workflow Overview

Launch Center Pro

Launch Center Pro is the perfect dashboard for testing (and even long-term running) of custom QS data collection efforts. You can set up multiple 1-touch actions to rapidly log many data points and have it integrate with other apps on your iOS device via URL-schemes if necessary. Notably, this process I’m using passes off to Drafts 4.

While it is possible to save files directly to Dropbox from Launch Center Pro, in order to fully support/implement the QS Data Spec I needed to be able to have a little more dynamic control over the JSON data before it was saved. You will see that my Launch Center Pro actions are missing the required uuid and timestamp JSON parameters. These will be inserted with Drafts later in the process.

I am going to demonstrate two basic workflow types: single-step (one touch) and multi-step. I’ve set up a emotional logger as the multi-step example, and a bathroom logger as the single-step example. For the bathroom logger, there are actually multiple single-step actions that I’ve grouped in to a folder, to make it more organized. I’m using single-step actions for this since it happens multiple times a day and I wanted to ensure it was as efficient as possible.

Launch Center Pro

Single-step Action (Bathroom log)

This one is very simple. We need to pass a URL-encoded version of the base JSON object for this datatype to Drafts for further processing. Here’s what the QS Data Spec says the JSON object should look like (full schema):

{
  "dataType": "consumption-waste",
  "dataSource": "manual",
  "uuid": "2b0b8801-c5e2-4b2d-a61a-31681eb95093",
  "timestamp": 1413555528,
  "urine": true,
  "feces": false
}

(Note: while the spec is very medical, I labeled things a little more politely in Launch Center Pro. i.e. feces -> BM)

As I mentioned previously, we will add the uuid and timestamp values dynamically within Drafts, so our Launch Center Pro action simply looks like this:

drafts4://x-callback-url/runAction?text={{%7B%22dataType%22%3A%22consumption-waste%22%2C%22dataSource%22%3A%22manual%22%2C%22urine%22%3Atrue%2C%22feces%22%3Afalse%7D}}&action={{Log QuantifiedSelf Entry}}&x-success={{launch:}}

This action is basically saying do the following steps:

  1. Launch Drafts 4
  2. Get ready to run an action
  3. Pass in the URL encoded text for the JSON object
  4. Run the ‘Log QuantifiedSelf Entry’ action
  5. On success, come back to Launch Center Pro

Multi-step Action (Emotional log)

Launch Center Pro - Emotional

The multi-step example follows the identical process as the single-step example except that it prompts the user for a few inputs before sending the JSON object over to Drafts. Here is what the QS Data Spec says the emotional object should look like (full schema):

{
  "dataType":"biometric-emotional",
  "dataSource":"manual",
  "energyLevel":5,
  "mood":3,
  "stressLevel":1,
  "uuid":"64759c11-e432-45a9-ad1b-a49b791549e9",
  "timestamp":1413765928
}

Our Launch Center Pro action, complete with user prompts to select the values of the three inputs looks like this:

drafts4://x-callback-url/runAction?text={{%7B%22dataType%22%3A%22biometric-emotional%22%2C%22dataSource%22%3A%22manual%22%2C%22energyLevel%22%3A[list:Energy Level|Energetic=5|Alert=4|Neutral=3|Tired=2|Exhausted=1]%2C%22mood%22%3A[list:Mood|Amazing=5|Good=4|Okay=3|Bad=2|Terrible=1]%2C%22stressLevel%22%3A[list:Stress Level|Stressed=5|Anxious=4|Neutral=3|Relaxed=2|Chill=1]%7D}}&action={{Log QuantifiedSelf Entry}}&x-success={{launch:}}

Bonus! Medical Log

You get the picture, right? So as a bonus we have the QS Data Spec example for medicine (full schema):

{
  "dataType": "consumption-drug",
  "dataSource": "manual",
  "uuid": "2b0b8801-c5e2-4b2d-a61a-31681eb95093",
  "timestamp": 1413555528,
  "drug": "Ibuprofen",
  "amount": 200,
  "measurementScale": "mg"
}

And the corresponding Launch Center Pro action, customized with entries for Ibuprofen and Ranitidine:

drafts4://x-callback-url/runAction?text={{%7B%22dataType%22%3A%22consumption-waste%22%2C%22dataSource%22%3A%22manual%22%2C%22urine%22%3Atrue%2C%22feces%22%3Afalse%7D}}&action={{Log QuantifiedSelf Entry}}&x-success={{launch:}}

Drafts 4

Drafts Action Overview

The quickest way to get this custom action into Drafts is from the Drafts Action Directory. I’ve posted it here.

The Drafts action is pretty straightforward. To contain a JavaScript file and a save to Dropbox action. To keep the files more organized until I can process them at a later date, I’ve told drafts to save the files in a date-based file system hierarchy: Logs/[year]/[month]/[day]/[UUID].json

The JavaScript is fairly simple itself. It basically sets the timestamp and UUID, and then saves the file to Dropbox with the UUID as the filename. It ends up looking more complicated because I had to paste in support-methods for UUID and JSON.

// Create the JSON object
var jsonString = decodeURIComponent(draft.content);
var j = JSON.parse(jsonString)

// Generate and set a UUID
var uuid = makeUUID();
j.uuid = uuid;

// Set the timestamp
j.timestamp = Math.round((new Date()).getTime() / 1000);

// Prep the Draft for saving to Dropbox in the next action step
draft.content = JSON.stringify(j);

// Send the UUID to the clipboard, so the next action can use it as the filename.
setClipboard(uuid);

// UUID support
function makeUUID(){

  var dec2hex = [];
  for (var i=0; i<=15; i++) {
    dec2hex[i] = i.toString(16);
  }

    var uuid = '';
    for (var i=1; i<=36; i++) {
      if (i===9 || i===14 || i===19 || i===24) {
        uuid += '-';
      } else if (i===15) {
        uuid += 4;
      } else if (i===20) {
        uuid += dec2hex[(Math.random()*4|0 + 8)];
      } else {
        uuid += dec2hex[(Math.random()*15|0)];
      }
    }

    return uuid;
}

// JSON support
var JSON;if(!JSON){JSON={}}(function(){function f(n){return n<10?"0"+n:n}if(typeof Date.prototype.toJSON!=="function"){Date.prototype.toJSON=function(key){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null};String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(key){return this.valueOf()}}var cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\b","\t":"\t","\n":"\n","\f":"\f","\r":"\r",'"':'\"',"\":"\\"},rep;function quote(string){escapable.lastIndex=0;return escapable.test(string)?'"'+string.replace(escapable,function(a){var c=meta[a];return typeof c==="string"?c:"\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+string+'"'}function str(key,holder){var i,k,v,length,mind=gap,partial,value=holder[key];if(value&&typeof value==="object"&&typeof value.toJSON==="function"){value=value.toJSON(key)}if(typeof rep==="function"){value=rep.call(holder,key,value)}switch(typeof value){case"string":return quote(value);case"number":return isFinite(value)?String(value):"null";case"boolean":case"null":return String(value);case"object":if(!value){return"null"}gap+=indent;partial=[];if(Object.prototype.toString.apply(value)==="[object Array]"){length=value.length;for(i=0;i<length;i+=1){partial[i]=str(i,value)||"null"}v=partial.length===0?"[]":gap?"[\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"]":"["+partial.join(",")+"]";gap=mind;return v}if(rep&&typeof rep==="object"){length=rep.length;for(i=0;i<length;i+=1){if(typeof rep[i]==="string"){k=rep[i];v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}else{for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=str(k,value);if(v){partial.push(quote(k)+(gap?": ":":")+v)}}}}v=partial.length===0?"{}":gap?"{\n"+gap+partial.join(",\n"+gap)+"\n"+mind+"}":"{"+partial.join(",")+"}";gap=mind;return v}}if(typeof JSON.stringify!=="function"){JSON.stringify=function(value,replacer,space){var i;gap="";indent="";if(typeof space==="number"){for(i=0;i<space;i+=1){indent+=" "}}else{if(typeof space==="string"){indent=space}}rep=replacer;if(replacer&&typeof replacer!=="function"&&(typeof replacer!=="object"||typeof replacer.length!=="number")){throw new Error("JSON.stringify")}return str("",{"":value})}}if(typeof JSON.parse!=="function"){JSON.parse=function(text,reviver){var j;function walk(holder,key){var k,v,value=holder[key];if(value&&typeof value==="object"){for(k in value){if(Object.prototype.hasOwnProperty.call(value,k)){v=walk(value,k);if(v!==undefined){value[k]=v}else{delete value[k]}}}}return reviver.call(holder,key,value)}text=String(text);cx.lastIndex=0;if(cx.test(text)){text=text.replace(cx,function(a){return"\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})}if(/^[],:{}\s]*$/.test(text.replace(/\(?:["\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\n\r]*"|true|false|null|-?\d+(?:.\d*)?(?:[eE][+-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*[)+/g,""))){j=eval("("+text+")");return typeof reviver==="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}}());