Selling for $30, with $5 costs. A side hustle that failed. πŸͺ

In the past years, I had a few different and weird business ideas / side hustles that I started, but in the end, many of them failed. Recently I decided that I’ll write a blog post about each one, mostly for myself to understand what went wrong and what I could do better in the future. They failed but were just fun to do and I got a lot of experience and good memories. From each one I learned something, but also I can see some common mistakes repeated each time.

Back In 2015, I started a small business – a website (twoja-foremka.pl AKA your-cookie-cutter), where you could upload your custom shape (PNG file) and I printed a cookie cutter from it in 3D. It was my third business about 3D printing that I started – I’ll write about them in the next posts.

How it was working?

All were semi-automated. I wrote a script that from the image file (png) was generating a 3D model suitable for a 3D printer. Then I was sending the model over email to someone that I found on the internet who was printing it and sending it directly to the customer.

I didn’t have many customers, just a few, but the margin was nice. I charged $30 for one, and printing cost me about $4, shipping $1. So I had $25 in my pocket – just for putting the PNG to the script and sending an email with an STL file to someone who prints it – about 5-10 minutes of work. For more technical details scroll down below to the last section.

What was done wrong?

I did have a 3D printer (RepRap) so I tested it myself and I was making cookies for the first time in my life. That was the first problem, I didn’t know anything about making cookies. But it turned out easy (thanks mom for helpπŸ˜‚) and here are some photos of cookies I’ve made while testing:

The biggest issue was that I didn’t have a clue who my customer was. I 100% focused on the technical side – which was fun for me. It’s not a business mindset. I was thinking that those cookie cutters are so cool because you can make a cookie from anything, like company logos or 3D puzzles, chess, lions, houses, etc. Imagine inviting your client to your office, and the cookies on the table are logos of their company – that’s brilliant. So I thought that maybe It’ll go viral somehow.

I didn’t write to companies or talk to the right people, I just set up Google / Facebook ads and I thought it will do the job for me, but it didn’t work this way. I also had a Facebook page (still online https://www.facebook.com/twojaforemka) and an online shop (PrestaShop), with ready-to-buy cutters, but with no traffic. So as I finished the interesting – technical side, I got bored and just left to the next idea – another big issue when you start a business.

What could be done better?

I should have found a non-technical partner that knows what to do to find customers and research the market. I didn’t have any motivation to do this. When someone says “marketing” or “sales” I just get instantly bored, I just can’t do this it is so boring for me. So I think the best thing I should have done is find a partner. But where or how can someone like this be found, someone that you can trust? If you know, post a comment, or write to me directly on my LinkedIn profile. Or maybeπŸ€”.. maybe this is a good business idea! A portal that combines technical people like me with people of marketing/sales?

What I’ve learned?

Aside from the issues above and technical stuff, the thing that had the biggest impact on me was the margin. I never thought that I could set it so high. It was shocking for me back then, that I can sell for $30 with costs around $5, with almost any work within the order.

Technical details – how I was converting PNG to a 3D ready-to-print STL file?

I already had some experience with Blender and using it programmatically with Python scripts. The mechanism was really easy. First, the PHP script was preparing the image, generating a stroke on it. Then the commands were run:

  • mogrify -flop $filename
  • convert $filename -transparent white $filename
  • convert $filename -transparent black $filename
  • convert $filename -fill black -opaque red $filename
  • convert -flatten $filename $filename.ppm
  • potrace -s $filename.ppm

Then the image was opened in Blender, and the script there was doing the magic. It was loading the SVG and extending the mesh upwards. Then it was exporting it to the STL file that can be sent directly to the printer. There was also a mechanism for creating 3D renders. There is the Python code for generating the model. I doubt it works now, as Blender often changes its API, and it is from 2015. I haven’t tested it now either.

import bpy, sys, copy, mathutils,time,math,array,bmesh,math


args = sys.argv
argv = args[args.index("--") + 1:]

def getArg(index):
    if (len(argv) <= index):
        return ""
    else:
        return argv[index]

def enterEditMode():
    bpy.ops.object.mode_set(mode='EDIT')

def enterObjectMode():
    bpy.ops.object.mode_set(mode='OBJECT')

def selectAllObjects():
    bpy.ops.object.select_all(action='SELECT')
    
def deselectAllObjects():
    bpy.ops.object.select_all(action='DESELECT')
    
def selectMeshAll():
    bpy.ops.mesh.select_all(action='SELECT')
    
def deselectMeshAll():
    bpy.ops.mesh.select_all(action='DESELECT')
    
def getAllObjects():
    return bpy.data.objects

def getMeshObjects():
    result = []
    for item in getAllObjects():  
        if item.type == 'MESH': 
            result.append(item)
    return result
        
def getSelectedObject():
    return bpy.context.scene.objects.active

def setSelectedObject(obj):
    bpy.context.scene.objects.active = obj
    
    
# MESH UTILS

class SizeInformation:
      min = None
      max = None
      size = None
      center = None
      biggest = None
      def __init__(self, min, max):
          self.min = min
          self.max = max
          self.center = self.min + ((self.max - self.min) / 2)
          self.size = self.max - self.min
          self.biggest = self.size.x
          if self.size.y > self.size.x:
              self.biggest = self.size.y
              if self.size.z > self.size.y:
                  self.biggest = self.size.z
          elif self.size.z > self.size.x:
              self.biggest = self.size.z
              
      
      def printInformation(self):
          print("Min: ", self.min)
          print("Max: ", self.max)
          print("Center: ", self.center)
          print("Size: ", self.size)
          
      def isNull(self):
          return self.size.x == 0 or self.size.y == 0 or self.size.z == 0

def getSizeAll():
    max = None
    min = None
    for item in bpy.data.objects:  
        if item.type == 'MESH':  
            for vertex in item.data.vertices:
                position = vertex.co  # + item.location
                if max == None:
                    max = copy.copy(position)
                if position.x > max.x:
                    max.x = position.x
                if position.y > max.y:
                    max.y = position.y
                if position.z > max.z:
                    max.z = position.z

                if min == None:
                    min = copy.copy(position)
                if position.x < min.x:
                    min.x = position.x
                if position.y < min.y:
                    min.y = position.y
                if position.z < min.z:
                    min.z = position.z
    if max == None:
        max = mathutils.Vector((0.0,0.0,0.0))
    if min == None:
        min = mathutils.Vector((0.0,0.0,0.0))
    return SizeInformation(min, max)

def scaleAll(scale):
    for item in bpy.data.objects:  
        if item.type == 'MESH':
           item.scale *= scale
           item.location *= scale
           bpy.context.scene.objects.active = item
           bpy.ops.object.transform_apply(scale=True)
           bpy.ops.object.transform_apply(location=True)
           bpy.ops.object.transform_apply(rotation=True)

def clean(obj,normals=False):
    bpy.ops.object.select_all(action='DESELECT')
    obj.select = True
    bpy.context.scene.objects.active = obj
    bpy.ops.object.transform_apply(scale=True)
    bpy.ops.object.transform_apply(location=True)
    bpy.ops.object.transform_apply(rotation=True)
    bpy.ops.object.mode_set(mode='EDIT')
    bpy.ops.mesh.remove_doubles()
    bpy.ops.mesh.select_all(action='SELECT')
    bpy.ops.mesh.quads_convert_to_tris()
    if normals:
        bpy.ops.mesh.normals_make_consistent(inside=False)
    bpy.ops.object.mode_set(mode='OBJECT')

# LIB END


svg1 = getArg(0)
svg2 = getArg(1)
y = float(getArg(2))
out = getArg(3)

height = 10.0
handleHeight = 1.0


bpy.ops.import_curve.svg(filepath=svg1)
bpy.ops.import_curve.svg(filepath=svg2)


for obj in getAllObjects():
    deselectAllObjects()
    setSelectedObject(obj)
    obj.select = True
    bpy.ops.object.convert()


selectAllObjects()


size = getSizeAll()

scale = y / size.size.y
scaleAll(scale)

objects = getMeshObjects()

def extrude(obj,height):
    enterObjectMode()
    deselectAllObjects()
    setSelectedObject(obj)
    enterEditMode()
    bpy.context.tool_settings.mesh_select_mode = (False, False, True)
    selectMeshAll()
    vector = mathutils.Vector((0.0,0.0,height))
    bpy.ops.mesh.extrude_region_move(MESH_OT_extrude_region={"mirror":False}, 
                                TRANSFORM_OT_translate={"value":vector,"release_confirm": False})
    
def run():
    i = 0
    for obj in objects:
        theHeight = handleHeight
        if (i < len(objects)/2):
            theHeight = height
        extrude(obj,theHeight)
        enterObjectMode()
        clean(obj)
        
        i += 1
    for obj in getMeshObjects():
        obj.select = True
run()
bpy.ops.object.select_all(action='SELECT')
bpy.ops.export_mesh.stl(filepath=out)

The end

The next post will be about a 3D people scanner machine that I’ve built πŸ˜… . Bigger failure than this above, but also more interesting.

Did you enjoy the post? Or maybe did you have a failed project like this in the past? Let me know in the comments.