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