diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8c3bc2d --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.* +__pycache__ +*~ +*.zip diff --git a/README.md b/README.md index 44fd46a..cd930ee 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ A Blender script to procedurally generate 3D spaceships from a random seed. Usage ----- * Install Blender: http://blender.org/download/ -* Open a *Text Editor* view -* Press *Alt + O*, or go to *Text > Open Text Block* and open `spaceship_generator.py` -* Press *Alt + P* or click *Run script* +* Download newest `add_mesh_SpaceshipGenerator.zip` from releases +* Under File > User Preferences... > Add-ons > Install From File... open the downloaded ZIP file +* Under File > User Preferences... > Add-ons enable this script (search for "spaceship") +* Add a spaceship in the 3D View under Add > Mesh > Spaceship How it works ------------ diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..4f68304 --- /dev/null +++ b/__init__.py @@ -0,0 +1,68 @@ +bl_info = { + "name": "Spaceship Generator", + "author": "Michael Davies", + "version": (1, 0, 0), + "blender": (2, 76, 0), + "location": "View3D > Add > Mesh", + "description": "Procedurally generate 3D spaceships from a random seed.", + "wiki_url": "https://github.com/a1studmuffin/SpaceshipGenerator/blob/master/README.md", + "tracker_url": "https://github.com/a1studmuffin/SpaceshipGenerator/issues", + "category": "Add Mesh" +} + +if "bpy" in locals(): + # reload logic (magic) + import importlib + importlib.reload(spaceship_generator) +else: + from add_mesh_SpaceshipGenerator import spaceship_generator + +import bpy +from bpy.props import StringProperty, BoolProperty, IntProperty +from bpy.types import Operator + +class GenerateSpaceship(Operator): + """Procedurally generate 3D spaceships from a random seed.""" + bl_idname = "mesh.generate_spaceship" + bl_label = "Spaceship" + bl_options = {'REGISTER', 'UNDO'} + + random_seed = StringProperty(default='', name='Seed') + num_hull_segments_min = IntProperty (default=3, min=0, soft_max=16, name='Min. Hull Segments') + num_hull_segments_max = IntProperty (default=6, min=0, soft_max=16, name='Max. Hull Segments') + create_asymmetry_segments = BoolProperty(default=True, name='Create Asymmetry Segments') + num_asymmetry_segments_min = IntProperty (default=1, min=1, soft_max=16, name='Min. Asymmetry Segments') + num_asymmetry_segments_max = IntProperty (default=5, min=1, soft_max=16, name='Max. Asymmetry Segments') + create_face_detail = BoolProperty(default=True, name='Create Face Detail') + allow_horizontal_symmetry = BoolProperty(default=True, name='Allow Horizontal Symmetry') + allow_vertical_symmetry = BoolProperty(default=False, name='Allow Vertical Symmetry') + apply_bevel_modifier = BoolProperty(default=True, name='Apply Bevel Modifier') + assign_materials = BoolProperty(default=True, name='Assign Materials') + + def execute(self, context): + spaceship_generator.generate_spaceship( + self.random_seed, + self.num_asymmetry_segments_min, + self.num_asymmetry_segments_max, + self.create_asymmetry_segments, + self.num_asymmetry_segments_min, + self.num_asymmetry_segments_max, + self.create_face_detail, + self.allow_horizontal_symmetry, + self.allow_vertical_symmetry, + self.apply_bevel_modifier) + return {'FINISHED'} + +def menu_func(self, context): + self.layout.operator(GenerateSpaceship.bl_idname, text="Spaceship") + +def register(): + bpy.utils.register_module(__name__) + bpy.types.INFO_MT_mesh_add.append(menu_func) + +def unregister(): + bpy.utils.unregister_module(__name__) + bpy.types.INFO_MT_mesh_add.remove(menu_func) + +if __name__ == "__main__": + register() diff --git a/build.py b/build.py new file mode 100755 index 0000000..e3de812 --- /dev/null +++ b/build.py @@ -0,0 +1,17 @@ +#!/usr/bin/env python + +from os.path import abspath, dirname, join as pjoin +import zipfile + +SRC_DIR = dirname(abspath(__file__)) + +with zipfile.ZipFile('add_mesh_SpaceshipGenerator.zip', 'w', zipfile.ZIP_DEFLATED) as arch: + for filename in [ + '__init__.py', + 'spaceship_generator.py', + 'textures/hull_normal.png', + 'textures/hull_lights_emit.png', + 'textures/hull_lights_diffuse.png']: + arch.write(pjoin(SRC_DIR, filename), 'add_mesh_SpaceshipGenerator/'+filename) + +print('created file: add_mesh_SpaceshipGenerator.zip') diff --git a/spaceship_generator.py b/spaceship_generator.py index 0b852af..4c6c49b 100644 --- a/spaceship_generator.py +++ b/spaceship_generator.py @@ -10,6 +10,7 @@ import sys import os +import os.path import bpy import bmesh import datetime @@ -19,6 +20,11 @@ from enum import IntEnum from colorsys import hls_to_rgb +DIR = os.path.dirname(os.path.abspath(__file__)) + +def resource_path(*path_components): + return os.path.join(DIR, *path_components) + # Deletes all existing spaceships and unused materials from the scene def reset_scene(): for item in bpy.data.objects: @@ -395,35 +401,25 @@ class Material(IntEnum): # Returns the texture. img_cache = {} def create_texture(name, tex_type, filename, use_alpha=True): - global img_cache - img = None if filename in img_cache: # Image has been cached already, so just use that. - img = img_cache[filename] + img = img_cache[(filename, use_alpha)] else: # We haven't cached this asset yet, so load it from disk. - - # Figure out the script path depending on our context (command-line or in-editor) - script_path = bpy.context.space_data.text.filepath if bpy.context.space_data else __file__ - - # Get the folder the script lives in. If it lives in a .blend file, strip that off too. - script_folder = os.path.split(os.path.realpath(script_path))[0] - if script_folder.endswith('.blend'): - script_folder = os.path.split(script_folder)[0] - - filepath = os.path.join(script_folder, filename) try: - img = bpy.data.images.load(filepath) + img = bpy.data.images.load(filename) except: - raise NameError("Cannot load image %s" % filepath) + raise IOError("Cannot load image: %s" % filename) + + img.use_alpha = use_alpha + img.pack() # Cache the asset - img_cache[filename] = img + img_cache[(filename, use_alpha)] = img # Create and return a new texture using img tex = bpy.data.textures.new(name, tex_type) tex.image = img - tex.image.use_alpha = use_alpha return tex # Adds a hull normal map texture slot to a material. @@ -455,7 +451,7 @@ def create_materials(): # Load up the hull normal map hull_normal_colortex = create_texture( - 'ColorTex', 'IMAGE', 'textures\\hull_normal.png') + 'ColorTex', 'IMAGE', resource_path('textures', 'hull_normal.png')) hull_normal_colortex.use_normal_map = True # Build the hull texture @@ -469,7 +465,7 @@ def create_materials(): # Add a diffuse layer that sets the window color mtex = mat.texture_slots.add() mtex.texture = create_texture( - 'ColorTex', 'IMAGE', 'textures\\hull_lights_diffuse.png') + 'ColorTex', 'IMAGE', resource_path('textures', 'hull_lights_diffuse.png')) mtex.texture_coords = 'GLOBAL' mtex.mapping = 'CUBE' mtex.blend_type = 'ADD' @@ -480,7 +476,7 @@ def create_materials(): # Add an emissive layer that lights up the windows mtex = mat.texture_slots.add() mtex.texture = create_texture( - 'ColorTex', 'IMAGE', 'textures\\hull_lights_emit.png', False) + 'ColorTex', 'IMAGE', resource_path('textures', 'hull_lights_emit.png'), False) mtex.texture_coords = 'GLOBAL' mtex.mapping = 'CUBE' mtex.use_map_emit = True @@ -805,7 +801,7 @@ def generate_spaceship(random_seed='', # Render the scene to disk script_path = bpy.context.space_data.text.filepath if bpy.context.space_data else __file__ folder = output_path if output_path else os.path.split(os.path.realpath(script_path))[0] - filename = 'renders\\' + timestamp + '\\' + timestamp + '_' + str(frame).zfill(5) + '.png' + filename = os.path.join('renders', timestamp, timestamp + '_' + str(frame).zfill(5) + '.png') bpy.data.scenes['Scene'].render.filepath = os.path.join(folder, filename) print('Rendering frame ' + str(frame) + '...') bpy.ops.render.render(write_still=True)