Been working hard to output some new videos for FreeCAD version 1.0Â The latest one is linked below:https://youtu.be/tKiXYO0TyaQ
Category Archives: News
MPCNC Post Processor
This is my MPCNC post processor, it works for me. Use it at your own risk!
Your mileage may vary!
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * (c) sliptonic (shopinthewoods@gmail.com) 2014 *
# * (c) Gauthier Briere - 2018, 2019 *
# * (c) Schildkroet - 2019-2020 *
# * (c) Ian Jobson - 2020 - 2021 *
# * *
# * This file is not yet part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Lesser General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************/
import FreeCAD
from FreeCAD import Units
import PathScripts.PostUtils as PostUtils
import argparse
import datetime
import shlex
import PathScripts.PathUtil as PathUtil
TOOLTIP = '''
Generate g-code from a Path that is compatible with the MPCNC Marlin controller.
import mpcnc_post
mpcnc_post.export(object, "/path/to/file.gcode")
'''
# ***************************************************************************
# * Globals set customization preferences
# ***************************************************************************
# Default values for command line arguments:
NUMBERTOOL = 1 # there has to be one tool to start with
PAUSE_FIRST_TOOL = False # should we create an M0 for the first tool
OUTPUT_COMMENTS = True # default output of comments in output gCode file
OUTPUT_HEADER = True # default output header in output gCode file
OUTPUT_LINE_NUMBERS = False # default doesn't output line numbers in output gCode file
OUTPUT_BCNC = False # default doesn't add bCNC operation block headers in output gCode file
SHOW_EDITOR = True # default show the resulting file dialog output in GUI
PRECISION = 3 # Default precision for metric (see http://linuxcnc.org/docs/2.7/html/gcode/overview.html#_g_code_best_practices)
TRANSLATE_DRILL_CYCLES = False # If true, G81, G82 & G83 are translated in G0/G1 moves
PREAMBLE = '''G90
G21
''' # default preamble text will appear at the beginning of the gCode output file.
POSTAMBLE = '''M117 Job End
''' # default postamble text will appear following the last operation.
SPINDLE_WAIT = 0 # no waiting after M3 / M4 by default
RETURN_TO = ("0","0",) # no movements after end of program
# Customisation with no command line argument
MODAL = False # if true commands are suppressed if the same as previous line.
LINENR = 100 # line number starting value
LINEINCR = 10 # line number increment
OUTPUT_TOOL_CHANGE = True # default output M0 tool changes pause the machine until the resume is hit
DRILL_RETRACT_MODE = 'G98' # Default value of drill retractations (CURRENT_Z) other possible value is G99
MOTION_MODE = 'G90' # G90 for absolute moves, G91 for relative
UNITS = 'G21' # G21 for metric, G20 for us standard
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = 'mm/min'
PRE_OPERATION = '''''' # Pre operation text will be inserted before every operation
POST_OPERATION = '''''' # Post operation text will be inserted after every operation
TOOL_CHANGE = '''M0
''' # Tool Change commands will be inserted before a tool change
# ***************************************************************************
# * End of customization
# ***************************************************************************
# Parser arguments list & definition
parser = argparse.ArgumentParser(prog='grbl', add_help=False)
parser.add_argument('--comments', action='store_true', help='output comment (default)')
parser.add_argument('--no-comments', action='store_true', help='suppress comment output')
parser.add_argument('--header', action='store_true', help='output headers (default)')
parser.add_argument('--no-header', action='store_true', help='suppress header output')
parser.add_argument('--line-numbers', action='store_true', help='prefix with line numbers')
parser.add_argument('--no-line-numbers', action='store_true', help='don\'t prefix with line numbers (default)')
parser.add_argument('--show-editor', action='store_true', help='pop up editor before writing output (default)')
parser.add_argument('--no-show-editor', action='store_true', help='don\'t pop up editor before writing output')
parser.add_argument('--precision', default='3', help='number of digits of precision, default=3')
parser.add_argument('--translate_drill', action='store_true', help='translate drill cycles G81, G82 & G83 in G0/G1 movements')
parser.add_argument('--no-translate_drill', action='store_true', help='don\'t translate drill cycles G81, G82 & G83 in G0/G1 movements (default)')
parser.add_argument('--preamble', help='set commands to be issued before the first command, default="G17 G90"')
parser.add_argument('--postamble', help='set commands to be issued after the last command, default="M5\nG17 G90\n;M2"')
parser.add_argument('--inches', action='store_true', help='Convert output for US imperial mode (G20)')
parser.add_argument('--tool-change', action='store_true', help='Insert M0 for all tool changes')
parser.add_argument('--wait-for-spindle', type=int, default=0, help='Wait for spindle to reach desired speed after M3 / M4, default=0')
parser.add_argument('--return-to', default='', help='Move to the specified coordinates at the end, e.g. --return-to=0,0')
parser.add_argument('--bcnc', action='store_true', help='Add Job operations as bCNC block headers. Consider suppressing existing comments: Add argument --no-comments')
parser.add_argument('--no-bcnc', action='store_true', help='suppress bCNC block header output (default)')
TOOLTIP_ARGS = parser.format_help()
# ***************************************************************************
# * Internal global variables
# ***************************************************************************
MOTION_COMMANDS = ['G0', 'G00', 'G1', 'G01', 'G2', 'G02', 'G3', 'G03'] # Motion gCode commands definition
RAPID_MOVES = ['G0', 'G00'] # Rapid moves gCode commands definition
SUPPRESS_COMMANDS = [] # These commands are ignored by commenting them out
COMMAND_SPACE = " "
# Global variables storing current position
CURRENT_X = 0
CURRENT_Y = 0
CURRENT_Z = 0
# ***************************************************************************
# * to distinguish python built-in open function from the one declared below
if open.__module__ in ['__builtin__', 'io']:
pythonopen = open
def processArguments(argstring):
global NUMBERTOOL
global PAUSE_FIRST_TOOL
global OUTPUT_HEADER
global OUTPUT_COMMENTS
global OUTPUT_LINE_NUMBERS
global SHOW_EDITOR
global PRECISION
global PREAMBLE
global POSTAMBLE
global UNITS
global UNIT_SPEED_FORMAT
global UNIT_FORMAT
global TRANSLATE_DRILL_CYCLES
global OUTPUT_TOOL_CHANGE
global SPINDLE_WAIT
global RETURN_TO
global OUTPUT_BCNC
try:
args = parser.parse_args(shlex.split(argstring))
if args.no_header:
OUTPUT_HEADER = False
if args.header:
OUTPUT_HEADER = True
if args.no_comments:
OUTPUT_COMMENTS = False
if args.comments:
OUTPUT_COMMENTS = True
if args.no_line_numbers:
OUTPUT_LINE_NUMBERS = False
if args.line_numbers:
OUTPUT_LINE_NUMBERS = True
if args.no_show_editor:
SHOW_EDITOR = False
if args.show_editor:
SHOW_EDITOR = True
PRECISION = args.precision
if args.preamble is not None:
PREAMBLE = args.preamble
if args.postamble is not None:
POSTAMBLE = args.postamble
if args.no_translate_drill:
TRANSLATE_DRILL_CYCLES = False
if args.translate_drill:
TRANSLATE_DRILL_CYCLES = True
if args.inches:
UNITS = 'G20'
UNIT_SPEED_FORMAT = 'in/min'
UNIT_FORMAT = 'in'
PRECISION = 4
if args.tool_change:
OUTPUT_TOOL_CHANGE = True
if args.wait_for_spindle > 0:
SPINDLE_WAIT = args.wait_for_spindle
if args.return_to != '':
RETURN_TO = [int(v) for v in args.return_to.split(',')]
if len(RETURN_TO) != 2:
RETURN_TO = None
print("--return-to coordinates must be specified as
if args.bcnc:
OUTPUT_BCNC = True
if args.no_bcnc:
OUTPUT_BCNC = False
except Exception as e:
return False
return True
# For debug...
def dump(obj):
for attr in dir(obj):
print("obj.%s = %s" % (attr, getattr(obj, attr)))
def export(objectslist, filename, argstring):
if not processArguments(argstring):
return None
global UNITS
global UNIT_FORMAT
global UNIT_SPEED_FORMAT
global MOTION_MODE
global SUPPRESS_COMMANDS
print("Post Processor: " + __name__ + " postprocessing...")
gcode = ""
# write header
if OUTPUT_HEADER:
gcode += linenumber() + ";Exported by FreeCAD\n"
gcode += linenumber() + ";Post Processor: " + __name__ + "\n"
gcode += linenumber() + ";Output Time:" + str(datetime.datetime.now()) + "\n"
# Check canned cycles for drilling
if TRANSLATE_DRILL_CYCLES:
if len(SUPPRESS_COMMANDS) == 0:
SUPPRESS_COMMANDS = ['G98', 'G80']
else:
SUPPRESS_COMMANDS += ['G98', 'G80']
# Write the preamble
if OUTPUT_COMMENTS:
gcode += linenumber() + ";Begin preamble\n"
for line in PREAMBLE.splitlines(True):
gcode += linenumber() + line
# verify if PREAMBLE have changed MOTION_MODE or UNITS
if 'G90' in PREAMBLE:
MOTION_MODE = 'G90'
elif 'G91' in PREAMBLE:
MOTION_MODE = 'G91'
else:
gcode += linenumber() + MOTION_MODE + "\n"
if 'G21' in PREAMBLE:
UNITS = 'G21'
UNIT_FORMAT = 'mm'
UNIT_SPEED_FORMAT = 'mm/min'
elif 'G20' in PREAMBLE:
UNITS = 'G20'
UNIT_FORMAT = 'in'
UNIT_SPEED_FORMAT = 'in/min'
else:
gcode += linenumber() + UNITS + "\n"
for obj in objectslist:
# Debug...
# print("\n" + "*"*70)
# dump(obj)
# print("*"*70 + "\n")
if not hasattr(obj, "Path"):
print("The object " + obj.Name + " is not a path. Please select only path and Compounds.")
return
# Skip inactive operations
if PathUtil.opProperty(obj, 'Active') is False:
continue
# do the pre_op
if OUTPUT_BCNC:
gcode += linenumber() + "(Block-name: " + obj.Label + ")\n"
gcode += linenumber() + "(Block-expand: 0)\n"
gcode += linenumber() + "(Block-enable: 1)\n"
if OUTPUT_COMMENTS:
gcode += linenumber() + ";Begin operation: " + obj.Label + "\n"
for line in PRE_OPERATION.splitlines(True):
gcode += linenumber() + line
# get coolant mode
coolantMode = 'None'
if hasattr(obj, "CoolantMode") or hasattr(obj, 'Base') and hasattr(obj.Base, "CoolantMode"):
if hasattr(obj, "CoolantMode"):
coolantMode = obj.CoolantMode
else:
coolantMode = obj.Base.CoolantMode
# turn coolant on if required
if OUTPUT_COMMENTS:
if not coolantMode == 'None':
gcode += linenumber() + ';(Coolant On:' + coolantMode + ')\n'
if coolantMode == 'Flood':
gcode += linenumber() + 'M8' + '\n'
if coolantMode == 'Mist':
gcode += linenumber() + 'M7' + '\n'
# Parse the op
gcode += parse(obj)
# do the post_op
if OUTPUT_COMMENTS:
gcode += linenumber() + ";Finish operation: " + obj.Label + "\n"
for line in POST_OPERATION.splitlines(True):
gcode += linenumber() + line
# turn coolant off if required
if not coolantMode == 'None':
if OUTPUT_COMMENTS:
gcode += linenumber() + ';(Coolant Off:' + coolantMode + ')\n'
gcode += linenumber() +'M9' + '\n'
# do the post_amble
if OUTPUT_BCNC:
gcode += linenumber() + "(Block-name: post_amble)\n"
gcode += linenumber() + "(Block-expand: 0)\n"
gcode += linenumber() + "(Block-enable: 1)\n"
if OUTPUT_COMMENTS:
gcode += linenumber() + ";Begin postamble\n"
for line in POSTAMBLE.splitlines(True):
gcode += linenumber() + line
if RETURN_TO:
gcode += linenumber() + "G0 X%s Y%s" % tuple(RETURN_TO)
# show the gCode result dialog
if FreeCAD.GuiUp and SHOW_EDITOR:
dia = PostUtils.GCodeEditorDialog()
dia.editor.setText(gcode)
result = dia.exec_()
if result:
final = dia.editor.toPlainText()
else:
final = gcode
else:
final = gcode
print("Done postprocessing.")
# write the file
gfile = pythonopen(filename, "w")
gfile.write(final)
gfile.close()
def linenumber():
global LINENR
global LINEINCR
if OUTPUT_LINE_NUMBERS:
s = "N" + str(LINENR) + " "
LINENR += LINEINCR
return s
return ""
def format_outstring(strTbl):
global COMMAND_SPACE
# construct the line for the final output
s = ""
for w in strTbl:
s += w + COMMAND_SPACE
s = s.strip()
return s
def parse(pathobj):
global DRILL_RETRACT_MODE
global MOTION_MODE
global CURRENT_X
global CURRENT_Y
global CURRENT_Z
global NUMBERTOOL
global TOOL_CHANGE
out = ""
lastcommand = None
precision_string = '.' + str(PRECISION) + 'f'
params = ['X', 'Y', 'Z', 'A', 'B', 'C', 'U', 'V', 'W', 'I', 'J', 'K', 'F', 'S', 'T', 'Q', 'R', 'L', 'P']
if hasattr(pathobj, "Group"): # We have a compound or project.
if OUTPUT_COMMENTS:
out += linenumber() + ";(Compound: " + pathobj.Label + ")\n"
for p in pathobj.Group:
out += parse(p)
return out
else: # parsing simple path
if not hasattr(pathobj, "Path"): # groups might contain non-path things like stock.
return out
if OUTPUT_COMMENTS:
out += linenumber() + ";Path: " + pathobj.Label + "\n"
for c in pathobj.Path.Commands:
outstring = []
command = c.Name
######################################################################
# Intercept the command here and modify it to insert the comment char
######################################################################
message = command
#print(message)
message_start = message[0]
#print(message_start)
if message_start == '(':
message = message.replace(message_start, ';' + message_start)
#print(message)
command = message
message_start = message[0:2]
#print(message_start + "---From Message[0:2]")
if message_start == 'M6':
TOOL_CHANGE = TOOL_CHANGE.rstrip("\n")
if NUMBERTOOL == 1 and PAUSE_FIRST_TOOL == False :
message = message.replace(message_start, ';' + TOOL_CHANGE)
NUMBERTOOL += 1
else:
message = message.replace(message_start, TOOL_CHANGE)
NUMBERTOOL += 1
#print(message)
#print (NUMBERTOOL)
#print ("^^^is the tool number")
command = message
#######################################################################
# End of Intercept mods
#######################################################################
outstring.append(command)
# if modal: only print the command if it is not the same as the last one
if MODAL:
if command == lastcommand:
outstring.pop(0)
# Now add the remaining parameters in order
for param in params:
if param in c.Parameters:
if param == 'F':
if command not in RAPID_MOVES:
speed = Units.Quantity(c.Parameters['F'], FreeCAD.Units.Velocity)
if speed.getValueAs(UNIT_SPEED_FORMAT) > 0.0:
outstring.append(param + format(float(speed.getValueAs(UNIT_SPEED_FORMAT)), precision_string))
elif param in ['T', 'H', 'D', 'S', 'P', 'L']:
outstring.append(param + str(c.Parameters[param]))
elif param in ['A', 'B', 'C']:
outstring.append(param + format(c.Parameters[param], precision_string))
else: # [X, Y, Z, U, V, W, I, J, K, R, Q] (Conversion eventuelle mm/inches)
pos = Units.Quantity(c.Parameters[param], FreeCAD.Units.Length)
outstring.append(param + format(float(pos.getValueAs(UNIT_FORMAT)), precision_string))
# store the latest command
lastcommand = command
# Memorizes the current position for calculating the related movements and the withdrawal plan
if command in MOTION_COMMANDS:
if 'X' in c.Parameters:
CURRENT_X = Units.Quantity(c.Parameters['X'], FreeCAD.Units.Length)
if 'Y' in c.Parameters:
CURRENT_Y = Units.Quantity(c.Parameters['Y'], FreeCAD.Units.Length)
if 'Z' in c.Parameters:
CURRENT_Z = Units.Quantity(c.Parameters['Z'], FreeCAD.Units.Length)
if command in ('G98', 'G99'):
DRILL_RETRACT_MODE = command
if command in ('G90', 'G91'):
MOTION_MODE = command
if TRANSLATE_DRILL_CYCLES:
if command in ('G81', 'G82', 'G83'):
out += drill_translate(outstring, command, c.Parameters)
# Erase the line we just translated
del(outstring[:])
outstring = []
if SPINDLE_WAIT > 0:
if command in ('M3', 'M03', 'M4', 'M04'):
out += linenumber() + format_outstring(outstring) + "\n"
out += linenumber() + format_outstring(['G4', 'P%s' % SPINDLE_WAIT]) + "\n"
del(outstring[:])
outstring = []
# Check for Tool Change:
if command in ('M6', 'M06'):
if OUTPUT_COMMENTS:
out += linenumber() + ";Begin toolchange\n"
if not OUTPUT_TOOL_CHANGE:
outstring[0] = "(" + outstring[0]
outstring[-1] = outstring[-1] + ")"
else:
for line in TOOL_CHANGE.splitlines(True):
out += linenumber() + line
if command == "message":
if OUTPUT_COMMENTS is False:
out = []
else:
outstring.pop(0) # remove the command
if command in SUPPRESS_COMMANDS:
outstring[0] = ";(" + outstring[0]
outstring[-1] = outstring[-1] + ")"
# prepend a line number and append a newline
if len(outstring) >= 1:
out += linenumber() + format_outstring(outstring) + "\n"
return out
def drill_translate(outstring, cmd, params):
global DRILL_RETRACT_MODE
global MOTION_MODE
global CURRENT_X
global CURRENT_Y
global CURRENT_Z
global UNITS
global UNIT_FORMAT
global UNIT_SPEED_FORMAT
strFormat = '.' + str(PRECISION) + 'f'
trBuff = ""
if OUTPUT_COMMENTS: # Comment the original command
outstring[0] = ";" + outstring[0]
outstring[-1] = outstring[-1] + "!"
trBuff += linenumber() + format_outstring(outstring) + "\n"
# Conversion du cycle
# Pour l'instant, on gere uniquement les cycles dans le plan XY (G17)
# les autres plans ZX (G18) et YZ (G19) ne sont pas traites : Calculs sur Z uniquement.
if MOTION_MODE == 'G90': # Deplacements en coordonnees absolues
drill_X = Units.Quantity(params['X'], FreeCAD.Units.Length)
drill_Y = Units.Quantity(params['Y'], FreeCAD.Units.Length)
drill_Z = Units.Quantity(params['Z'], FreeCAD.Units.Length)
RETRACT_Z = Units.Quantity(params['R'], FreeCAD.Units.Length)
else: # G91 Deplacements relatifs
drill_X = CURRENT_X + Units.Quantity(params['X'], FreeCAD.Units.Length)
drill_Y = CURRENT_Y + Units.Quantity(params['Y'], FreeCAD.Units.Length)
drill_Z = CURRENT_Z + Units.Quantity(params['Z'], FreeCAD.Units.Length)
RETRACT_Z = CURRENT_Z + Units.Quantity(params['R'], FreeCAD.Units.Length)
if DRILL_RETRACT_MODE == 'G98' and CURRENT_Z >= RETRACT_Z:
RETRACT_Z = CURRENT_Z
# Recupere les valeurs des autres parametres
drill_Speed = Units.Quantity(params['F'], FreeCAD.Units.Velocity)
if cmd == 'G83':
drill_Step = Units.Quantity(params['Q'], FreeCAD.Units.Length)
elif cmd == 'G82':
drill_DwellTime = params['P']
if MOTION_MODE == 'G91':
trBuff += linenumber() + "G90" + "\n" # Force des deplacements en coordonnees absolues pendant les cycles
# Mouvement(s) preliminaire(s))
if CURRENT_Z < RETRACT_Z:
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
trBuff += linenumber() + 'G0 X' + format(float(drill_X.getValueAs(UNIT_FORMAT)), strFormat) + ' Y' + format(float(drill_Y.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
if CURRENT_Z > RETRACT_Z:
trBuff += linenumber() + 'G0 Z' + format(float(CURRENT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
# Mouvement de percage
if cmd in ('G81', 'G82'):
trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
# Temporisation eventuelle
if cmd == 'G82':
trBuff += linenumber() + 'G4 P' + str(drill_DwellTime) + "\n"
# Sortie de percage
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
else: # 'G83'
next_Stop_Z = RETRACT_Z - drill_Step
while 1:
if next_Stop_Z > drill_Z:
trBuff += linenumber() + 'G1 Z' + format(float(next_Stop_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
next_Stop_Z -= drill_Step
else:
trBuff += linenumber() + 'G1 Z' + format(float(drill_Z.getValueAs(UNIT_FORMAT)), strFormat) + ' F' + format(float(drill_Speed.getValueAs(UNIT_SPEED_FORMAT)), '.2f') + "\n"
trBuff += linenumber() + 'G0 Z' + format(float(RETRACT_Z.getValueAs(UNIT_FORMAT)), strFormat) + "\n"
break
if MOTION_MODE == 'G91':
trBuff += linenumber() + 'G91' # Restore le mode de deplacement relatif
return trBuff
print(__name__ + ": GCode postprocessor loaded.")
Another FreeCAD video
This is a good video if you are creating lots of things that are similar but not identical.
Ko-Fi Link
https://ko-fi.com/adventuresincreation This is our Ko-fi link, if you want to show your appreciation for our channel feel free to leave us a coffee 🙂
FreeCAD Beginner Series
The FreeCAD beginner series has reached #8 – Our patrons already have access to #8.
The videos are
- FreeCAD for Beginners #1 – Download to First Model
- FreeCAD for Beginners #2 – Sketching and Constraints
- FreeCAD for Beginners #3 Offset Sketches and Datum Planes
- FreeCAD for Beginners #4 Modeling Revolves
- FreeCAD for Beginners #5 Lofts and Sweeps
- FreeCAD for Beginners #6 Golf Ball Marker project
- FreeCAD For Beginners #7 Spreadsheet Driven Fully Parametric Table
- FreeCAD For Beginners #8 – Boolean Operations
FreeCAD Beginner Video Series
We have created a beginner series of videos for FreeCAD. It’s great to be able to share our experience with the software and help others with their journey through Adventures in CreationÂ
You can find the beginner series playlist on our YouTube channel at this link
Don’t forget to give us feedback once you have had a chance to review the videos.
Keeping up with the changes
It’s been a while since we posted a new update. FreeCAD has become our go-to CAD/CAM package. It’s working out nicely as a tool to create 3D models for either CNC routing or 3D printing. The Path module is good but not 100% complete yet. It continues to improve with time.
We are working on creating some helpful videos for other users that want to get the most out of FreeCAD and their maker experience. Check out our channel http://www.youtube.com/adventuresincreation
MPCNC inlays
I am still working on creating a flawless inlay. I have found some wood that I can use but now I have to find a good subject. I am starting to get a feel for the type of graphic that would make a good inlay candidate.
Take a look at this page MPCNC vcarve inlay technique it should give you a good idea of where I am with this.
I have created some videos on the subject you can find them here YouTube video
Home Automation
As you may have seen I have the Amazon Echo at home. I liked the potential to make the echo the hub for home automation. The first step is to get something you can control so I set out to find something that works with Alexa natively so there would be no issues with integration. I searched Amazon for the a switchable plug that I could plug my living room light in to. I found the TP-LINK HS100.
This is a wifi enabled smart plug. You plug it in and from an Android app set it up on your wi-fi network. Once it’s set up you just ask Alexa to look for new devices and it will find the smart plug. Through the set up you designate a name for the device so now you can say something like “Alexa turn on the living room light” and it will switch it on and say “OK”. This is cool and how I work my living room light today. Of course the light is just one thing you can turn any item on that is plugged in to the smart plug. It is just a simple way to power on and power off. This is the beginning of automating the home of course, the smart plug is about $30 so this could be an expensive way to avoid turning a light switch. I will admit that I got mine a lot cheaper than that, it was on sale and I had a gift card from a survey so I ended up paying about $4 out of pocket.  Since we have had that light operated by Alexa we have wished that we had one for the bedroom light so that it would be on when we go upstairs. Now imagine, instead of saying “Alexa turn off the living room light” at the end of the day we could say “Alexa its bed time” and have her turn off the living room light, turn off the tv and the tuner, turn on the bedroom light, set the alarm, check the garage door is closed, and start the sounds of the sea music. – Well, this is possible if you introduce something like Openhab. What is this you ask, here’s a brief description of Openhab
Cat Wheel Chair
I met a sweetheart little cat who had been hit by a car and injured his spine. Regardless of this horrific injury he is a lively and inquisitive little fellow that can move his front legs and scoot around on his bottom. This can cause issues with wear and tear on his rear end. His back legs appear to have some feeling but are not working as they should. He needs a “cat wheel chair” so I thought how can we create something light enough, strong enough and cheap enough that we can make it a reality for him. How about some some plastic plumbing pipe and pair of wheels?
Here’s the first attempt. The idea is his back legs will sit in the sling and the front of the wheelchair will be attached to a harness.
Here he is trying it on for the first time. It needs some adjusting, it’s a little too tall for him yet. We will get that right and do a final fitting then hopefully, he will be able to move himself around without hurting himself.
Great news! Thanks to the dedication of the folks at Hoof and Paw Vets this little guy is able to walk again… Â Just look here.