Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender-addons.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJacques Lucke <mail@jlucke.com>2018-09-24 13:21:05 +0300
committerJacques Lucke <mail@jlucke.com>2018-09-24 13:21:05 +0300
commit70177028974edb177fe7a4fa7096ee0e80ce7fb3 (patch)
tree19425dcac8b4f780ac4a4495de67648c9989c89c /io_mesh_uv_layout
parentce871b0b50d0abc853c3a04002f32e620da01e2d (diff)
Export UV Layout: Rewrite Export UV Layout addon
Differential Revision: https://developer.blender.org/D3715 Reviewer: brecht
Diffstat (limited to 'io_mesh_uv_layout')
-rw-r--r--io_mesh_uv_layout/__init__.py341
-rw-r--r--io_mesh_uv_layout/export_uv_eps.py123
-rw-r--r--io_mesh_uv_layout/export_uv_png.py291
-rw-r--r--io_mesh_uv_layout/export_uv_svg.py101
4 files changed, 366 insertions, 490 deletions
diff --git a/io_mesh_uv_layout/__init__.py b/io_mesh_uv_layout/__init__.py
index 4f801a41..5d8d27da 100644
--- a/io_mesh_uv_layout/__init__.py
+++ b/io_mesh_uv_layout/__init__.py
@@ -43,15 +43,20 @@ if "bpy" in locals():
if "export_uv_svg" in locals():
importlib.reload(export_uv_svg)
+import os
import bpy
+from . import export_uv_eps
+from . import export_uv_png
+from . import export_uv_svg
+
from bpy.props import (
- StringProperty,
- BoolProperty,
- EnumProperty,
- IntVectorProperty,
- FloatProperty,
- )
+ StringProperty,
+ BoolProperty,
+ EnumProperty,
+ IntVectorProperty,
+ FloatProperty,
+)
class ExportUVLayout(bpy.types.Operator):
@@ -62,238 +67,162 @@ class ExportUVLayout(bpy.types.Operator):
bl_options = {'REGISTER', 'UNDO'}
filepath: StringProperty(
- subtype='FILE_PATH',
- )
- check_existing: BoolProperty(
- name="Check Existing",
- description="Check and warn on overwriting existing files",
- default=True,
- options={'HIDDEN'},
- )
+ subtype='FILE_PATH',
+ )
export_all: BoolProperty(
- name="All UVs",
- description="Export all UVs in this mesh (not just visible ones)",
- default=False,
- )
+ name="All UVs",
+ description="Export all UVs in this mesh (not just visible ones)",
+ default=False,
+ )
modified: BoolProperty(
- name="Modified",
- description="Exports UVs from the modified mesh",
- default=False,
- )
+ name="Modified",
+ description="Exports UVs from the modified mesh",
+ default=False,
+ )
mode: EnumProperty(
- items=(('SVG', "Scalable Vector Graphic (.svg)",
- "Export the UV layout to a vector SVG file"),
- ('EPS', "Encapsulate PostScript (.eps)",
- "Export the UV layout to a vector EPS file"),
- ('PNG', "PNG Image (.png)",
- "Export the UV layout to a bitmap image"),
- ),
- name="Format",
- description="File format to export the UV layout to",
- default='PNG',
- )
+ items=(('SVG', "Scalable Vector Graphic (.svg)",
+ "Export the UV layout to a vector SVG file"),
+ ('EPS', "Encapsulate PostScript (.eps)",
+ "Export the UV layout to a vector EPS file"),
+ ('PNG', "PNG Image (.png)",
+ "Export the UV layout to a bitmap image"),
+ ),
+ name="Format",
+ description="File format to export the UV layout to",
+ default='PNG',
+ )
size: IntVectorProperty(
- name="Size",
- size=2,
- default=(1024, 1024),
- min=8, max=32768,
- description="Dimensions of the exported file",
- )
+ size=2,
+ default=(1024, 1024),
+ min=8, max=32768,
+ description="Dimensions of the exported file",
+ )
opacity: FloatProperty(
- name="Fill Opacity",
- min=0.0, max=1.0,
- default=0.25,
- description="Set amount of opacity for exported UV layout"
- )
- tessellated: BoolProperty(
- name="Tessellated UVs",
- description="Export tessellated UVs instead of polygons ones",
- default=False,
- options={'HIDDEN'}, # As not working currently :/
- )
+ name="Fill Opacity",
+ min=0.0, max=1.0,
+ default=0.5,
+ description="Set amount of opacity for exported UV layout"
+ )
@classmethod
def poll(cls, context):
obj = context.active_object
- return (obj and obj.type == 'MESH' and obj.data.uv_layers)
-
- def _space_image(self, context):
- space_data = context.space_data
- if isinstance(space_data, bpy.types.SpaceImageEditor):
- return space_data
- else:
- return None
-
- def _image_size(self, context, default_width=1024, default_height=1024):
- # fallback if not in image context.
- image_width, image_height = default_width, default_height
+ return obj is not None and obj.type == 'MESH' and len(obj.data.uv_layers) > 0
- space_data = self._space_image(context)
- if space_data:
- image = space_data.image
- if image:
- width, height = tuple(context.space_data.image.size)
- # in case no data is found.
- if width and height:
- image_width, image_height = width, height
+ def invoke(self, context, event):
+ self.size = self.get_image_size(context)
+ self.filepath = self.get_default_file_name(context) + "." + self.mode.lower()
+ context.window_manager.fileselect_add(self)
+ return {'RUNNING_MODAL'}
- return image_width, image_height
+ def get_default_file_name(self, context):
+ AMOUNT = 3
+ objects = list(self.iter_objects_to_export(context))
+ name = " ".join(sorted([obj.name for obj in objects[:AMOUNT]]))
+ if len(objects) > AMOUNT:
+ name += " and more"
+ return name
- # Trying to be consistent with ED_object_get_active_image
- # from uvedit_ops.c so that what is exported are the uvs
- # that are seen in the UV Editor
- #
- # returns Image or None
- def _get_active_texture(self, mat):
- if mat is None or not mat.use_nodes:
- return None
+ def check(self, context):
+ if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
+ self.filepath = self.filepath[:-4]
- node = self._get_active_texture_nodetree(mat.node_tree)
-
- if node is not None and node.bl_rna.identifier in {'ShaderNodeTexImage', 'ShaderNodeTexEnvironment'}:
- return node.image
-
- return None
-
- # returns image node or None
- def _get_active_texture_nodetree(self, node_tree):
- active_tex_node = None
- active_group = None
- has_group = False
- inactive_node = None
-
- for node in node_tree.nodes:
- if node.show_texture:
- active_tex_node = node
- if node.select:
- return node
- elif inactive_node is None and node.bl_rna.identifier in {'ShaderNodeTexImage', 'ShaderNodeTexEnvironment'}:
- inactive_node = node
- elif node.bl_rna.identifier == 'ShaderNodeGroup':
- if node.select:
- active_group = node
- else:
- has_group = True
-
- # Not found a selected show_texture node
- # Try to find a selected show_texture node in the selected group
- if active_group is not None:
- node = self._get_active_texture_nodetree(active_group.node_tree)
- if node is not None:
- return node
-
- if active_tex_node is not None:
- return active_tex_node
-
- if has_group:
- for node in node_tree.nodes:
- if node.bl_rna.identifier == 'ShaderNodeGroup':
- n = self._get_active_texture_nodetree(node.node_tree)
- if n is not None and (n.show_texture or inactive_node is None):
- return n
-
- return None
-
- def _face_uv_iter(self, context, material_slots, mesh):
- uv_layer = mesh.uv_layers.active.data
- polys = mesh.polygons
-
- if not self.export_all:
- local_image = None
-
- if context.tool_settings.show_uv_local_view:
- space_data = self._space_image(context)
- if space_data:
- local_image = space_data.image
- has_active_texture = [
- self._get_active_texture(slot.material)
- is local_image for slot in material_slots]
-
- for i, p in enumerate(polys):
- # context checks
- if (polys[i].select and (local_image is None or has_active_texture[polys[i].material_index])):
- start = p.loop_start
- end = start + p.loop_total
- uvs = tuple((uv.uv[0], uv.uv[1]) for uv in uv_layer[start:end])
-
- # just write what we see.
- yield (i, uvs)
- else:
- # all, simple
- for i, p in enumerate(polys):
- start = p.loop_start
- end = start + p.loop_total
- uvs = tuple((uv.uv[0], uv.uv[1]) for uv in uv_layer[start:end])
- yield (i, uvs)
+ ext = "." + self.mode.lower()
+ self.filepath = bpy.path.ensure_ext(self.filepath, ext)
+ return True
def execute(self, context):
- obj = context.active_object
- is_editmode = (obj.mode == 'EDIT')
+ object = context.active_object
+ is_editmode = (object.mode == 'EDIT')
if is_editmode:
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
- mode = self.mode
-
filepath = self.filepath
- filepath = bpy.path.ensure_ext(filepath, "." + mode.lower())
- file = open(filepath, "w")
- fw = file.write
+ filepath = bpy.path.ensure_ext(filepath, "." + self.mode.lower())
- if mode == 'EPS':
- from . import export_uv_eps
- exportUV = export_uv_eps.Export_UV_EPS()
- elif mode == 'PNG':
- from . import export_uv_png
- exportUV = export_uv_png.Export_UV_PNG()
- elif mode == 'SVG':
- from . import export_uv_svg
- exportUV = export_uv_svg.Export_UV_SVG()
+ meshes = list(self.iter_meshes_to_export(context))
+ polygon_data = list(self.iter_polygon_data_to_draw(context, meshes))
+ different_colors = set(color for _, color in polygon_data)
+ if self.modified:
+ self.free_meshes(meshes)
- obList = [ob for ob in context.selected_objects if ob.type == 'MESH']
+ export = self.get_exporter()
+ export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
- for obj in obList:
- obj.data.tag = False
-
- exportUV.begin(fw, self.size, self.opacity)
-
- for obj in obList:
- if (obj.data.tag):
- continue
+ if is_editmode:
+ bpy.ops.object.mode_set(mode='EDIT', toggle=False)
- obj.data.tag = True
+ return {'FINISHED'}
+ def iter_meshes_to_export(self, context):
+ for object in self.iter_objects_to_export(context):
if self.modified:
- mesh = obj.to_mesh(context.depsgraph, True)
+ yield object.to_mesh(context.depsgraph, apply_modifiers=True)
else:
- mesh = obj.data
+ yield object.data
- exportUV.build(mesh, lambda: self._face_uv_iter(
- context, obj.material_slots, mesh))
+ def iter_objects_to_export(self, context):
+ for object in context.selected_objects:
+ if object.type != "MESH":
+ continue
+ mesh = object.data
+ if mesh.uv_layers.active is None:
+ continue
+ yield object
- exportUV.end()
+ def free_meshes(self, meshes):
+ for mesh in meshes:
+ bpy.data.meshes.remove(mesh)
- if is_editmode:
- bpy.ops.object.mode_set(mode='EDIT', toggle=False)
+ def currently_image_image_editor(self, context):
+ return isinstance(context.space_data, bpy.types.SpaceImageEditor)
- file.close()
+ def get_currently_opened_image(self, context):
+ if not self.currently_image_image_editor(context):
+ return None
+ return context.space_data.image
- return {'FINISHED'}
+ def get_image_size(self, context):
+ # fallback if not in image context
+ image_width = self.size[0]
+ image_height = self.size[1]
- def check(self, context):
- filepath = bpy.path.ensure_ext(self.filepath, "." + self.mode.lower())
- if filepath != self.filepath:
- self.filepath = filepath
- return True
- else:
- return False
+ # get size of "active" image if some exist
+ image = self.get_currently_opened_image(context)
+ if image is not None:
+ width, height = image.size
+ if width and height:
+ image_width = width
+ image_height = height
- def invoke(self, context, event):
- import os
- self.size = self._image_size(context)
- self.filepath = os.path.splitext(bpy.data.filepath)[0]
- wm = context.window_manager
- wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
+ return image_width, image_height
+
+ def iter_polygon_data_to_draw(self, context, meshes):
+ for mesh in meshes:
+ uv_layer = mesh.uv_layers.active.data
+ for polygon in mesh.polygons:
+ if self.export_all or polygon.select:
+ start = polygon.loop_start
+ end = start + polygon.loop_total
+ uvs = tuple(tuple(uv.uv) for uv in uv_layer[start:end])
+ yield (uvs, self.get_polygon_color(mesh, polygon))
+
+ def get_polygon_color(self, mesh, polygon, default = (0.8, 0.8, 0.8)):
+ if polygon.material_index < len(mesh.materials):
+ material = mesh.materials[polygon.material_index]
+ if material is not None:
+ return tuple(material.diffuse_color)
+ return default
+
+ def get_exporter(self):
+ if self.mode == "PNG":
+ return export_uv_png.export
+ elif self.mode == "EPS":
+ return export_uv_eps.export
+ elif self.mode == "SVG":
+ return export_uv_svg.export
+ else:
+ assert False
def menu_func(self, context):
@@ -304,11 +233,9 @@ def register():
bpy.utils.register_class(ExportUVLayout)
bpy.types.IMAGE_MT_uvs.append(menu_func)
-
def unregister():
bpy.utils.unregister_class(ExportUVLayout)
bpy.types.IMAGE_MT_uvs.remove(menu_func)
-
if __name__ == "__main__":
register()
diff --git a/io_mesh_uv_layout/export_uv_eps.py b/io_mesh_uv_layout/export_uv_eps.py
index d00e998a..3280cefa 100644
--- a/io_mesh_uv_layout/export_uv_eps.py
+++ b/io_mesh_uv_layout/export_uv_eps.py
@@ -21,75 +21,72 @@
import bpy
-class Export_UV_EPS:
- def begin(self, fw, image_size, opacity):
+def export(filepath, face_data, colors, width, height, opacity):
+ with open(filepath, "w") as file:
+ for text in get_file_parts(face_data, colors, width, height, opacity):
+ file.write(text)
- self.fw = fw
- self.image_width = image_size[0]
- self.image_height = image_size[1]
- self.opacity = opacity
+def get_file_parts(face_data, colors, width, height, opacity):
+ yield from header(width, height)
+ if opacity > 0.0:
+ name_by_color = {}
+ yield from prepare_colors(colors, name_by_color)
+ yield from draw_colored_polygons(face_data, name_by_color, width, height)
+ yield from draw_lines(face_data, width, height)
+ yield from footer()
- fw("%!PS-Adobe-3.0 EPSF-3.0\n")
- fw("%%%%Creator: Blender %s\n" % bpy.app.version_string)
- fw("%%Pages: 1\n")
- fw("%%Orientation: Portrait\n")
- fw("%%%%BoundingBox: 0 0 %d %d\n" % (self.image_width, self.image_height))
- fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" %
- (self.image_width, self.image_height))
- fw("%%EndComments\n")
- fw("%%Page: 1 1\n")
- fw("0 0 translate\n")
- fw("1.0 1.0 scale\n")
- fw("0 0 0 setrgbcolor\n")
- fw("[] 0 setdash\n")
- fw("1 setlinewidth\n")
- fw("1 setlinejoin\n")
- fw("1 setlinecap\n")
- def build(self, mesh, face_iter_func):
- polys = mesh.polygons
+def header(width, height):
+ yield "%!PS-Adobe-3.0 EPSF-3.0\n"
+ yield f"%%Creator: Blender {bpy.app.version_string}\n"
+ yield "%%Pages: 1\n"
+ yield "%%Orientation: Portrait\n"
+ yield f"%%BoundingBox: 0 0 {width} {height}\n"
+ yield f"%%HiResBoundingBox: 0.0 0.0 {width:.4f} {height:.4f}\n"
+ yield "%%EndComments\n"
+ yield "%%Page: 1 1\n"
+ yield "0 0 translate\n"
+ yield "1.0 1.0 scale\n"
+ yield "0 0 0 setrgbcolor\n"
+ yield "[] 0 setdash\n"
+ yield "1 setlinewidth\n"
+ yield "1 setlinejoin\n"
+ yield "1 setlinecap\n"
- if self.opacity > 0.0:
- for i, mat in enumerate(mesh.materials if mesh.materials else [None]):
- self.fw("/DRAW_%d {" % i)
- self.fw("gsave\n")
- if mat:
- color = tuple((1.0 - ((1.0 - c) * self.opacity))
- for c in mat.diffuse_color)
- else:
- color = 1.0, 1.0, 1.0
- self.fw("%.3g %.3g %.3g setrgbcolor\n" % color)
- self.fw("fill\n")
- self.fw("grestore\n")
- self.fw("0 setgray\n")
- self.fw("} def\n")
+def prepare_colors(colors, out_name_by_color):
+ for i, color in enumerate(colors):
+ name = f"COLOR_{i}"
+ yield "/%s {" % name
+ out_name_by_color[color] = name
- # fill
- for i, uvs in face_iter_func():
- self.fw("newpath\n")
- for j, uv in enumerate(uvs):
- uv_scale = (uv[0] * self.image_width, uv[1] * self.image_height)
- if j == 0:
- self.fw("%.5f %.5f moveto\n" % uv_scale)
- else:
- self.fw("%.5f %.5f lineto\n" % uv_scale)
+ yield "gsave\n"
+ yield "%.3g %.3g %.3g setrgbcolor\n" % color
+ yield "fill\n"
+ yield "grestore\n"
+ yield "0 setgray\n"
+ yield "} def\n"
- self.fw("closepath\n")
- self.fw("DRAW_%d\n" % polys[i].material_index)
+def draw_colored_polygons(face_data, name_by_color, width, height):
+ for uvs, color in face_data:
+ yield from draw_polygon_path(uvs, width, height)
+ yield "closepath\n"
+ yield "%s\n" % name_by_color[color]
- # stroke only
- for i, uvs in face_iter_func():
- self.fw("newpath\n")
- for j, uv in enumerate(uvs):
- uv_scale = (uv[0] * self.image_width, uv[1] * self.image_height)
- if j == 0:
- self.fw("%.5f %.5f moveto\n" % uv_scale)
- else:
- self.fw("%.5f %.5f lineto\n" % uv_scale)
+def draw_lines(face_data, width, height):
+ for uvs, _ in face_data:
+ yield from draw_polygon_path(uvs, width, height)
+ yield "closepath\n"
+ yield "stroke\n"
- self.fw("closepath\n")
- self.fw("stroke\n")
+def draw_polygon_path(uvs, width, height):
+ yield "newpath\n"
+ for j, uv in enumerate(uvs):
+ uv_scale = (uv[0] * width, uv[1] * height)
+ if j == 0:
+ yield "%.5f %.5f moveto\n" % uv_scale
+ else:
+ yield "%.5f %.5f lineto\n" % uv_scale
- def end(self):
- self.fw("showpage\n")
- self.fw("%%EOF\n")
+def footer():
+ yield "showpage\n"
+ yield "%%EOF\n" \ No newline at end of file
diff --git a/io_mesh_uv_layout/export_uv_png.py b/io_mesh_uv_layout/export_uv_png.py
index be03ed0b..74b820b8 100644
--- a/io_mesh_uv_layout/export_uv_png.py
+++ b/io_mesh_uv_layout/export_uv_png.py
@@ -20,163 +20,134 @@
import bpy
-
-class Export_UV_PNG:
- def begin(self, fw, image_size, opacity):
- self.filepath = fw.__self__.name
- fw.__self__.close()
-
- self.scene = bpy.data.scenes.new("uv_temp")
-
- image_width = image_size[0]
- image_height = image_size[1]
-
- self.scene.render.resolution_x = image_width
- self.scene.render.resolution_y = image_height
- self.scene.render.resolution_percentage = 100
-
- self.scene.render.alpha_mode = 'TRANSPARENT'
-
- if image_width > image_height:
- self.scene.render.pixel_aspect_y = image_width / image_height
- elif image_width < image_height:
- self.scene.render.pixel_aspect_x = image_height / image_width
-
- self.base_material = bpy.data.materials.new("uv_temp_base")
- self.base_material.use_nodes = True
- self.base_material.node_tree.nodes.clear()
- output_node = self.base_material.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
- emission_node = self.base_material.node_tree.nodes.new(type="ShaderNodeEmission")
- emission_node.inputs["Color"].default_value = (1.0, 1.0, 1.0, opacity)
- self.base_material.node_tree.links.new(
- output_node.inputs["Surface"],
- emission_node.outputs["Emission"])
-
- self.material_wire = self.base_material.copy()
- self.material_wire.name = "Wire"
- self.material_wire.node_tree.nodes['Emission'].inputs["Color"].default_value = (0.0, 0.0, 0.0, 1.0)
-
- self.base_material.blend_method = "BLEND"
-
- self.material_solids_list = [] # list of lists
- self.material_solids_list.append([self.base_material,
- self.material_wire])
-
- self.mesh_list = []
- self.obj_list = []
-
- def build(self, mesh_source, face_iter_func):
- material_solids = [self.base_material.copy() for i in range(max(1, len(mesh_source.materials)))]
-
- self.material_solids_list.append(material_solids)
-
- mesh = bpy.data.meshes.new("uv_temp")
- self.mesh_list.append(mesh)
-
- for mat_solid in material_solids:
- mesh.materials.append(mat_solid)
-
- # setup materials
- for i, mat_solid in enumerate(material_solids):
- if mesh_source.materials and mesh_source.materials[i]:
- mat_solid.node_tree.nodes['Emission'].\
- inputs["Color"].default_value[0:3]\
- = mesh_source.materials[i].diffuse_color
-
- # Add materials for wireframe modifier.
- for mat_solid in material_solids:
- mesh.materials.append(self.material_wire)
-
- polys_source = mesh_source.polygons
-
- # get unique UV's in case there are many overlapping
- # which slow down filling.
- face_hash = {(uvs, polys_source[i].material_index)
- for i, uvs in face_iter_func()}
-
- # now set the faces coords and locations
- # build mesh data
- mesh_new_vertices = []
- mesh_new_materials = []
- mesh_new_polys_startloop = []
- mesh_new_polys_totloop = []
- mesh_new_loops_vertices = []
-
- current_vert = 0
-
- for uvs, mat_idx in face_hash:
- num_verts = len(uvs)
- # dummy = (0.0,) * num_verts
- for uv in uvs:
- mesh_new_vertices += (uv[0], uv[1], 0.0)
- mesh_new_polys_startloop.append(current_vert)
- mesh_new_polys_totloop.append(num_verts)
- mesh_new_loops_vertices += range(current_vert,
- current_vert + num_verts)
- mesh_new_materials.append(mat_idx)
- current_vert += num_verts
-
- mesh.vertices.add(current_vert)
- mesh.loops.add(current_vert)
- mesh.polygons.add(len(mesh_new_polys_startloop))
-
- mesh.vertices.foreach_set("co", mesh_new_vertices)
- mesh.loops.foreach_set("vertex_index", mesh_new_loops_vertices)
- mesh.polygons.foreach_set("loop_start", mesh_new_polys_startloop)
- mesh.polygons.foreach_set("loop_total", mesh_new_polys_totloop)
- mesh.polygons.foreach_set("material_index", mesh_new_materials)
-
- mesh.update(calc_edges=True)
-
- obj_solid = bpy.data.objects.new("uv_temp_solid", mesh)
-
- wire_mod = obj_solid.modifiers.new("wire_mod", 'WIREFRAME')
- wire_mod.use_replace = False
- wire_mod.use_relative_offset = True
-
- wire_mod.material_offset = len(material_solids)
-
- self.obj_list.append(obj_solid)
- self.scene.collection.objects.link(obj_solid)
-
- def end(self):
- # setup the camera
- cam = bpy.data.cameras.new("uv_temp")
- cam.type = 'ORTHO'
- cam.ortho_scale = 1.0
- obj_cam = bpy.data.objects.new("uv_temp_cam", cam)
- obj_cam.location = 0.5, 0.5, 1.0
- self.scene.collection.objects.link(obj_cam)
- self.obj_list.append(obj_cam)
- self.scene.camera = obj_cam
-
- # scene render settings
- self.scene.render.alpha_mode = 'TRANSPARENT'
- self.scene.render.image_settings.color_mode = 'RGBA'
-
- self.scene.frame_start = 1
- self.scene.frame_end = 1
-
- self.scene.render.image_settings.file_format = 'PNG'
- self.scene.render.filepath = self.filepath
-
- self.scene.update()
-
- data_context = {"blend_data": bpy.context.blend_data,
- "scene": self.scene}
- bpy.ops.render.render(data_context, write_still=True)
-
- # cleanup
- bpy.data.scenes.remove(self.scene, do_unlink=True)
-
- for obj in self.obj_list:
- bpy.data.objects.remove(obj, do_unlink=True)
-
- bpy.data.cameras.remove(cam, do_unlink=True)
-
- for mesh in self.mesh_list:
- bpy.data.meshes.remove(mesh, do_unlink=True)
-
- for material_solids in self.material_solids_list:
- for mat_solid in material_solids:
- bpy.data.materials.remove(mat_solid, do_unlink=True)
+# maybe we could also just use the svg exporter, import it again
+# and render it. Unfortunately the svg importer does not work atm.
+def export(filepath, face_data, colors, width, height, opacity):
+ aspect = width / height
+
+ # curves for lines
+ lines = curve_from_uvs(face_data, aspect, 1 / min(width, height))
+ lines_object = bpy.data.objects.new("temp_lines_object", lines)
+ black_material = make_colored_material((0, 0, 0))
+ lines.materials.append(black_material)
+
+ # background mesh
+ background_mesh = background_mesh_from_uvs(face_data, colors, aspect, opacity)
+ background_object = bpy.data.objects.new("temp_background_object", background_mesh)
+ background_object.location = (0, 0, -1)
+
+ # camera
+ camera = bpy.data.cameras.new("temp_camera")
+ camera_object = bpy.data.objects.new("temp_camera_object", camera)
+ camera.type = "ORTHO"
+ camera.ortho_scale = max(1, aspect)
+ camera_object.location = (aspect / 2, 0.5, 1)
+ camera_object.rotation_euler = (0, 0, 0)
+
+ # scene
+ scene = bpy.data.scenes.new("temp_scene")
+ scene.render.engine = "BLENDER_EEVEE"
+ scene.render.resolution_x = width
+ scene.render.resolution_y = height
+ scene.render.image_settings.color_mode = "RGBA"
+ scene.render.alpha_mode = "TRANSPARENT"
+ scene.render.filepath = filepath
+
+ # Link everything to the scene
+ scene.collection.objects.link(lines_object)
+ scene.collection.objects.link(camera_object)
+ scene.collection.objects.link(background_object)
+ scene.camera = camera_object
+
+ # Render
+ override = {"scene" : scene}
+ bpy.ops.render.render(override, write_still=True)
+
+ # Cleanup
+ bpy.data.objects.remove(lines_object)
+ bpy.data.objects.remove(camera_object)
+ bpy.data.objects.remove(background_object)
+
+ for material in background_mesh.materials:
+ bpy.data.materials.remove(material)
+ bpy.data.meshes.remove(background_mesh)
+
+ bpy.data.cameras.remove(camera)
+ bpy.data.curves.remove(lines)
+ bpy.data.materials.remove(black_material)
+ bpy.data.scenes.remove(scene)
+
+def curve_from_uvs(face_data, aspect, thickness):
+ lines = bpy.data.curves.new("temp_curve", "CURVE")
+ lines.fill_mode = "BOTH"
+ lines.bevel_depth = thickness
+ lines.offset = -thickness / 2
+ lines.dimensions = "3D"
+
+ for uvs, _ in face_data:
+ for i in range(len(uvs)):
+ start = uvs[i]
+ end = uvs[(i+1) % len(uvs)]
+
+ spline = lines.splines.new("POLY")
+ # one point is already there
+ spline.points.add(count=1)
+ points = spline.points
+
+ points[0].co.x = start[0] * aspect
+ points[0].co.y = start[1]
+
+ points[1].co.x = end[0] * aspect
+ points[1].co.y = end[1]
+
+ return lines
+
+def background_mesh_from_uvs(face_data, colors, aspect, opacity):
+ mesh = bpy.data.meshes.new("temp_background")
+
+ vertices = []
+ polygons = []
+ for uvs, _ in face_data:
+ polygon = []
+ for uv in uvs:
+ polygon.append(len(vertices))
+ vertices.append((uv[0] * aspect, uv[1], 0))
+ polygons.append(tuple(polygon))
+
+ mesh.from_pydata(vertices, [], polygons)
+
+ materials, material_index_by_color = make_polygon_background_materials(colors, opacity)
+ for material in materials:
+ mesh.materials.append(material)
+
+ for generated_polygon, (_, color) in zip(mesh.polygons, face_data):
+ generated_polygon.material_index = material_index_by_color[color]
+
+ mesh.update()
+ mesh.validate()
+
+ return mesh
+
+def make_polygon_background_materials(colors, opacity=1):
+ materials = []
+ material_index_by_color = {}
+ for i, color in enumerate(colors):
+ material = make_colored_material(color, opacity)
+ materials.append(material)
+ material_index_by_color[color] = i
+ return materials, material_index_by_color
+
+def make_colored_material(color, opacity=1):
+ material = bpy.data.materials.new("temp_material")
+ material.use_nodes = True
+ material.blend_method = "BLEND"
+ tree = material.node_tree
+ tree.nodes.clear()
+
+ output_node = tree.nodes.new("ShaderNodeOutputMaterial")
+ emission_node = tree.nodes.new("ShaderNodeEmission")
+
+ emission_node.inputs["Color"].default_value = [color[0], color[1], color[2], opacity]
+ tree.links.new(emission_node.outputs["Emission"], output_node.inputs["Surface"])
+
+ return material
diff --git a/io_mesh_uv_layout/export_uv_svg.py b/io_mesh_uv_layout/export_uv_svg.py
index fe727d09..d00f9402 100644
--- a/io_mesh_uv_layout/export_uv_svg.py
+++ b/io_mesh_uv_layout/export_uv_svg.py
@@ -19,65 +19,46 @@
# <pep8-80 compliant>
import bpy
-
-
-from xml.sax.saxutils import escape
from os.path import basename
+from xml.sax.saxutils import escape
-
-class Export_UV_SVG:
- def begin(self, fw, image_size, opacity):
-
- self.fw = fw
- self.image_width = image_size[0]
- self.image_height = image_size[1]
- self.opacity = opacity
-
- fw('<?xml version="1.0" standalone="no"?>\n')
- fw('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n')
- fw(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
- fw('<svg width="%d" height="%d" viewBox="0 0 %d %d"\n' %
- (self.image_width, self.image_height, self.image_width, self.image_height))
- fw(' xmlns="http://www.w3.org/2000/svg" version="1.1">\n')
- desc = ("%r, (Blender %s)" %
- (basename(bpy.data.filepath), bpy.app.version_string))
- fw('<desc>%s</desc>\n' % escape(desc))
-
- def build(self, mesh, face_iter_func):
- self.fw('<g>\n')
- desc = ("Mesh: %s" % (mesh.name))
- self.fw('<desc>%s</desc>\n' % escape(desc))
-
- # svg colors
- fill_settings = []
- fill_default = 'fill="grey"'
- for mat in mesh.materials if mesh.materials else [None]:
- if mat:
- fill_settings.append('fill="rgb(%d, %d, %d)"' %
- tuple(int(c * 255) for c in mat.diffuse_color))
- else:
- fill_settings.append(fill_default)
-
- polys = mesh.polygons
- for i, uvs in face_iter_func():
- try: # rare cases material index is invalid.
- fill = fill_settings[polys[i].material_index]
- except IndexError:
- fill = fill_default
-
- self.fw('<polygon stroke="black" stroke-width="1"')
- if self.opacity > 0.0:
- self.fw(' %s fill-opacity="%.2g"' % (fill, self.opacity))
-
- self.fw(' points="')
-
- for j, uv in enumerate(uvs):
- x, y = uv[0], 1.0 - uv[1]
- self.fw('%.3f,%.3f ' % (x * self.image_width, y * self.image_height))
- self.fw('" />\n')
-
- self.fw('</g>\n')
-
- def end(self):
- self.fw('\n')
- self.fw('</svg>\n')
+def export(filepath, face_data, colors, width, height, opacity):
+ with open(filepath, "w") as file:
+ for text in get_file_parts(face_data, colors, width, height, opacity):
+ file.write(text)
+
+def get_file_parts(face_data, colors, width, height, opacity):
+ yield from header(width, height)
+ yield from draw_polygons(face_data, width, height, opacity)
+ yield from footer()
+
+def header(width, height):
+ yield '<?xml version="1.0" standalone="no"?>\n'
+ yield '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n'
+ yield ' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n'
+ yield f'<svg width="{width}" height="{height}" viewBox="0 0 {width} {height}"\n'
+ yield ' xmlns="http://www.w3.org/2000/svg" version="1.1">\n'
+ desc = f"{basename(bpy.data.filepath)}, (Blender {bpy.app.version_string})"
+ yield f'<desc>{escape(desc)}</desc>\n'
+
+def draw_polygons(face_data, width, height, opacity):
+ for uvs, color in face_data:
+ fill = f'fill="{get_color_string(color)}"'
+
+ yield '<polygon stroke="black" stroke-width="1"'
+ yield f' {fill} fill-opacity="{opacity:.2g}"'
+
+ yield ' points="'
+
+ for uv in uvs:
+ x, y = uv[0], 1.0 - uv[1]
+ yield f'{x*width:.3f},{y*height:.3f} '
+ yield '" />\n'
+
+def get_color_string(color):
+ r, g, b = color
+ return f"rgb({round(r*255)}, {round(g*255)}, {round(b*255)})"
+
+def footer():
+ yield '\n'
+ yield '</svg>\n' \ No newline at end of file