File size: 5,473 Bytes
7e51d94
 
 
 
 
 
 
 
 
cad0c51
7e51d94
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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)


@contextmanager
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()