Fix add-ons with Python 3.12 by replacing "imp" with "importlib"
[blender-addons.git] / render_povray / scripting.py
blobbb00ac661010a8d92e5842660944288442e8c061
1 # SPDX-FileCopyrightText: 2021-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 """Support POV Scene Description Language snippets or full includes: import,
7 load, create or edit"""
9 import bpy
10 from bpy.props import StringProperty, BoolProperty, CollectionProperty
11 from bpy_extras.io_utils import ImportHelper
12 from bpy.utils import register_class, unregister_class
14 from mathutils import Vector
15 from math import pi, sqrt
18 def export_custom_code(file):
19 """write all POV user defined custom code to exported file"""
20 # Write CurrentAnimation Frame for use in Custom POV Code
21 file.write("#declare CURFRAMENUM = %d;\n" % bpy.context.scene.frame_current)
22 # Change path and uncomment to add an animated include file by hand:
23 file.write('//#include "/home/user/directory/animation_include_file.inc"\n')
24 for txt in bpy.data.texts:
25 if txt.pov.custom_code == "both":
26 # Why are the newlines needed?
27 file.write("\n")
28 file.write(txt.as_string())
29 file.write("\n")
32 # ----------------------------------- IMPORT
35 class SCENE_OT_POV_Import(bpy.types.Operator, ImportHelper):
36 """Load Povray files"""
38 bl_idname = "import_scene.pov"
39 bl_label = "POV-Ray files (.pov/.inc)"
40 bl_options = {"PRESET", "UNDO"}
41 COMPAT_ENGINES = {"POVRAY_RENDER"}
43 # -----------
44 # File props.
45 files: CollectionProperty(
46 type=bpy.types.OperatorFileListElement, options={"HIDDEN", "SKIP_SAVE"}
48 directory: StringProperty(maxlen=1024, subtype="FILE_PATH", options={"HIDDEN", "SKIP_SAVE"})
50 filename_ext = {".pov", ".inc"}
51 filter_glob: StringProperty(default="*.pov;*.inc", options={"HIDDEN"})
53 import_at_cur: BoolProperty(
54 name="Import at Cursor Location", description="Ignore Object Matrix", default=False
57 def execute(self, context):
58 from mathutils import Matrix
60 verts = []
61 faces = []
62 materials = []
63 blend_mats = [] # XXX
64 pov_mats = [] # XXX
65 colors = []
66 mat_names = []
67 lenverts = None
68 lenfaces = None
69 suffix = -1
70 name = "Mesh2_%s" % suffix
71 name_search = False
72 verts_search = False
73 faces_search = False
74 plane_search = False
75 box_search = False
76 cylinder_search = False
77 sphere_search = False
78 cone_search = False
79 tex_search = False # XXX
80 cache = []
81 matrixes = {}
82 write_matrix = False
83 index = None
84 value = None
85 # file_pov = bpy.path.abspath(self.filepath) # was used for single files
87 def mat_search(cache):
88 r = g = b = 0.5
89 f = t = 0
90 color = None
91 for item, value in enumerate(cache):
92 # if value == 'texture': # add more later
93 if value == "pigment":
94 # Todo: create function for all color models.
95 # instead of current pass statements
96 # distinguish srgb from rgb into blend option
97 if cache[item + 2] in {"rgb", "srgb"}:
98 pass
99 elif cache[item + 2] in {"rgbf", "srgbf"}:
100 pass
101 elif cache[item + 2] in {"rgbt", "srgbt"}:
102 try:
103 r, g, b, t = (
104 float(cache[item + 3]),
105 float(cache[item + 4]),
106 float(cache[item + 5]),
107 float(cache[item + 6]),
109 except BaseException as e:
110 print(e.__doc__)
111 print("An exception occurred: {}".format(e))
112 r = g = b = t = float(cache[item + 2])
113 color = (r, g, b, t)
115 elif cache[item + 2] in {"rgbft", "srgbft"}:
116 pass
118 else:
119 pass
121 if colors == [] or color not in colors:
122 colors.append(color)
123 name = ob.name + "_mat"
124 mat_names.append(name)
125 mat = bpy.data.materials.new(name)
126 mat.diffuse_color = (r, g, b)
127 mat.pov.alpha = 1 - t
128 if mat.pov.alpha != 1:
129 mat.pov.use_transparency = True
130 ob.data.materials.append(mat)
132 else:
133 for i, value in enumerate(colors):
134 if color == value:
135 ob.data.materials.append(bpy.data.materials[mat_names[i]])
137 for file in self.files:
138 print("Importing file: " + file.name)
139 file_pov = self.directory + file.name
140 # Ignore any non unicode character
141 with open(file_pov, 'r', encoding='utf-8', errors="ignore") as infile:
142 for line in infile:
143 string = line.replace("{", " ")
144 string = string.replace("}", " ")
145 string = string.replace("<", " ")
146 string = string.replace(">", " ")
147 string = string.replace(",", " ")
148 lw = string.split()
149 # lenwords = len(lw) # Not used... why written?
150 if lw:
151 if lw[0] == "object":
152 write_matrix = True
153 if write_matrix:
154 if lw[0] not in {"object", "matrix"}:
155 index = lw[0]
156 if lw[0] in {"matrix"}:
157 value = [
158 float(lw[1]),
159 float(lw[2]),
160 float(lw[3]),
161 float(lw[4]),
162 float(lw[5]),
163 float(lw[6]),
164 float(lw[7]),
165 float(lw[8]),
166 float(lw[9]),
167 float(lw[10]),
168 float(lw[11]),
169 float(lw[12]),
171 matrixes[index] = value
172 write_matrix = False
173 with open(file_pov, 'r', encoding='utf-8', errors="ignore") as infile:
174 for line in infile:
175 S = line.replace("{", " { ")
176 S = S.replace("}", " } ")
177 S = S.replace(",", " ")
178 S = S.replace("<", "")
179 S = S.replace(">", " ")
180 S = S.replace("=", " = ")
181 S = S.replace(";", " ; ")
182 S = S.split()
183 # lenS = len(S) # Not used... why written?
184 for word in S:
185 # -------- Primitives Import -------- #
186 if word == "cone":
187 cone_search = True
188 name_search = False
189 if cone_search:
190 cache.append(word)
191 if cache[-1] == "}":
192 try:
193 x0 = float(cache[2])
194 y0 = float(cache[3])
195 z0 = float(cache[4])
196 r0 = float(cache[5])
197 x1 = float(cache[6])
198 y1 = float(cache[7])
199 z1 = float(cache[8])
200 r1 = float(cache[9])
201 # Y is height in most pov files, not z
202 bpy.ops.pov.addcone(base=r0, cap=r1, height=(y1 - y0))
203 ob = context.object
204 ob.location = (x0, y0, z0)
205 # ob.scale = (r,r,r)
206 mat_search(cache)
207 except ValueError:
208 pass
209 cache = []
210 cone_search = False
211 if word == "plane":
212 plane_search = True
213 name_search = False
214 if plane_search:
215 cache.append(word)
216 if cache[-1] == "}":
217 try:
218 bpy.ops.pov.addplane()
219 ob = context.object
220 mat_search(cache)
221 except ValueError:
222 pass
223 cache = []
224 plane_search = False
225 if word == "box":
226 box_search = True
227 name_search = False
228 if box_search:
229 cache.append(word)
230 if cache[-1] == "}":
231 try:
232 x0 = float(cache[2])
233 y0 = float(cache[3])
234 z0 = float(cache[4])
235 x1 = float(cache[5])
236 y1 = float(cache[6])
237 z1 = float(cache[7])
238 # imported_corner_1=(x0, y0, z0)
239 # imported_corner_2 =(x1, y1, z1)
240 center = ((x0 + x1) / 2, (y0 + y1) / 2, (z0 + z1) / 2)
241 bpy.ops.pov.addbox()
242 ob = context.object
243 ob.location = center
244 mat_search(cache)
246 except ValueError:
247 pass
248 cache = []
249 box_search = False
250 if word == "cylinder":
251 cylinder_search = True
252 name_search = False
253 if cylinder_search:
254 cache.append(word)
255 if cache[-1] == "}":
256 try:
257 x0 = float(cache[2])
258 y0 = float(cache[3])
259 z0 = float(cache[4])
260 x1 = float(cache[5])
261 y1 = float(cache[6])
262 z1 = float(cache[7])
263 imported_cyl_loc = (x0, y0, z0)
264 imported_cyl_loc_cap = (x1, y1, z1)
266 r = float(cache[8])
268 vec = Vector(imported_cyl_loc_cap) - Vector(imported_cyl_loc)
269 depth = vec.length
270 rot = Vector((0, 0, 1)).rotation_difference(
272 ) # Rotation from Z axis.
273 trans = rot @ Vector( # XXX Not used, why written?
274 (0, 0, depth / 2)
275 ) # Such that origin is at center of the base of the cylinder.
276 # center = ((x0 + x1)/2,(y0 + y1)/2,(z0 + z1)/2)
277 scale_z = sqrt((x1 - x0) ** 2 + (y1 - y0) ** 2 + (z1 - z0) ** 2) / 2
278 bpy.ops.pov.addcylinder(
279 R=r,
280 imported_cyl_loc=imported_cyl_loc,
281 imported_cyl_loc_cap=imported_cyl_loc_cap,
283 ob = context.object
284 ob.location = (x0, y0, z0)
285 # todo: test and search where to add the below currently commented
286 # since Blender defers the evaluation until the results are needed.
287 # bpy.context.view_layer.update()
288 # as explained here: https://docs.blender.org/api/current/info_gotcha.html?highlight=gotcha#no-updates-after-setting-values
289 ob.rotation_euler = rot.to_euler()
290 ob.scale = (1, 1, scale_z)
292 # scale data rather than obj?
293 # bpy.ops.object.mode_set(mode='EDIT')
294 # bpy.ops.mesh.reveal()
295 # bpy.ops.mesh.select_all(action='SELECT')
296 # bpy.ops.transform.resize(value=(1,1,scale_z), orient_type='LOCAL')
297 # bpy.ops.mesh.hide(unselected=False)
298 # bpy.ops.object.mode_set(mode='OBJECT')
300 mat_search(cache)
302 except ValueError:
303 pass
304 cache = []
305 cylinder_search = False
306 if word == "sphere":
307 sphere_search = True
308 name_search = False
309 if sphere_search:
310 cache.append(word)
311 if cache[-1] == "}":
312 x = y = z = r = 0
313 try:
314 x = float(cache[2])
315 y = float(cache[3])
316 z = float(cache[4])
317 r = float(cache[5])
319 except ValueError:
320 pass
321 except BaseException as e:
322 print(e.__doc__)
323 print("An exception occurred: {}".format(e))
324 x = y = z = float(cache[2])
325 r = float(cache[3])
326 bpy.ops.pov.addsphere(R=r, imported_loc=(x, y, z))
327 ob = context.object
328 ob.location = (x, y, z)
329 ob.scale = (r, r, r)
330 mat_search(cache)
331 cache = []
332 sphere_search = False
333 # -------- End Primitives Import -------- #
334 if word == "#declare":
335 name_search = True
336 if name_search:
337 cache.append(word)
338 if word == "mesh2":
339 name_search = False
340 if cache[-2] == "=":
341 name = cache[-3]
342 else:
343 suffix += 1
344 cache = []
345 if word in {"texture", ";"}:
346 name_search = False
347 cache = []
348 if word == "vertex_vectors":
349 verts_search = True
350 if verts_search:
351 cache.append(word)
352 if word == "}":
353 verts_search = False
354 lenverts = cache[2]
355 cache.pop()
356 cache.pop(0)
357 cache.pop(0)
358 cache.pop(0)
359 for j in range(int(lenverts)):
360 x = j * 3
361 y = (j * 3) + 1
362 z = (j * 3) + 2
363 verts.append((float(cache[x]), float(cache[y]), float(cache[z])))
364 cache = []
365 # if word == 'face_indices':
366 # faces_search = True
367 if word == "texture_list": # XXX
368 tex_search = True # XXX
369 if tex_search: # XXX
370 if (
371 word not in {"texture_list", "texture", "{", "}", "face_indices"}
372 and not word.isdigit()
373 ): # XXX
374 pov_mats.append(word) # XXX
375 if word == "face_indices":
376 tex_search = False # XXX
377 faces_search = True
378 if faces_search:
379 cache.append(word)
380 if word == "}":
381 faces_search = False
382 lenfaces = cache[2]
383 cache.pop()
384 cache.pop(0)
385 cache.pop(0)
386 cache.pop(0)
387 lf = int(lenfaces)
388 var = int(len(cache) / lf)
389 for k in range(lf):
390 if var == 3:
391 v0 = k * 3
392 v1 = k * 3 + 1
393 v2 = k * 3 + 2
394 faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
395 if var == 4:
396 v0 = k * 4
397 v1 = k * 4 + 1
398 v2 = k * 4 + 2
399 m = k * 4 + 3
400 materials.append((int(cache[m])))
401 faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
402 if var == 6:
403 v0 = k * 6
404 v1 = k * 6 + 1
405 v2 = k * 6 + 2
406 m0 = k * 6 + 3
407 m1 = k * 6 + 4
408 m2 = k * 6 + 5
409 materials.append(
410 (int(cache[m0]), int(cache[m1]), int(cache[m2]))
412 faces.append((int(cache[v0]), int(cache[v1]), int(cache[v2])))
413 # mesh = pov_define_mesh(None, verts, [], faces, name, hide_geometry=False)
414 # ob = object_utils.object_data_add(context, mesh, operator=None)
416 me = bpy.data.meshes.new(name) # XXX
417 ob = bpy.data.objects.new(name, me) # XXX
418 bpy.context.collection.objects.link(ob) # XXX
419 me.from_pydata(verts, [], faces) # XXX
421 for mat in bpy.data.materials: # XXX
422 blend_mats.append(mat.name) # XXX
423 for m_name in pov_mats: # XXX
424 if m_name not in blend_mats: # XXX
425 bpy.data.materials.new(m_name) # XXX
426 mat_search(cache)
427 ob.data.materials.append(bpy.data.materials[m_name]) # XXX
428 if materials: # XXX
429 for idx, val in enumerate(materials): # XXX
430 try: # XXX
431 ob.data.polygons[idx].material_index = val # XXX
432 except TypeError: # XXX
433 ob.data.polygons[idx].material_index = int(val[0]) # XXX
435 blend_mats = [] # XXX
436 pov_mats = [] # XXX
437 materials = [] # XXX
438 cache = []
439 name_search = True
440 if name in matrixes and not self.import_at_cur:
441 global_matrix = Matrix.Rotation(pi / 2.0, 4, "X")
442 ob = bpy.context.object
443 matrix = ob.matrix_world
444 v = matrixes[name]
445 matrix[0][0] = v[0]
446 matrix[1][0] = v[1]
447 matrix[2][0] = v[2]
448 matrix[0][1] = v[3]
449 matrix[1][1] = v[4]
450 matrix[2][1] = v[5]
451 matrix[0][2] = v[6]
452 matrix[1][2] = v[7]
453 matrix[2][2] = v[8]
454 matrix[0][3] = v[9]
455 matrix[1][3] = v[10]
456 matrix[2][3] = v[11]
457 matrix = global_matrix * ob.matrix_world
458 ob.matrix_world = matrix
459 verts = []
460 faces = []
462 # if word == 'pigment':
463 # try:
464 # #all indices have been incremented once to fit a bad test file
465 # r,g,b,t = float(S[2]),float(S[3]),float(S[4]),float(S[5])
466 # color = (r,g,b,t)
468 # except IndexError:
469 # #all indices have been incremented once to fit alternate test file
470 # r,g,b,t = float(S[3]),float(S[4]),float(S[5]),float(S[6])
471 # color = (r,g,b,t)
472 # except UnboundLocalError:
473 # # In case no transmit is specified ? put it to 0
474 # r,g,b,t = float(S[2]),float(S[3]),float(S[4],0)
475 # color = (r,g,b,t)
477 # except ValueError:
478 # color = (0.8,0.8,0.8,0)
479 # pass
481 # if colors == [] or (colors != [] and color not in colors):
482 # colors.append(color)
483 # name = ob.name+"_mat"
484 # mat_names.append(name)
485 # mat = bpy.data.materials.new(name)
486 # mat.diffuse_color = (r,g,b)
487 # mat.pov.alpha = 1-t
488 # if mat.pov.alpha != 1:
489 # mat.pov.use_transparency=True
490 # ob.data.materials.append(mat)
491 # print (colors)
492 # else:
493 # for m in range(len(colors)):
494 # if color == colors[m]:
495 # ob.data.materials.append(bpy.data.materials[mat_names[m]])
497 # To keep Avogadro Camera angle:
498 # for obj in bpy.context.view_layer.objects:
499 # if obj.type == "CAMERA":
500 # track = obj.constraints.new(type = "TRACK_TO")
501 # track.target = ob
502 # track.track_axis ="TRACK_NEGATIVE_Z"
503 # track.up_axis = "UP_Y"
504 # obj.location = (0,0,0)
505 return {"FINISHED"}
508 classes = (SCENE_OT_POV_Import,)
511 def register():
512 for cls in classes:
513 register_class(cls)
516 def unregister():
517 for cls in reversed(classes):
518 unregister_class(cls)