Spaces:
Paused
Paused
| import argparse | |
| import math | |
| import os | |
| import sys | |
| from contextlib import contextmanager | |
| from pathlib import Path | |
| import bpy | |
| # Add path to ImportLDraw module | |
| root_dir = Path(__file__).resolve().parents[2] | |
| sys.path.append(str(root_dir)) | |
| import ImportLDraw | |
| from ImportLDraw.loadldraw.loadldraw import Options, Configure, loadFromFile, FileSystem | |
| def render_bricks( | |
| in_file: str, | |
| out_file: str, | |
| reposition_camera: bool = True, | |
| square_image: bool = True, | |
| instructions_look: bool = False, | |
| fov: float = 45, | |
| img_resolution: int = 512, | |
| ) -> None: | |
| in_file = os.path.abspath(in_file) | |
| out_file = os.path.abspath(out_file) | |
| # Set the path to the ImportLDraw and LDraw libraries | |
| plugin_path = Path(ImportLDraw.__file__).parent | |
| ldraw_lib_path = os.environ.get('LDRAW_LIBRARY_PATH') | |
| if not ldraw_lib_path or not os.path.exists(ldraw_lib_path): | |
| # Default path to LDraw library is home directory | |
| ldraw_lib_path = Path.home() / 'ldraw' | |
| ldraw_lib_path = os.path.abspath(ldraw_lib_path) | |
| # Initialize bpy | |
| with stdout_redirected(os.devnull): | |
| bpy.data.scenes[0].render.engine = 'CYCLES' | |
| # Set the device_type | |
| if sys.platform == 'darwin': # macOS | |
| bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'METAL' | |
| else: | |
| bpy.context.preferences.addons['cycles'].preferences.compute_device_type = 'CUDA' | |
| # Set the device and feature set | |
| bpy.context.scene.cycles.device = 'GPU' | |
| bpy.context.scene.cycles.samples = 512 | |
| # get_devices() to let Blender detects GPU device | |
| bpy.context.preferences.addons['cycles'].preferences.get_devices() | |
| print(bpy.context.preferences.addons['cycles'].preferences.compute_device_type) | |
| for d in bpy.context.preferences.addons['cycles'].preferences.devices: | |
| d['use'] = 0 | |
| if d['name'].startswith('NVIDIA') or d['name'].startswith('Apple'): | |
| d['use'] = 1 | |
| print(d['name'], d['use']) | |
| # Remove all objects but keep the camera | |
| bpy.ops.object.select_all(action='DESELECT') | |
| bpy.ops.object.select_by_type(type='MESH') | |
| bpy.ops.object.delete() | |
| Options.ldrawDirectory = ldraw_lib_path | |
| Options.instructionsLook = instructions_look | |
| Options.useLogoStuds = True | |
| Options.useUnofficialParts = True | |
| Options.gaps = True | |
| Options.studLogoDirectory = os.path.join(plugin_path, 'studs') | |
| Options.LSynthDirectory = os.path.join(plugin_path, 'lsynth') | |
| Options.verbose = 0 | |
| Options.overwriteExistingMaterials = True | |
| Options.overwriteExistingMeshes = True | |
| Options.scale = 0.01 | |
| Options.createInstances = True # Multiple bricks share geometry (recommended) | |
| Options.removeDoubles = True # Remove duplicate vertices (recommended) | |
| Options.positionObjectOnGroundAtOrigin = True # Centre the object at the origin, sitting on the z=0 plane | |
| Options.flattenHierarchy = False # All parts are under the root object - no sub-models | |
| Options.edgeSplit = True # Add the edge split modifier | |
| Options.addBevelModifier = True # Adds a bevel modifier to each part (for rounded edges) | |
| Options.bevelWidth = 0.5 # Bevel width | |
| Options.addEnvironmentTexture = True | |
| Options.scriptDirectory = os.path.join(plugin_path, 'loadldraw') | |
| Options.addWorldEnvironmentTexture = True # Add an environment texture | |
| Options.addGroundPlane = True # Add a ground plane | |
| Options.setRenderSettings = True # Set render percentage, denoising | |
| Options.removeDefaultObjects = True # Remove cube and lamp | |
| Options.positionCamera = reposition_camera # Reposition the camera to a good place to see the scene | |
| Options.cameraBorderPercent = 0.05 # Add a percentage border around the repositioned camera | |
| Configure() | |
| loadFromFile(None, FileSystem.locate(in_file)) | |
| if square_image: | |
| bpy.context.scene.render.resolution_x = img_resolution | |
| bpy.context.scene.render.resolution_y = img_resolution | |
| bpy.context.scene.camera.data.angle = math.radians(fov) | |
| bpy.context.scene.render.image_settings.file_format = 'PNG' | |
| bpy.context.scene.render.filepath = out_file | |
| # Redirect stdout to suppress the verbose render output | |
| with stdout_redirected(os.devnull): | |
| bpy.ops.render.render(write_still=True) | |
| def stdout_redirected(to: str): | |
| """ | |
| Redirects stdout to a file. | |
| """ | |
| fd = sys.stdout.fileno() | |
| def _redirect_stdout(to_file): | |
| sys.stdout.close() # + implicit flush() | |
| os.dup2(to_file.fileno(), fd) # fd writes to 'to' file | |
| sys.stdout = os.fdopen(fd, 'w') # Python writes to fd | |
| with os.fdopen(os.dup(fd), 'w') as old_stdout: | |
| with open(to, 'w') as file: | |
| _redirect_stdout(file) | |
| try: | |
| yield # allow code to be run with the redirected stdout | |
| finally: | |
| _redirect_stdout(old_stdout) # restore stdout. | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument('--in_file', type=str, help='Path to LDR file') | |
| parser.add_argument('--out_file', type=str, help='Path to output image file') | |
| args = parser.parse_args() | |
| # Get the absolute path of the input file | |
| render_bricks(args.in_file, args.out_file, square_image=True, instructions_look=False) | |
| print(f'Rendered image to {args.out_file}') | |
| if __name__ == '__main__': | |
| main() | |