Newer
Older
# SPDX-License-Identifier: GPL-2.0-or-later
# <pep8 compliant>
"""Get some Blender particle objects translated to POV."""
import bpy
def pixel_relative_guess(ob):
"""Convert some object x dimension to a rough pixel relative order of magnitude"""
from bpy_extras import object_utils
scene = bpy.context.scene
cam = scene.camera
render = scene.render
# Get rendered image resolution
output_x_res = render.resolution_x
focal_length = cam.data.lens
# Get object bounding box size
object_location = ob.location
object_dimension_x = ob.dimensions[0]
world_to_camera = object_utils.world_to_camera_view(scene, cam, object_location)
apparent_size = (object_dimension_x * focal_length) / world_to_camera[2]
sensor_width = cam.data.sensor_width
pixel_pitch_x = sensor_width / output_x_res
return apparent_size / pixel_pitch_x
def export_hair(file, ob, mod, p_sys, global_matrix, write_matrix):
"""Get Blender path particles (hair strands) objects translated to POV sphere_sweep unions."""
# tstart = time.time()
textured_hair = 0
if ob.material_slots[p_sys.settings.material - 1].material and ob.active_material is not None:
pmaterial = ob.material_slots[p_sys.settings.material - 1].material
# XXX Todo: replace by pov_(Particles?)_texture_slot
for th in pmaterial.pov_texture_slots:
povtex = th.texture # slot.name
tex = bpy.data.textures[povtex]
if (
th
and th.use
and (
(tex.type == 'IMAGE' and tex.image) or tex.type != 'IMAGE'
)
and th.use_map_color_diffuse
):
textured_hair = 1
if pmaterial.strand.use_blender_units:
strand_start = pmaterial.strand.root_size
strand_end = pmaterial.strand.tip_size
else:
try:
# inexact pixel size, just to make radius relative to screen and object size.
pixel_fac = pixel_relative_guess(ob)
except ZeroDivisionError:
# Fallback to hardwired constant value
pixel_fac = 4500
print("no pixel size found for stand radius, falling back to %i" % pixel_fac)
strand_start = pmaterial.strand.root_size / pixel_fac
strand_end = pmaterial.strand.tip_size / pixel_fac
strand_shape = pmaterial.strand.shape
else:
pmaterial = "default" # No material assigned in blender, use default one
strand_start = 0.01
strand_end = 0.01
strand_shape = 0.0
# Set the number of particles to render count rather than 3d view display
# p_sys.set_resolution(scene, ob, 'RENDER') # DEPRECATED
# When you render, the entire dependency graph will be
# evaluated at render resolution, including the particles.
# In the viewport it will be at viewport resolution.
# So there is no need fo render engines to use this function anymore,
# it's automatic now.
steps = p_sys.settings.display_step
steps = 2 ** steps # or + 1 # Formerly : len(particle.hair_keys)
total_number_of_strands = p_sys.settings.count + p_sys.settings.rendered_child_count
# hairCounter = 0
file.write('#declare HairArray = array[%i] {\n' % total_number_of_strands)
for pindex in range(total_number_of_strands):
# if particle.is_exist and particle.is_visible:
# hairCounter += 1
# controlPointCounter = 0
# Each hair is represented as a separate sphere_sweep in POV-Ray.
file.write('sphere_sweep{')
if p_sys.settings.use_hair_bspline:
file.write('b_spline ')
file.write(
'%i,\n' % (steps + 2)
) # +2 because the first point needs tripling to be more than a handle in POV
else:
file.write('linear_spline ')
Maurice Raybaud
committed
file.write('%i,\n' % steps)
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
# changing world coordinates to object local coordinates by
# multiplying with inverted matrix
init_coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=0))
if (
ob.material_slots[p_sys.settings.material - 1].material
and ob.active_material is not None
):
pmaterial = ob.material_slots[p_sys.settings.material - 1].material
for th in pmaterial.pov_texture_slots:
if th and th.use and th.use_map_color_diffuse:
povtex = th.texture # slot.name
tex = bpy.data.textures[povtex]
# treat POV textures as bitmaps
if (
tex.type == 'IMAGE'
and tex.image
and th.texture_coords == 'UV'
and ob.data.uv_textures is not None
):
# or (
# tex.pov.tex_pattern_type != 'emulator'
# and th.texture_coords == 'UV'
# and ob.data.uv_textures is not None
# ):
image = tex.image
image_width = image.size[0]
image_height = image.size[1]
image_pixels = image.pixels[:]
uv_co = p_sys.uv_on_emitter(mod, p_sys.particles[pindex], pindex, 0)
x_co = round(uv_co[0] * (image_width - 1))
y_co = round(uv_co[1] * (image_height - 1))
pixelnumber = (image_width * y_co) + x_co
r = image_pixels[pixelnumber * 4]
g = image_pixels[pixelnumber * 4 + 1]
b = image_pixels[pixelnumber * 4 + 2]
a = image_pixels[pixelnumber * 4 + 3]
init_color = (r, g, b, a)
else:
# only overwrite variable for each competing texture for now
init_color = tex.evaluate((init_coord[0], init_coord[1], init_coord[2]))
coord = ob.matrix_world.inverted() @ (p_sys.co_hair(ob, particle_no=pindex, step=step))
# for controlPoint in particle.hair_keys:
if p_sys.settings.clump_factor != 0:
hair_strand_diameter = p_sys.settings.clump_factor / 200.0 * random.uniform(0.5, 1)
elif step == 0:
hair_strand_diameter = strand_start
else:
Maurice Raybaud
committed
# still initialize variable
hair_strand_diameter = strand_start
if strand_shape != 0.0:
if strand_shape < 0.0:
fac = pow(step, (1.0 + strand_shape))
else:
fac = pow(step, (1.0 / (1.0 - strand_shape)))
else:
fac = step
hair_strand_diameter += fac * (strand_end - strand_start) / (
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
if step == 0 and p_sys.settings.use_hair_bspline:
# Write three times the first point to compensate pov Bezier handling
file.write(
'<%.6g,%.6g,%.6g>,%.7g,\n'
% (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
)
file.write(
'<%.6g,%.6g,%.6g>,%.7g,\n'
% (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
)
# Useless because particle location is the tip, not the root:
# file.write(
# '<%.6g,%.6g,%.6g>,%.7g'
# % (
# particle.location[0],
# particle.location[1],
# particle.location[2],
# abs(hair_strand_diameter)
# )
# )
# file.write(',\n')
# controlPointCounter += 1
# total_number_of_strands += len(p_sys.particles)# len(particle.hair_keys)
# Each control point is written out, along with the radius of the
# hair at that point.
file.write(
'<%.6g,%.6g,%.6g>,%.7g' % (coord[0], coord[1], coord[2], abs(hair_strand_diameter))
)
# All coordinates except the last need a following comma.
if textured_hair:
# Write pigment and alpha (between Pov and Blender,
# alpha 0 and 1 are reversed)
file.write(
'\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n'
% (init_color[0], init_color[1], init_color[2], 1.0 - init_color[3])
)
# End the sphere_sweep declaration for this hair
file.write('}\n')
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# All but the final sphere_sweep (each array element) needs a terminating comma.
if pindex != total_number_of_strands:
file.write(',\n')
else:
file.write('\n')
# End the array declaration.
file.write('}\n')
file.write('\n')
if not textured_hair:
# Pick up the hair material diffuse color and create a default POV-Ray hair texture.
file.write('#ifndef (HairTexture)\n')
file.write(' #declare HairTexture = texture {\n')
file.write(
' pigment {srgbt <%s,%s,%s,%s>}\n'
% (
pmaterial.diffuse_color[0],
pmaterial.diffuse_color[1],
pmaterial.diffuse_color[2],
(pmaterial.strand.width_fade + 0.05),
)
)
file.write(' }\n')
file.write('#end\n')
file.write('\n')
# Dynamically create a union of the hairstrands (or a subset of them).
# By default use every hairstrand, commented line is for hand tweaking test renders.
file.write('//Increasing HairStep divides the amount of hair for test renders.\n')
file.write('#ifndef(HairStep) #declare HairStep = 1; #end\n')
file.write('union{\n')
file.write(' #local I = 0;\n')
file.write(' #while (I < %i)\n' % total_number_of_strands)
file.write(' object {HairArray[I]')
else:
file.write(' texture{HairTexture}\n')
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
# Translucency of the hair:
file.write(' hollow\n')
file.write(' double_illuminate\n')
file.write(' interior {\n')
file.write(' ior 1.45\n')
file.write(' media {\n')
file.write(' scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n')
file.write(' absorption 10/<0.83, 0.75, 0.15>\n')
file.write(' samples 1\n')
file.write(' method 2\n')
file.write(' density {cylindrical\n')
file.write(' color_map {\n')
file.write(' [0.0 rgb <0.83, 0.45, 0.35>]\n')
file.write(' [0.5 rgb <0.8, 0.8, 0.4>]\n')
file.write(' [1.0 rgb <1,1,1>]\n')
file.write(' }\n')
file.write(' }\n')
file.write(' }\n')
file.write(' }\n')
file.write(' }\n')
file.write(' #local I = I + HairStep;\n')
file.write(' #end\n')
write_matrix(global_matrix @ ob.matrix_world)
file.write('}')
print("Totals hairstrands written: %i" % total_number_of_strands)
print("Number of tufts (particle systems)", len(ob.particle_systems))
# Set back the displayed number of particles to preview count
# p_sys.set_resolution(scene, ob, 'PREVIEW') #DEPRECATED
# When you render, the entire dependency graph will be
# evaluated at render resolution, including the particles.
# In the viewport it will be at viewport resolution.
# So there is no need fo render engines to use this function anymore,
# it's automatic now.