Export UV Layout: Rewrite Export UV Layout addon

Differential Revision: https://developer.blender.org/D3715

Reviewer: brecht
This commit is contained in:
Jacques Lucke 2018-09-24 12:21:05 +02:00
parent ce871b0b50
commit 7017702897
4 changed files with 336 additions and 460 deletions

View File

@ -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)
return obj is not None and obj.type == 'MESH' and len(obj.data.uv_layers) > 0
def _space_image(self, context):
space_data = context.space_data
if isinstance(space_data, bpy.types.SpaceImageEditor):
return space_data
else:
return None
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'}
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
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
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 check(self, context):
if any(self.filepath.endswith(ext) for ext in (".png", ".eps", ".svg")):
self.filepath = self.filepath[:-4]
return image_width, image_height
# 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
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']
for obj in obList:
obj.data.tag = False
exportUV.begin(fw, self.size, self.opacity)
for obj in obList:
if (obj.data.tag):
continue
obj.data.tag = True
if self.modified:
mesh = obj.to_mesh(context.depsgraph, True)
else:
mesh = obj.data
exportUV.build(mesh, lambda: self._face_uv_iter(
context, obj.material_slots, mesh))
exportUV.end()
export = self.get_exporter()
export(filepath, polygon_data, different_colors, self.size[0], self.size[1], self.opacity)
if is_editmode:
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
file.close()
return {'FINISHED'}
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
def iter_meshes_to_export(self, context):
for object in self.iter_objects_to_export(context):
if self.modified:
yield object.to_mesh(context.depsgraph, apply_modifiers=True)
else:
yield object.data
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'}
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
def free_meshes(self, meshes):
for mesh in meshes:
bpy.data.meshes.remove(mesh)
def currently_image_image_editor(self, context):
return isinstance(context.space_data, bpy.types.SpaceImageEditor)
def get_currently_opened_image(self, context):
if not self.currently_image_image_editor(context):
return None
return context.space_data.image
def get_image_size(self, context):
# fallback if not in image context
image_width = self.size[0]
image_height = self.size[1]
# 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
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()

View File

@ -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"

View File

@ -20,163 +20,134 @@
import bpy
# 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
class Export_UV_PNG:
def begin(self, fw, image_size, opacity):
self.filepath = fw.__self__.name
fw.__self__.close()
# 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)
self.scene = bpy.data.scenes.new("uv_temp")
# 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)
image_width = image_size[0]
image_height = image_size[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)
self.scene.render.resolution_x = image_width
self.scene.render.resolution_y = image_height
self.scene.render.resolution_percentage = 100
# 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
self.scene.render.alpha_mode = 'TRANSPARENT'
# 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
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
# Render
override = {"scene" : scene}
bpy.ops.render.render(override, write_still=True)
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"])
# Cleanup
bpy.data.objects.remove(lines_object)
bpy.data.objects.remove(camera_object)
bpy.data.objects.remove(background_object)
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)
for material in background_mesh.materials:
bpy.data.materials.remove(material)
bpy.data.meshes.remove(background_mesh)
self.base_material.blend_method = "BLEND"
bpy.data.cameras.remove(camera)
bpy.data.curves.remove(lines)
bpy.data.materials.remove(black_material)
bpy.data.scenes.remove(scene)
self.material_solids_list = [] # list of lists
self.material_solids_list.append([self.base_material,
self.material_wire])
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"
self.mesh_list = []
self.obj_list = []
for uvs, _ in face_data:
for i in range(len(uvs)):
start = uvs[i]
end = uvs[(i+1) % len(uvs)]
def build(self, mesh_source, face_iter_func):
material_solids = [self.base_material.copy() for i in range(max(1, len(mesh_source.materials)))]
spline = lines.splines.new("POLY")
# one point is already there
spline.points.add(count=1)
points = spline.points
self.material_solids_list.append(material_solids)
points[0].co.x = start[0] * aspect
points[0].co.y = start[1]
mesh = bpy.data.meshes.new("uv_temp")
self.mesh_list.append(mesh)
points[1].co.x = end[0] * aspect
points[1].co.y = end[1]
for mat_solid in material_solids:
mesh.materials.append(mat_solid)
return lines
# 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
def background_mesh_from_uvs(face_data, colors, aspect, opacity):
mesh = bpy.data.meshes.new("temp_background")
# Add materials for wireframe modifier.
for mat_solid in material_solids:
mesh.materials.append(self.material_wire)
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))
polys_source = mesh_source.polygons
mesh.from_pydata(vertices, [], 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()}
materials, material_index_by_color = make_polygon_background_materials(colors, opacity)
for material in materials:
mesh.materials.append(material)
# 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 = []
for generated_polygon, (_, color) in zip(mesh.polygons, face_data):
generated_polygon.material_index = material_index_by_color[color]
current_vert = 0
mesh.update()
mesh.validate()
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
return mesh
mesh.vertices.add(current_vert)
mesh.loops.add(current_vert)
mesh.polygons.add(len(mesh_new_polys_startloop))
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
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)
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()
mesh.update(calc_edges=True)
output_node = tree.nodes.new("ShaderNodeOutputMaterial")
emission_node = tree.nodes.new("ShaderNodeEmission")
obj_solid = bpy.data.objects.new("uv_temp_solid", mesh)
emission_node.inputs["Color"].default_value = [color[0], color[1], color[2], opacity]
tree.links.new(emission_node.outputs["Emission"], output_node.inputs["Surface"])
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)
return material

View File

@ -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
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)
class Export_UV_SVG:
def begin(self, fw, image_size, opacity):
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()
self.fw = fw
self.image_width = image_size[0]
self.image_height = image_size[1]
self.opacity = opacity
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'
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 draw_polygons(face_data, width, height, opacity):
for uvs, color in face_data:
fill = f'fill="{get_color_string(color)}"'
def build(self, mesh, face_iter_func):
self.fw('<g>\n')
desc = ("Mesh: %s" % (mesh.name))
self.fw('<desc>%s</desc>\n' % escape(desc))
yield '<polygon stroke="black" stroke-width="1"'
yield f' {fill} fill-opacity="{opacity:.2g}"'
# 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)
yield ' points="'
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
for uv in uvs:
x, y = uv[0], 1.0 - uv[1]
yield f'{x*width:.3f},{y*height:.3f} '
yield '" />\n'
self.fw('<polygon stroke="black" stroke-width="1"')
if self.opacity > 0.0:
self.fw(' %s fill-opacity="%.2g"' % (fill, self.opacity))
def get_color_string(color):
r, g, b = color
return f"rgb({round(r*255)}, {round(g*255)}, {round(b*255)})"
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 footer():
yield '\n'
yield '</svg>\n'