Skip to content

Commit 933409f

Browse files
committed
As presented in ETRA 2014
0 parents  commit 933409f

File tree

7 files changed

+681
-0
lines changed

7 files changed

+681
-0
lines changed

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2014 Lech Swirski
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

example.py

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/usr/bin/python
2+
3+
import eyemodel
4+
5+
# ^
6+
# | .-.
7+
# | | | <- Head
8+
# | `^u^'
9+
# Y | ¦V <- Camera (As seen from above)
10+
# | ¦
11+
# | ¦
12+
# | o <- Target
13+
#
14+
# ----------> X
15+
#
16+
# +X = left
17+
# +Y = back
18+
# +Z = up
19+
20+
with eyemodel.Renderer() as r:
21+
r.eye_target = [0, -1000, 0]
22+
r.camera_position = [20, -50, -10]
23+
r.camera_target = [0, -r.eye_radius, 0]
24+
r.eye_closedness = 0.2
25+
26+
r.lights = [
27+
eyemodel.Light(
28+
location = [15, -50, -10],
29+
target = r.camera_target),
30+
eyemodel.Light(
31+
location = [25, -50, -10],
32+
target = r.camera_target)
33+
]
34+
35+
r.render("example.png", "example.m")

eyemodel/Swirski-EyeModel.blend

9.51 MB
Binary file not shown.

eyemodel/__init__.py

+222
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
#!/usr/bin/python
2+
# coding=utf-8
3+
4+
import sys
5+
import os
6+
import math
7+
import collections
8+
import subprocess
9+
import tempfile
10+
import shutil
11+
import time
12+
13+
SCRIPT_PATH = sys.arg[0] if __name__ == "__main__" else __file__
14+
SCRIPT_DIR = os.path.dirname(SCRIPT_PATH)
15+
16+
BLENDER_PATH = "C:/Program Files/Blender Foundation/Blender/blender.exe"
17+
18+
MODEL_PATH = os.path.join(SCRIPT_DIR, "Swirski-EyeModel.blend")
19+
TEXTURE_PATH = os.path.join(SCRIPT_DIR, "textures")
20+
BLENDER_SCRIPT_TEMPLATE = os.path.join(SCRIPT_DIR, "script_template.py")
21+
22+
23+
class Light(collections.namedtuple('Light', ["location", "target", "size", "strength", "view_angle"])):
24+
def __new__(cls, location, target, size=2, strength=2, view_angle=45):
25+
return super(Light, cls).__new__(cls, location, target, size, strength, view_angle)
26+
27+
28+
class Renderer():
29+
"""Renderer.
30+
31+
^
32+
| .-.
33+
| | | <- Head
34+
| `^u^'
35+
Y | ¦V <- Camera (As seen from above)
36+
| ¦
37+
| ¦
38+
| o <- Target
39+
40+
----------> X
41+
42+
+X = left
43+
+Y = back
44+
+Z = up
45+
"""
46+
47+
_renderer_active = False
48+
49+
def __enter__(self):
50+
return self
51+
52+
def __exit__(self, type, value, traceback):
53+
Renderer._renderer_active = False
54+
55+
def __init__(self):
56+
Renderer._renderer_active = True
57+
58+
self.eye_radius = 24/2
59+
self.eye_position = [0,0,0]
60+
self.eye_target = [0,-1000,0]
61+
self.eye_up = [0,0,1]
62+
self.eye_closedness = 0.0
63+
64+
self.iris = "dark"
65+
66+
self.cornea_refractive_index = 1.336
67+
68+
self.pupil_radius = 4/2
69+
70+
self.camera_position = None
71+
self.camera_target = None
72+
self.camera_up = [0,0,1]
73+
74+
self.image_size = (640, 480)
75+
self.focal_length = (640/2.0) / math.tan(45*math.pi/180 / 2)
76+
self.focus_distance = None
77+
78+
self.fstop = 2.0
79+
80+
self.lights = []
81+
82+
self.render_samples = 20
83+
84+
def render(self, path, params=None, background=True):
85+
__, ext = os.path.splitext(path)
86+
ext = ext.lower()
87+
if ext == ".png":
88+
render_format = "PNG"
89+
elif ext == ".jpg" or ext == ".jpeg":
90+
render_format = "JPEG"
91+
elif ext == ".bmp":
92+
render_format = "BMP"
93+
else:
94+
raise RuntimeError("Path extension needs to be one of png, jpg or bmp")
95+
96+
new_ireye_path = os.path.join(TEXTURE_PATH, "ireye-{}.png".format(self.iris))
97+
if not os.path.exists(new_ireye_path):
98+
raise RuntimeError("Eye texture {} does not exist. Create one in the textures folder.".format("ireye-{}.png".format(self.iris)))
99+
100+
ireye_path = os.path.join(TEXTURE_PATH, "ireye.png")
101+
if os.path.exists(ireye_path):
102+
os.remove(ireye_path)
103+
shutil.copy(new_ireye_path, ireye_path)
104+
105+
with open(BLENDER_SCRIPT_TEMPLATE) as blender_script_template_file:
106+
blender_script_template = blender_script_template_file.read()
107+
blender_script_template = blender_script_template.replace("{","{{")
108+
blender_script_template = blender_script_template.replace("}","}}")
109+
blender_script_template = blender_script_template.replace("$INPUTS","{}")
110+
111+
if self.camera_position is None:
112+
raise RuntimeError("Camera position not set")
113+
if self.camera_target is None:
114+
raise RuntimeError("Camera target not set")
115+
if len(self.lights) == 0:
116+
print("WARNING: No lights in scene")
117+
118+
if self.focus_distance is None:
119+
self.focus_distance = math.sqrt(sum((a-b)**2 for a,b in zip(self.camera_position, self.camera_target)))
120+
121+
inputs = {
122+
"input_eye_radius": self.eye_radius,
123+
"input_eye_pos": "Vector({})".format(list(self.eye_position)),
124+
"input_eye_target": "Vector({})".format(list(self.eye_target)),
125+
"input_eye_up": "Vector({})".format(list(self.eye_up)),
126+
"input_eye_closedness": self.eye_closedness,
127+
128+
"input_eye_cornea_refrative_index": self.cornea_refractive_index,
129+
130+
"input_pupil_radius": self.pupil_radius,
131+
132+
"input_cam_pos": "Vector({})".format(list(self.camera_position)),
133+
"input_cam_target": "Vector({})".format(list(self.camera_target)),
134+
"input_cam_up": "Vector({})".format(list(self.camera_up)),
135+
136+
"input_cam_image_size": list(self.image_size),
137+
"input_cam_focal_length": self.focal_length,
138+
"input_cam_focus_distance": self.focus_distance,
139+
"input_cam_fstop": self.fstop,
140+
141+
"input_lights": ["Light({})".format(
142+
",".join("{} = {}".format(k,v) for k,v in
143+
{
144+
"location": "Vector({})".format(list(l.location)),
145+
"target": "Vector({})".format(list(l.target)),
146+
"size": l.size,
147+
"strength": l.strength,
148+
"view_angle": l.view_angle
149+
}.items())) for l in self.lights],
150+
151+
"input_render_samples" : self.render_samples,
152+
"output_render_path" : "'{}'".format(path).replace("\\","/"),
153+
}
154+
if params:
155+
inputs["output_params_path"] = "'{}'".format(params).replace("\\","/")
156+
else:
157+
inputs["output_params_path"] = "None"
158+
159+
def inputVal(v):
160+
if isinstance(v,list):
161+
return '[{}]'.format(",".join(inputVal(x) for x in v))
162+
else:
163+
return str(v)
164+
165+
blender_script = blender_script_template.format("\n".join(
166+
"{} = {}".format(k,inputVal(v)) for k,v in inputs.items()))
167+
blender_script = "\n".join(" " + x for x in blender_script.split("\n"))
168+
blender_script = ("import sys\ntry:\n" + blender_script +
169+
"\nexcept:"
170+
"\n import traceback"
171+
"\n with open('blender_err.log','a') as f:"
172+
"\n f.write('\\n'.join(traceback.format_exception(*sys.exc_info())))"
173+
"\n sys.exit(1)")
174+
175+
with tempfile.NamedTemporaryFile("w+", suffix=".py", delete=False) as blender_script_file:
176+
blender_script_file.write(blender_script)
177+
178+
try:
179+
with tempfile.NamedTemporaryFile(suffix="0000", delete=False) as blender_outfile:
180+
pass
181+
182+
blender_args = [BLENDER_PATH,
183+
MODEL_PATH,
184+
"-y",
185+
"-P", blender_script_file.name,
186+
"-o", blender_outfile.name[:-4]+"####",
187+
"-F", render_format,
188+
"-x", "0",]
189+
if background:
190+
blender_args += ["-b"]
191+
if self.render_samples > 0:
192+
blender_args += ["-f", "0"]
193+
194+
with open("blender_err.log","w") as blender_err_file:
195+
blender_err_file.write("\n".join(
196+
"{: 4} | {}".format(i+1,x)
197+
for i,x in enumerate(blender_script.split("\n"))))
198+
blender_err_file.write("\n------\n")
199+
200+
# Sometimes blender fails in rendering, so retry until success
201+
while True:
202+
try:
203+
subprocess.check_call(blender_args)
204+
break
205+
except:
206+
print("Blender call failed, retrying in 1 sec")
207+
time.sleep(1)
208+
209+
if background and self.render_samples > 0:
210+
if os.path.exists(path):
211+
os.remove(path)
212+
shutil.copy(blender_outfile.name, path)
213+
print(("Moved image to {}".format(path)))
214+
215+
except subprocess.CalledProcessError:
216+
with open("blender_err.log") as blender_err_file:
217+
print(blender_err_file.read())
218+
219+
raise
220+
finally:
221+
os.remove(blender_script_file.name)
222+
#os.remove(blender_outfile.name)

0 commit comments

Comments
 (0)