1 # SPDX-License-Identifier: GPL-2.0-or-later
3 """Get some Blender particle objects translated to POV."""
10 def pixel_relative_guess(ob
):
11 """Convert some object x dimension to a rough pixel relative order of magnitude"""
12 from bpy_extras
import object_utils
14 scene
= bpy
.context
.scene
17 # Get rendered image resolution
18 output_x_res
= render
.resolution_x
19 focal_length
= cam
.data
.lens
20 # Get object bounding box size
21 object_location
= ob
.location
22 object_dimension_x
= ob
.dimensions
[0]
23 world_to_camera
= object_utils
.world_to_camera_view(scene
, cam
, object_location
)
25 apparent_size
= (object_dimension_x
* focal_length
) / world_to_camera
[2]
26 sensor_width
= cam
.data
.sensor_width
27 pixel_pitch_x
= sensor_width
/ output_x_res
28 return apparent_size
/ pixel_pitch_x
31 def export_hair(file, ob
, mod
, p_sys
, global_matrix
):
32 """Get Blender path particles (hair strands) objects translated to POV sphere_sweep unions."""
33 # tstart = time.time()
34 from .render
import write_matrix
37 depsgraph
= bpy
.context
.evaluated_depsgraph_get()
38 p_sys_settings
= p_sys
.settings
.evaluated_get(depsgraph
)
39 if ob
.material_slots
[p_sys_settings
.material
- 1].material
and ob
.active_material
is not None:
40 pmaterial
= ob
.material_slots
[p_sys_settings
.material
- 1].material
41 # XXX Todo: replace by pov_(Particles?)_texture_slot
42 for th
in pmaterial
.pov_texture_slots
:
43 povtex
= th
.texture
# slot.name
44 tex
= bpy
.data
.textures
[povtex
]
49 and ((tex
.type == "IMAGE" and tex
.image
) or tex
.type != "IMAGE")
50 and th
.use_map_color_diffuse
53 if pmaterial
.strand
.use_blender_units
:
54 strand_start
= pmaterial
.strand
.root_size
55 strand_end
= pmaterial
.strand
.tip_size
58 # inexact pixel size, just to make radius relative to screen and object size.
59 pixel_fac
= pixel_relative_guess(ob
)
60 except ZeroDivisionError:
61 # Fallback to hardwired constant value
63 print("no pixel size found for stand radius, falling back to %i" % pixel_fac
)
65 strand_start
= pmaterial
.strand
.root_size
/ pixel_fac
66 strand_end
= pmaterial
.strand
.tip_size
/ pixel_fac
67 strand_shape
= pmaterial
.strand
.shape
69 pmaterial
= "default" # No material assigned in blender, use default one
73 # Set the number of particles to render count rather than 3d view display
74 # p_sys.set_resolution(scene, ob, 'RENDER') # DEPRECATED
75 # When you render, the entire dependency graph will be
76 # evaluated at render resolution, including the particles.
77 # In the viewport it will be at viewport resolution.
78 # So there is no need for render engines to use this function anymore,
80 steps
= p_sys_settings
.display_step
81 steps
= 2**steps
# or + 1 # Formerly : len(particle.hair_keys)
83 total_number_of_strands
= p_sys_settings
.count
* p_sys_settings
.rendered_child_count
85 file.write("#declare HairArray = array[%i] {\n" % total_number_of_strands
)
86 for pindex
in range(total_number_of_strands
):
88 # if particle.is_exist and particle.is_visible:
90 # controlPointCounter = 0
91 # Each hair is represented as a separate sphere_sweep in POV-Ray.
93 file.write("sphere_sweep{")
94 if p_sys_settings
.use_hair_bspline
:
95 file.write("b_spline ")
98 ) # +2 because the first point needs tripling to be more than a handle in POV
100 file.write("linear_spline ")
101 file.write("%i,\n" % steps
)
102 # changing world coordinates to object local coordinates by
103 # multiplying with inverted matrix
104 init_coord
= ob
.matrix_world
.inverted() @ (p_sys
.co_hair(ob
, particle_no
=pindex
, step
=0))
105 init_coord
= (init_coord
[0], init_coord
[1], init_coord
[2])
107 ob
.material_slots
[p_sys_settings
.material
- 1].material
108 and ob
.active_material
is not None
110 pmaterial
= ob
.material_slots
[p_sys_settings
.material
- 1].material
111 for th
in pmaterial
.pov_texture_slots
:
112 povtex
= th
.texture
# slot.name
113 tex
= bpy
.data
.textures
[povtex
]
114 if tex
and th
.use
and th
.use_map_color_diffuse
:
115 # treat POV textures as bitmaps
119 and th
.texture_coords
== "UV"
120 and ob
.data
.uv_textures
is not None
123 # tex.pov.tex_pattern_type != 'emulator'
124 # and th.texture_coords == 'UV'
125 # and ob.data.uv_textures is not None
128 image_width
= image
.size
[0]
129 image_height
= image
.size
[1]
130 image_pixels
= image
.pixels
[:]
131 uv_co
= p_sys
.uv_on_emitter(mod
, p_sys
.particles
[pindex
], pindex
, 0)
132 x_co
= round(uv_co
[0] * (image_width
- 1))
133 y_co
= round(uv_co
[1] * (image_height
- 1))
134 pixelnumber
= (image_width
* y_co
) + x_co
135 r
= image_pixels
[pixelnumber
* 4]
136 g
= image_pixels
[pixelnumber
* 4 + 1]
137 b
= image_pixels
[pixelnumber
* 4 + 2]
138 a
= image_pixels
[pixelnumber
* 4 + 3]
139 init_color
= (r
, g
, b
, a
)
141 # only overwrite variable for each competing texture for now
142 init_color
= tex
.evaluate(init_coord
)
143 for step
in range(steps
):
144 coord
= ob
.matrix_world
.inverted() @ (p_sys
.co_hair(ob
, particle_no
=pindex
, step
=step
))
145 # for controlPoint in particle.hair_keys:
146 if p_sys_settings
.clump_factor
:
147 hair_strand_diameter
= p_sys_settings
.clump_factor
/ 200.0 * random
.uniform(0.5, 1)
149 hair_strand_diameter
= strand_start
151 # still initialize variable
152 hair_strand_diameter
= strand_start
153 if strand_shape
== 0.0:
155 elif strand_shape
< 0:
156 fac
= pow(step
, (1.0 + strand_shape
))
158 fac
= pow(step
, (1.0 / (1.0 - strand_shape
)))
159 hair_strand_diameter
+= (
160 fac
* (strand_end
- strand_start
) / (p_sys_settings
.display_step
+ 1)
161 ) # XXX +1 or -1 or nothing ?
162 abs_hair_strand_diameter
= abs(hair_strand_diameter
)
163 if step
== 0 and p_sys_settings
.use_hair_bspline
:
164 # Write three times the first point to compensate pov Bezier handling
166 "<%.6g,%.6g,%.6g>,%.7g,\n"
167 % (coord
[0], coord
[1], coord
[2], abs_hair_strand_diameter
)
170 "<%.6g,%.6g,%.6g>,%.7g,\n"
171 % (coord
[0], coord
[1], coord
[2], abs_hair_strand_diameter
)
173 # Useless because particle location is the tip, not the root:
175 # '<%.6g,%.6g,%.6g>,%.7g'
177 # particle.location[0],
178 # particle.location[1],
179 # particle.location[2],
180 # abs_hair_strand_diameter
184 # controlPointCounter += 1
185 # total_number_of_strands += len(p_sys.particles)# len(particle.hair_keys)
187 # Each control point is written out, along with the radius of the
188 # hair at that point.
190 "<%.6g,%.6g,%.6g>,%.7g" % (coord
[0], coord
[1], coord
[2], abs_hair_strand_diameter
)
193 # All coordinates except the last need a following comma.
195 if step
== steps
- 1:
197 # Write pigment and alpha (between Pov and Blender,
198 # alpha 0 and 1 are reversed)
200 "\npigment{ color srgbf < %.3g, %.3g, %.3g, %.3g> }\n"
201 % (init_color
[0], init_color
[1], init_color
[2], 1.0 - init_color
[3])
203 # End the sphere_sweep declaration for this hair
208 # All but the final sphere_sweep (each array element) needs a terminating comma.
209 if pindex
!= total_number_of_strands
:
214 # End the array declaration.
219 if not textured_hair
:
220 # Pick up the hair material diffuse color and create a default POV-Ray hair texture.
222 file.write("#ifndef (HairTexture)\n")
223 file.write(" #declare HairTexture = texture {\n")
225 " pigment {srgbt <%s,%s,%s,%s>}\n"
227 pmaterial
.diffuse_color
[0],
228 pmaterial
.diffuse_color
[1],
229 pmaterial
.diffuse_color
[2],
230 (pmaterial
.strand
.width_fade
+ 0.05),
237 # Dynamically create a union of the hairstrands (or a subset of them).
238 # By default use every hairstrand, commented line is for hand tweaking test renders.
239 file.write("//Increasing HairStep divides the amount of hair for test renders.\n")
240 file.write("#ifndef(HairStep) #declare HairStep = 1; #end\n")
241 file.write("union{\n")
242 file.write(" #local I = 0;\n")
243 file.write(" #while (I < %i)\n" % total_number_of_strands
)
244 file.write(" object {HairArray[I]")
248 file.write(" texture{HairTexture}\n")
249 # Translucency of the hair:
250 file.write(" hollow\n")
251 file.write(" double_illuminate\n")
252 file.write(" interior {\n")
253 file.write(" ior 1.45\n")
254 file.write(" media {\n")
255 file.write(" scattering { 1, 10*<0.73, 0.35, 0.15> /*extinction 0*/ }\n")
256 file.write(" absorption 10/<0.83, 0.75, 0.15>\n")
257 file.write(" samples 1\n")
258 file.write(" method 2\n")
259 file.write(" density {cylindrical\n")
260 file.write(" color_map {\n")
261 file.write(" [0.0 rgb <0.83, 0.45, 0.35>]\n")
262 file.write(" [0.5 rgb <0.8, 0.8, 0.4>]\n")
263 file.write(" [1.0 rgb <1,1,1>]\n")
270 file.write(" #local I = I + HairStep;\n")
271 file.write(" #end\n")
273 write_matrix(file, global_matrix
@ ob
.matrix_world
)
276 print("Totals hairstrands written: %i" % total_number_of_strands
)
277 print("Number of tufts (particle systems): %i" % len(ob
.particle_systems
))