Automating the import of hundreds of sprites to Construct

I’m making a game where an area of ground is broken piece by piece. I managed to create a python script that uses a voronoi pattern to shatter my image into several small irregular pieces. Here’s how that looked:

I then needed to import those 200 pieces into Construct. I figured out that if I set the position of each piece to 0,0, they would form the full image perfectly. After that I can just crop the pieces to their actual pixels and things work really nicely: the voronoi jigsaw puzzle comes together like it should.

The only pain point I had left was importing 200 sprites one by one into Construct. I knew that I could drag them all at the same time on top of Construct, which would create an animation. But I needed to create several individual sprites instead of animation frames.

The real life saver in this situation turned out to be the Project Folder feature in Construct (works best in Chrome), since it gives direct access to the project file structure. So here are the steps I performed and the scripts I used:

Please don’t attempt this with your production code, instead start a new empty Construct project and save it as a Project Folder on your local hard disk

1. Use a Python script to loop through all the image files in a folder and have it generate the JSON files Construct expects to have in the objectTypes folder. Here is the script for that (zipped due to security considerations). To run that, just unzip, place it in the same folder with the images, open a terminal there and type: python generateProjectJSON.py and hit enter. A bunch of JSON files should appear which you should copy to your Project Folder’s objectTypes folder.

2. Next the project.c3proj file needed to have the files listed in the items array in this manner:

“objectTypes”: {
“items”: [
“myImage_0”,
“myImage_1”,
“myImage_2”
],

So made a script to generate the array. Note that this is a very simple script and could be improved to support more files name conventions. At the moment it is just adding an incrementing number at the end of each image name. This script will output a simple array in a file called names.txt. Copy the array to your project.c3proj file.

3. The images needed to be renamed with -default-000.png appended to their file name. Time for another script, run this similarly in the folder with the images. After that I copied the images into the images folder inside the Project Folder.

4. That’s basically it but I also wanted the sprites already placed in the layout at coordinates 0,0 so I wrote one more script to do that. Run this script where you have your JSON files (that we generated earlier) and it will give you a new file called new_layout.json. Take the “instances” array from the generated file and merge it with your Layout 1.json file (which can be found in the layouts folder).

Now just reload the project and something magical happens: all your images will appear in the project and in the layout! Even if it’s hundreds or even thousands of images.

So there we have it. This workflow is obviously not useful if you just need to import a small number of sprites. But if you needs hundreds, then this can definitely save you some frustrating manual labor.

Render a folder full of STL files to PNG images

I wanted to create images out of all the STL-files I had 3D-printed so far. Here is a script that automates the process using Blender.

import bpy
import os
import math
from bpy_extras.object_utils import world_to_camera_view

def clear_scene():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def setup_camera_light():
    bpy.ops.object.camera_add(location=(0, -10, 5))
    camera = bpy.context.active_object
    camera.rotation_euler = (1.0, 0, 0)
    bpy.context.scene.camera = camera

    bpy.ops.object.light_add(type='SUN', align='WORLD', location=(0, 0, 10))
    light = bpy.context.active_object
    light.rotation_euler = (1.0, 0, 0)

def create_red_material():
    red_material = bpy.data.materials.new(name="RedMaterial")
    red_material.use_nodes = True
    red_material.node_tree.nodes["Principled BSDF"].inputs["Base Color"].default_value = (1, 0, 0, 1)
    return red_material



from mathutils import Vector

def set_camera_position(camera, obj):
    bound_box = obj.bound_box
    min_x, max_x = min(v[0] for v in bound_box), max(v[0] for v in bound_box)
    min_y, max_y = min(v[1] for v in bound_box), max(v[1] for v in bound_box)
    min_z, max_z = min(v[2] for v in bound_box), max(v[2] for v in bound_box)
    
    # Calculate object dimensions
    width = max_x - min_x
    height = max_y - min_y
    depth = max_z - min_z

    # Calculate object center
    center_x = min_x + (width / 2)
    center_y = min_y + (height / 2)
    center_z = min_z + (depth / 2)

    # Calculate distance from camera to object center
    distance = max(width, height, depth) * 2.5  # Increase the multiplier from 2 to 2.5

    # Set camera location and rotation
    camera.location = (center_x, center_y - distance, center_z + (distance / 2))
    camera.rotation_euler = (math.radians(60), 0, 0)




def import_stl_and_render(input_path, output_path):
    clear_scene()
    setup_camera_light()

    bpy.ops.import_mesh.stl(filepath=input_path)
    obj = bpy.context.selected_objects[0]

    # Set camera position based on object bounding box
    camera = bpy.context.scene.objects['Camera']
    set_camera_position(camera, obj)
    
    # Apply red material to the object
    red_material = create_red_material()
    if len(obj.data.materials) == 0:
        obj.data.materials.append(red_material)
    else:
        obj.data.materials[0] = red_material

    # Set render settings
    bpy.context.scene.render.image_settings.file_format = 'PNG'
    bpy.context.scene.render.filepath = output_path
    bpy.ops.render.render(write_still=True)
    
     # Set transparent background
    bpy.context.scene.render.film_transparent = True

    # Set render settings
    bpy.context.scene.render.image_settings.file_format = 'PNG'
    bpy.context.scene.render.filepath = output_path
    bpy.ops.render.render(write_still=True)

def render_stl_images(input_folder, output_folder):
    for root, _, files in os.walk(input_folder):
        for file in files:
            if file.lower().endswith(".stl"):
                input_path = os.path.join(root, file)
                output_file = os.path.splitext(file)[0] + ".png"
                output_path = os.path.join(output_folder, output_file)

                import_stl_and_render(input_path, output_path)

if __name__ == "__main__":

    if __name__ == "__main__":
        input_folder = "3D Prints"
        output_folder = "/outputSTL"

    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    try:
        render_stl_images(input_folder, output_folder)
    except Exception as e:
        print(f"Error: {e}")

How to use:

Save the script in a python file. You can call it for example renderSTL.py. Change the input and output folders in the script to fit your situation.

Make sure you have Blender in PATH so that you can run it by simply typing “Blender” in a command prompt. If you don’t have it in PATH, open “enviroments variables” and edit the “PATH” variable under “system”. Add the path to your Blender installation as a new path.

Open up a command line in the folder which has the proper path to your STL root folder and paste this command in:
blender –background –factory-startup –python renderSTL.py

Blender should now render images out of all your STL files in the background and save them into the output folder.

Run a simple lightweight Python server

When you are developing websites you often run into the need to test them on a server environment, because browsers block websites with certain features from running locally.

Here is a really quick way to run such a website in a server environment using Python 3 (which you probably have already installed on your machine anyways).

  1. Open the command window where your website root folder is. One easy way of doing that is simply typing “cmd” in the Windows Explorer address bar.
  2. Type this command in the command window:
python -m http.server 8000
  1. Now simply go to http://127.0.0.1:8000/ in your browser.

If you are getting errors regarding script files with that simple version, you can also try this longer code:

python
#Use to create local host
import http.server
import socketserver

PORT = 1337

Handler = http.server.SimpleHTTPRequestHandler
Handler.extensions_map.update({
      ".js": "application/javascript",
});

httpd = socketserver.TCPServer(("", PORT), Handler)
httpd.serve_forever()

And open that with:

http://127.0.0.1:1337/