diff --git a/object_fracture_voroni/__init__.py b/object_fracture_voroni/__init__.py
index 0f78474afae429adf9866e18abe8a64cb2e8b8e9..a63eef41567a6aa80d710defba4bd0936d796553 100644
--- a/object_fracture_voroni/__init__.py
+++ b/object_fracture_voroni/__init__.py
@@ -31,48 +31,213 @@ bl_info = {
     "category": "Object"}
 
 
-if "bpy" in locals():
-    import imp
-    imp.reload(fracture_cell_setup)
+#if "bpy" in locals():
+#    import imp
+#    imp.reload(fracture_cell_setup)
 
 import bpy
+from bpy.props import (StringProperty,
+                       BoolProperty,
+                       IntProperty,
+                       FloatProperty,
+                       EnumProperty)
+
 from bpy.types import Operator
 
-def main(context):
+def main_object(scene, obj, level, **kw):
+    import random
+
+    # pull out some args
+    kw_copy = kw.copy()
+    use_recenter = kw_copy.pop("use_recenter")
+    use_remove_original = kw_copy.pop("use_remove_original")
+    recursion = kw_copy.pop("recursion")
+    recursion_chance = kw_copy.pop("recursion_chance")
+    
     from . import fracture_cell_setup
+    
+    objects = fracture_cell_setup.cell_fracture_objects(scene, obj, **kw_copy)
+    objects = fracture_cell_setup.cell_fracture_boolean(scene, obj, objects)
+
+    # todo, split islands.
+
+    # must apply after boolean.
+    if use_recenter:
+        bpy.ops.object.origin_set({"selected_editable_objects": objects},
+                                  type='ORIGIN_GEOMETRY', center='MEDIAN')
+
+    if level < recursion:
+        objects_recursive = []
+        for i in range(len(objects) - 1, -1, -1):  # reverse loop
+            
+            if recursion_chance == 1.0 or recursion_chance < random.random():
+                obj_cell = objects[i]
+                objects_recursive += main_object(scene, obj_cell, level + 1, **kw)
+                if use_remove_original:
+                    scene.objects.unlink(obj_cell)
+                    del objects[i]
+        objects.extend(objects_recursive)
+                
+
+    # testing only!
+    obj.hide = True
+    return objects
+
+
+def main(context, **kw):
+    import time
+    t = time.time()
+    scene = context.scene
     obj = context.active_object
-    objects = fracture_cell_setup.cell_fracture_objects(context, obj)
-    objects = fracture_cell_setup.cell_fracture_boolean(context, obj, objects)
+    objects = main_object(scene, obj, 0, **kw)
 
     bpy.ops.object.select_all(action='DESELECT')
     for obj_cell in objects:
         obj_cell.select = True
+    
+    print("Done! %d objects in %.4f sec" % (len(objects), time.time() - t))
 
 
 class FractureCell(Operator):
     bl_idname = "object.add_fracture_cell_objects"
     bl_label = "Cell Fracture Helper Objects"
+    bl_options = {'PRESET'}
+
+    # -------------------------------------------------------------------------
+    # Source Options
+    source = EnumProperty(
+            name="Source",
+            items=(('VERT_OWN', "Own Verts", "Use own vertices"),
+                   ('EDGE_OWN', "Own Edges", "Use own edges"),
+                   ('FACE_OWN', "Own Faces", "Use own faces"),
+                   ('VERT_CHILD', "Child Verts", "Use own vertices"),
+                   ('EDGE_CHILD', "Child Edges", "Use own edges"),
+                   ('FACE_CHILD', "Child Faces", "Use own faces"),
+                   ('PARTICLE', "Particles", ("All particle systems of the "
+                                              "source object")),
+                   ('PENCIL', "Grease Pencil", "This objects grease pencil"),
+                   ),
+            options={'ENUM_FLAG'},
+            default={'PARTICLE', 'VERT_OWN'}  # 'VERT_OWN', 'EDGE_OWN', 'FACE_OWN'
+            )
+
+    source_limit = IntProperty(
+            name="Source Limit",
+            description="Limit the number of input points, 0 for unlimited",
+            min=0, max=5000,
+            default=1000,
+            )
+
+    source_noise = FloatProperty(
+            name="Noise",
+            description="Randomize point distrobution",
+            min=0.0, max=1.0,
+            default=0.0,
+            )
+
+    # -------------------------------------------------------------------------
+    # Mesh Data Options
+
+    use_smooth_faces = BoolProperty(
+            name="Smooth Faces",
+            default=False,
+            )
+
+    use_smooth_edges = BoolProperty(
+            name="Smooth Edges",
+            description="Set sharp edges whem disabled",
+            default=True,
+            )
+
+    use_data_match = BoolProperty(
+            name="Match Data",
+            description="Match original mesh materials and data layers",
+            default=True,
+            )
+
+    use_island_split = BoolProperty(
+            name="Split Islands",
+            description="Split disconnected meshes",
+            default=True,
+            )
+
+    # -------------------------------------------------------------------------
+    # Object Options
+
+    use_recenter = BoolProperty(
+            name="Recenter",
+            description="Recalculate the center points after splitting",
+            default=True,
+            )
+
+    use_remove_original = BoolProperty(
+            name="Remove Original",
+            description="Removes the parents used to create the shatter",
+            default=True,
+            )
+
+    # -------------------------------------------------------------------------
+    # Recursion
+
+    recursion = IntProperty(
+            name="Recursion",
+            description="Break shards resursively",
+            min=0, max=5000,
+            default=0,
+            )
+
+    recursion_chance = FloatProperty(
+            name="Random Factor",
+            description="Likelyhood of recursion",
+            min=0.0, max=1.0,
+            default=1.0,
+            )
 
     def execute(self, context):
-        main(context)
+        keywords = self.as_keywords()  # ignore=("blah",)
+
+        main(context, **keywords)
+
         return {'FINISHED'}
 
-'''
-class INFO_MT_add_fracture_objects(bpy.types.Menu):
-    bl_idname = "INFO_MT_add_fracture_objects"
-    bl_label = "Fracture Helper Objects"
+
+    def invoke(self, context, event):
+        wm = context.window_manager
+        return wm.invoke_props_dialog(self, width=600)
 
     def draw(self, context):
         layout = self.layout
-        layout.operator_context = 'INVOKE_REGION_WIN'
-
-        layout.operator("object.import_fracture_bomb",
-            text="Bomb")
-        layout.operator("object.import_fracture_projectile",
-            text="Projectile")
-        layout.operator("object.import_fracture_recorder",
-            text="Rigidbody Recorder")
-'''
+        box = layout.box()
+        col = box.column()
+        col.label("Point Source")
+        rowsub = col.row()
+        rowsub.prop(self, "source")
+        rowsub = col.row()
+        rowsub.prop(self, "source_limit")
+        rowsub.prop(self, "source_noise")
+        rowsub = col.row()
+
+        box = layout.box()
+        col = box.column()
+        col.label("Mesh Data")
+        rowsub = col.row(align=True)
+        rowsub.prop(self, "use_smooth_faces")
+        rowsub.prop(self, "use_smooth_edges")
+        rowsub.prop(self, "use_data_match")
+        # rowsub.prop(self, "use_island_split")  # TODO
+
+        box = layout.box()
+        col = box.column()
+        col.label("Object")
+        rowsub = col.row(align=True)
+        rowsub.prop(self, "use_recenter")
+
+        box = layout.box()
+        col = box.column()
+        col.label("Recursive Shatter")
+        rowsub = col.row(align=True)
+        rowsub.prop(self, "recursion")
+        rowsub.prop(self, "recursion_chance")
 
 #def menu_func(self, context):
 #    self.layout.menu("INFO_MT_add_fracture_objects", icon="PLUGIN")
diff --git a/object_fracture_voroni/fracture_cell_calc.py b/object_fracture_voroni/fracture_cell_calc.py
index 3b5d6da8dcc2823d4503eb9bd2788166247235f3..e25a0ee3951c2c3b823005ec93770f99a00cb89c 100644
--- a/object_fracture_voroni/fracture_cell_calc.py
+++ b/object_fracture_voroni/fracture_cell_calc.py
@@ -27,7 +27,7 @@ def points_as_bmesh_cells(verts, points, margin=0.01):
 
     cells = []
 
-    sortedVoronoiPoints = [p for p in points]
+    points_sorted_current = [p for p in points]
     plane_indices = []
     vertices = []
 
@@ -37,7 +37,7 @@ def points_as_bmesh_cells(verts, points, margin=0.01):
         xa = [v[0] for v in verts]
         ya = [v[1] for v in verts]
         za = [v[2] for v in verts]
-        
+
         xmin, xmax = min(xa) - margin, max(xa) + margin
         ymin, ymax = min(ya) - margin, max(ya) + margin
         zmin, zmax = min(za) - margin, max(za) + margin
@@ -50,19 +50,19 @@ def points_as_bmesh_cells(verts, points, margin=0.01):
             Vector((0.0, 0.0, -1.0, -abs(zmin))),
             ]
 
-    for i, curVoronoiPoint in enumerate(points):
+    for i, point_cell_current in enumerate(points):
         planes = [None] * len(convexPlanes)
         for j in range(len(convexPlanes)):
             planes[j] = convexPlanes[j].copy()
-            planes[j][3] += planes[j].xyz.dot(curVoronoiPoint)
-        maxDistance = 10000000000.0  # a big value!
+            planes[j][3] += planes[j].xyz.dot(point_cell_current)
+        distance_max = 10000000000.0  # a big value!
 
-        sortedVoronoiPoints.sort(key=lambda p: (p - curVoronoiPoint).length_squared)
+        points_sorted_current.sort(key=lambda p: (p - point_cell_current).length_squared)
 
         for j in range(1, len(points)):
-            normal = sortedVoronoiPoints[j] - curVoronoiPoint
+            normal = points_sorted_current[j] - point_cell_current
             nlength = normal.length
-            if nlength > maxDistance:
+            if nlength > distance_max:
                 break
 
             plane = normal.normalized()
@@ -77,17 +77,17 @@ def points_as_bmesh_cells(verts, points, margin=0.01):
             if len(plane_indices) != len(planes):
                 planes[:] = [planes[k] for k in plane_indices]
 
-            maxDistance = vertices[0].length
+            distance_max = vertices[0].length
             for k in range(1, len(vertices)):
                 distance = vertices[k].length
-                if maxDistance < distance:
-                    maxDistance = distance
-            maxDistance *= 2.0
+                if distance_max < distance:
+                    distance_max = distance
+            distance_max *= 2.0
 
         if len(vertices) == 0:
             continue
 
-        cells.append((curVoronoiPoint, vertices[:]))
+        cells.append((point_cell_current, vertices[:]))
         vertices[:] = []
 
     return cells
diff --git a/object_fracture_voroni/fracture_cell_setup.py b/object_fracture_voroni/fracture_cell_setup.py
index de3e1210470a7a8698a7402feeee73a76a648f31..631f11e3411bd01b413ee9c7dc3d6065f10f67fc 100644
--- a/object_fracture_voroni/fracture_cell_setup.py
+++ b/object_fracture_voroni/fracture_cell_setup.py
@@ -23,30 +23,152 @@
 import bpy
 import bmesh
 
+def _points_from_object(obj, source):
 
-def cell_fracture_objects(context, obj, method={'PARTICLES'}, clean=True):
+    _source_all = {
+        'PARTICLE', 'PENCIL',
+        'VERT_OWN', 'EDGE_OWN', 'FACE_OWN',
+        'VERT_CHILD', 'EDGE_CHILD', 'FACE_CHILD'}
 
-    #assert(method in {'OTHER', 'PARTICLES'})
-    
-    from . import fracture_cell_calc
+    print(source - _source_all)
+    print(source)
+    assert(len(source | _source_all) == len(_source_all))
+    assert(len(source))
 
     points = []
 
-    if 'PARTICLES' in method:
+    def edge_center(mesh, edge):
+        v1, v2 = edge.vertices
+        return (mesh.vertices[v1].co + mesh.vertices[v2].co) / 2.0
+
+    def poly_center(mesh, poly):
+        from mathutils import Vector
+        co = Vector()
+        tot = 0
+        for i in poly.loop_indices:
+            co += mesh.vertices[mesh.loops[i].vertex_index].co
+            tot += 1
+        return co / tot
+
+    def points_from_verts(obj):
+        if obj.type == 'MESH':
+            mesh = obj.data
+            matrix = obj.matrix_world.copy()
+            points.extend([matrix * v.co for v in mesh.vertices])
+
+    def points_from_edges(obj):
+        if obj.type == 'MESH':
+            mesh = obj.data
+            matrix = obj.matrix_world.copy()
+            points.extend([matrix * edge_center(mesh, e) for e in mesh.edges])
+
+    def points_from_faces(obj):
+        if obj.type == 'MESH':
+            mesh = obj.data
+            matrix = obj.matrix_world.copy()
+            points.extend([matrix * poly_center(mesh, p) for p in mesh.polygons])
+
+    # geom own
+    if 'VERT_OWN' in source:
+        points_from_verts(obj)
+    if 'EDGE_OWN' in source:
+        points_from_edges(obj)
+    if 'FACE_OWN' in source:
+        points_from_faces(obj)
+
+    # geom children
+    if 'VERT_CHILD' in source:
+        for obj_child in obj.children:
+            points_from_verts(obj_child)
+    if 'EDGE_CHILD' in source:
+        for obj_child in obj.children:
+            points_from_edges(obj_child)
+    if 'FACE_CHILD' in source:
+        for obj_child in obj.children:
+            points_from_faces(obj_child)
+
+    # geom particles
+    if 'PARTICLE' in source:
         points.extend([p.location.copy()
                          for psys in obj.particle_systems
                          for p in psys.particles])
 
-    if 'OTHER' in method:
-        for obj_other in context.selected_objects:
-            if obj_other.type == 'MESH':
-                mesh = obj_other.data
-                matrix = obj_other.matrix_world.copy()
-                points.extend([matrix * v.co for v in mesh.vertices])
+    # grease pencil
+    def get_points(stroke):
+        return [point.co.copy() for point in stroke.points]
+
+    def get_splines(gp):
+        if gp.layers.active:
+            frame = gp.layers.active.active_frame
+            return [get_points(stroke) for stroke in frame.strokes]
+        else:
+            return []
+
+    if 'PENCIL' in source:
+        gp = obj.grease_pencil
+        if gp:
+            points.extend([p for spline in get_splines(gp)
+                             for p in spline])
+
+    return points
+
+
+def cell_fracture_objects(scene, obj,
+                          source={'PARTICLE'},
+                          source_limit=0,
+                          source_noise=0.0,
+                          clean=True,
+                          # operator options
+                          use_smooth_faces=False,
+                          use_smooth_edges=True,
+                          use_data_match=False,
+                          use_island_split=False,
+                          ):
+    
+    from . import fracture_cell_calc
+
+    # -------------------------------------------------------------------------
+    # GET POINTS
+
+    points = _points_from_object(obj, source)
 
     if not points:
+        # print using fallback
+        points = _points_from_object(obj, source | {'VERT_OWN'})
+
+    if not points:
+        print("no points found")
         return []
 
+    # apply optional clamp
+    if source_limit != 0 and source_limit < len(points):
+        import random
+        random.shuffle(points)
+        points[source_limit:] = []
+
+
+    # saddly we cant be sure there are no doubles
+    from mathutils import Vector
+    to_tuple = Vector.to_tuple
+    points = list({to_tuple(p, 4): p for p in points}.values())
+    del to_tuple
+    del Vector
+
+
+    if source_noise > 0.0:
+        # boundbox approx of overall scale
+        from mathutils import Vector
+        matrix = obj.matrix_world.copy()
+        bb_world = [matrix * Vector(v) for v in obj.bound_box]
+        scalar = (bb_world[0] - bb_world[6]).length / 2.0
+
+        from mathutils.noise import noise_vector
+        
+        points[:] = [p + (noise_vector(p) * scalar) for p in points]
+    
+    # end remove doubles
+    # ------------------
+
     mesh = obj.data
     matrix = obj.matrix_world.copy()
     verts = [matrix * v.co for v in mesh.vertices]
@@ -54,17 +176,14 @@ def cell_fracture_objects(context, obj, method={'PARTICLES'}, clean=True):
     cells = fracture_cell_calc.points_as_bmesh_cells(verts, points)
     
     # some hacks here :S
-    scene = context.scene
     cell_name = obj.name + "_cell"
     
     objects = []
     
     for center_point, cell_points in cells:
-        mesh = bpy.data.meshes.new(name=cell_name)
-        obj_cell = bpy.data.objects.new(name=cell_name, object_data=mesh)
-        scene.objects.link(obj_cell)
-        # scene.objects.active = obj_cell
-        obj_cell.location = center_point
+
+        # ---------------------------------------------------------------------
+        # BMESH
 
         # create the convex hulls
         bm = bmesh.new()
@@ -73,10 +192,12 @@ def cell_fracture_objects(context, obj, method={'PARTICLES'}, clean=True):
             bm_vert.tag = True
 
         import mathutils
-        bm.transform(mathutils.Matrix.Translation((+100.0, +100.0, +100.0))) # BUG IN BLENDER
         bmesh.ops.remove_doubles(bm, {'TAG'}, 0.0001)
-        bmesh.ops.convex_hull(bm, {'TAG'})
-        bm.transform(mathutils.Matrix.Translation((-100.0, -100.0, -100.0))) # BUG IN BLENDER
+        try:
+            bmesh.ops.convex_hull(bm, {'TAG'})
+        except RuntimeError:
+            import traceback
+            traceback.print_exc()
 
         if clean:
             for bm_vert in bm.verts:
@@ -84,20 +205,66 @@ def cell_fracture_objects(context, obj, method={'PARTICLES'}, clean=True):
             for bm_edge in bm.edges:
                 bm_edge.tag = True
             bm.normal_update()
-            bmesh.ops.dissolve_limit(bm, {'TAG'}, {'TAG'}, 0.001)
+            try:
+                bmesh.ops.dissolve_limit(bm, {'TAG'}, {'TAG'}, 0.001)
+            except RuntimeError:
+                import traceback
+                traceback.print_exc()
+
+        if use_smooth_faces:
+            for bm_face in bm.faces:
+                bm_face.smooth = True
+
+        if use_smooth_edges:
+            for bm_edge in bm.edges:
+                bm_edge.smooth = True
+
+
+        # ---------------------------------------------------------------------
+        # MESH
 
-        bm.to_mesh(mesh)
+        mesh_dst = bpy.data.meshes.new(name=cell_name)
+
+        bm.to_mesh(mesh_dst)
         bm.free()
+        del bm
+
+        if use_data_match:
+            # match materials and data layers so boolean displays them
+            # currently only materials + data layers, could do others...
+            mesh_src = obj.data
+            for mat in mesh_src.materials:
+                mesh.materials.append(mat)
+            for lay_attr in ("vertex_colors", "uv_layers"):
+                lay_src = getattr(mesh_src, lay_attr)
+                lay_dst = getattr(mesh_dst, lay_attr)
+                for key in lay_src.keys():
+                    lay_dst.new(name=key)
+
+        # ---------------------------------------------------------------------
+        # OBJECT
+
+        obj_cell = bpy.data.objects.new(name=cell_name, object_data=mesh_dst)
+        scene.objects.link(obj_cell)
+        # scene.objects.active = obj_cell
+        obj_cell.location = center_point
 
         objects.append(obj_cell)
-    
+
     scene.update()
 
+
+    # move this elsewhere...
+    for obj_cell in objects:
+        game = obj_cell.game
+        game.physics_type = 'RIGID_BODY'
+        game.use_collision_bounds = True
+        game.collision_bounds_type = 'TRIANGLE_MESH'
+
     return objects
 
 
-def cell_fracture_boolean(context, obj, objects, apply=True, clean=True):
-    scene = context.scene
+def cell_fracture_boolean(scene, obj, objects, apply=True, clean=True):
 
     objects_boolean = []
     
@@ -107,7 +274,9 @@ def cell_fracture_boolean(context, obj, objects, apply=True, clean=True):
         mod.operation = 'INTERSECT'
 
         if apply:
-            mesh_new = obj_cell.to_mesh(scene, apply_modifiers=True, settings='PREVIEW')
+            mesh_new = obj_cell.to_mesh(scene,
+                                        apply_modifiers=True,
+                                        settings='PREVIEW')
             mesh_old = obj_cell.data
             obj_cell.data = mesh_new
             obj_cell.modifiers.remove(mod)
@@ -119,10 +288,12 @@ def cell_fracture_boolean(context, obj, objects, apply=True, clean=True):
                 scene.objects.unlink(obj_cell)
                 if not obj_cell.users:
                     bpy.data.objects.remove(obj_cell)
+                    obj_cell = None
                     if not mesh_new.users:
                         bpy.data.meshes.remove(mesh_new)
+                        mesh_new = None
 
-            if clean:
+            if clean and mesh_new is not None:
                 bm = bmesh.new()
                 bm.from_mesh(mesh_new)
                 for bm_vert in bm.verts:
@@ -130,9 +301,14 @@ def cell_fracture_boolean(context, obj, objects, apply=True, clean=True):
                 for bm_edge in bm.edges:
                     bm_edge.tag = True
                 bm.normal_update()
-                bmesh.ops.dissolve_limit(bm, {'TAG'}, {'TAG'}, 0.01)
+                try:
+                    bmesh.ops.dissolve_limit(bm, {'TAG'}, {'TAG'}, 0.001)
+                except RuntimeError:
+                    import traceback
+                    traceback.print_exc()
                 bm.to_mesh(mesh_new)
                 bm.free()
 
-        objects_boolean.append(obj_cell)
+        if obj_cell is not None:
+            objects_boolean.append(obj_cell)
     return objects_boolean