mirror of https://github.com/H-uru/korman.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
9.5 KiB
225 lines
9.5 KiB
# This file is part of Korman. |
|
# |
|
# Korman is free software: you can redistribute it and/or modify |
|
# it under the terms of the GNU General Public License as published by |
|
# the Free Software Foundation, either version 3 of the License, or |
|
# (at your option) any later version. |
|
# |
|
# Korman is distributed in the hope that it will be useful, |
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
# GNU General Public License for more details. |
|
# |
|
# You should have received a copy of the GNU General Public License |
|
# along with Korman. If not, see <http://www.gnu.org/licenses/>. |
|
|
|
import bpy |
|
from bpy.props import * |
|
import cProfile |
|
from pathlib import Path |
|
import pstats |
|
|
|
from .. import exporter |
|
from ..properties.prop_world import PlasmaAge, game_versions |
|
from ..korlib import ConsoleToggler |
|
|
|
class ExportOperator(bpy.types.Operator): |
|
"""Exports ages for Cyan Worlds' Plasma Engine""" |
|
|
|
bl_idname = "export.plasma_age" |
|
bl_label = "Export Age" |
|
|
|
# Here's the rub: we define the properties in this dict. We're going to register them as a seekrit |
|
# over on the PlasmaAge world properties. We've got a helper so we can access them like they're actually on us... |
|
# If you want a volatile property, register it directly on this operator! |
|
_properties = { |
|
"profile_export": (BoolProperty, {"name": "Profile", |
|
"description": "Profiles the exporter using cProfile", |
|
"default": False}), |
|
|
|
"verbose": (BoolProperty, {"name": "Display Verbose Log", |
|
"description": "Shows the verbose export log in the console", |
|
"default": False}), |
|
|
|
"show_console": (BoolProperty, {"name": "Display Log Console", |
|
"description": "Forces the Blender System Console open during the export", |
|
"default": True}), |
|
|
|
"texcache_path": (StringProperty, {"name": "Texture Cache Path", |
|
"description": "Texture Cache Filepath"}), |
|
|
|
"texcache_method": (EnumProperty, {"name": "Texture Cache", |
|
"description": "Texture Cache Settings", |
|
"items": [("skip", "Don't Use Texture Cache", "The texture cache is neither used nor updated."), |
|
("use", "Use Texture Cache", "Use (and update, if needed) cached textures."), |
|
("rebuild", "Rebuild Texture Cache", "Rebuilds the texture cache from scratch.")], |
|
"default": "use"}), |
|
|
|
"lighting_method": (EnumProperty, {"name": "Static Lighting", |
|
"description": "Static Lighting Settings", |
|
"items": [("skip", "Don't Bake Lighting", "Static lighting is not baked during this export (fastest export)"), |
|
("bake", "Bake Lighting", "Static lighting is baked according to your specifications"), |
|
("force_vcol", "Force Vertex Color Bake", "All static lighting is baked as vertex colors (faster export)"), |
|
("force_lightmap", "Force Lightmap Bake", "All static lighting is baked as lightmaps (slower export)")], |
|
"default": "bake"}), |
|
|
|
"export_active": (BoolProperty, {"name": "INTERNAL: Export currently running", |
|
"default": False, |
|
"options": {"SKIP_SAVE"}}), |
|
} |
|
|
|
# This wigs out and very bad things happen if it's not directly on the operator... |
|
filepath = StringProperty(subtype="FILE_PATH") |
|
filter_glob = StringProperty(default="*.age", options={'HIDDEN'}) |
|
|
|
version = EnumProperty(name="Version", |
|
description="Plasma version to export this age for", |
|
items=game_versions, |
|
default="pvPots", |
|
options=set()) |
|
|
|
def draw(self, context): |
|
layout = self.layout |
|
age = context.scene.world.plasma_age |
|
|
|
# The crazy mess we're doing with props on the fly means we have to explicitly draw them :( |
|
layout.prop(self, "version") |
|
layout.prop(age, "texcache_method", text="") |
|
layout.prop(age, "lighting_method") |
|
row = layout.row() |
|
row.enabled = ConsoleToggler.is_platform_supported() |
|
row.prop(age, "show_console") |
|
layout.prop(age, "verbose") |
|
layout.prop(age, "profile_export") |
|
|
|
def __getattr__(self, attr): |
|
if attr in self._properties: |
|
return getattr(bpy.context.scene.world.plasma_age, attr) |
|
raise AttributeError(attr) |
|
|
|
def __setattr__(self, attr, value): |
|
if attr in self._properties: |
|
setattr(bpy.context.scene.world.plasma_age, attr, value) |
|
else: |
|
super().__setattr__(attr, value) |
|
|
|
@property |
|
def has_reports(self): |
|
return hasattr(self.report) |
|
|
|
@classmethod |
|
def poll(cls, context): |
|
return context.scene.render.engine == "PLASMA_GAME" |
|
|
|
def execute(self, context): |
|
# Before we begin, do some basic sanity checking... |
|
path = Path(self.filepath) |
|
if not self.filepath: |
|
self.error = "No file specified" |
|
return {"CANCELLED"} |
|
else: |
|
if not path.exists: |
|
try: |
|
path.mkdir(parents=True) |
|
except: |
|
self.report({"ERROR"}, "Failed to create export directory") |
|
return {"CANCELLED"} |
|
|
|
# We need to back out of edit mode--this ensures that all changes are committed |
|
if context.mode != "OBJECT": |
|
bpy.ops.object.mode_set(mode="OBJECT") |
|
|
|
# Separate blender operator and actual export logic for my sanity |
|
ageName = path.stem |
|
with _UiHelper(context) as _ui: |
|
e = exporter.Exporter(self) |
|
try: |
|
self.export_active = True |
|
if self.profile_export: |
|
profile = path.with_name("{}_cProfile".format(ageName)) |
|
profile = cProfile.runctx("e.run()", globals(), locals(), str(profile)) |
|
else: |
|
e.run() |
|
except exporter.ExportError as error: |
|
self.report({"ERROR"}, str(error)) |
|
return {"CANCELLED"} |
|
else: |
|
if self.profile_export: |
|
stats_out = path.with_name("{}_profile.log".format(ageName)) |
|
with open(str(stats_out), "w") as out: |
|
stats = pstats.Stats(profile, stream=out) |
|
stats = stats.sort_stats("time", "calls") |
|
stats.print_stats() |
|
return {"FINISHED"} |
|
finally: |
|
self.export_active = False |
|
|
|
def invoke(self, context, event): |
|
# Called when a user hits "export" from the menu |
|
# We will prompt them for the export info, then call execute() |
|
if not self.filepath: |
|
blend_filepath = context.blend_data.filepath |
|
if not blend_filepath: |
|
blend_filepath = context.scene.world.plasma_age.age_name |
|
if not blend_filepath: |
|
blend_filepath = "Korman" |
|
self.filepath = str(Path(blend_filepath).with_suffix(".age")) |
|
context.window_manager.fileselect_add(self) |
|
return {"RUNNING_MODAL"} |
|
|
|
@classmethod |
|
def register(cls): |
|
# BEGIN MAJICK |
|
# Register the exporter properties such that they will persist |
|
for name, (prop, options) in cls._properties.items(): |
|
# Hide these settings from being seen on the age properties |
|
age_options = dict(options) |
|
bl_options = age_options.setdefault("options", set()) |
|
bl_options.add("HIDDEN") |
|
|
|
# Now do the majick |
|
setattr(PlasmaAge, name, prop(**age_options)) |
|
|
|
|
|
class _UiHelper: |
|
"""This fun little helper makes sure that we don't wreck the UI""" |
|
def __init__(self, context): |
|
self.active_object = context.active_object |
|
self.selected_objects = context.selected_objects |
|
|
|
def __enter__(self): |
|
scene = bpy.context.scene |
|
self.layers = tuple(scene.layers) |
|
self.frame_num = scene.frame_current |
|
scene.frame_set(scene.frame_start) |
|
scene.update() |
|
|
|
# Some operators require there be an active_object even though they |
|
# don't actually use it... |
|
if scene.objects.active is None: |
|
scene.objects.active = scene.objects[0] |
|
return self |
|
|
|
def __exit__(self, type, value, traceback): |
|
for i in bpy.data.objects: |
|
i.select = (i in self.selected_objects) |
|
|
|
scene = bpy.context.scene |
|
scene.objects.active = self.active_object |
|
scene.layers = self.layers |
|
scene.frame_set(self.frame_num) |
|
scene.update() |
|
|
|
|
|
# Add the export operator to the Export menu :) |
|
def menu_cb(self, context): |
|
if context.scene.render.engine == "PLASMA_GAME": |
|
self.layout.operator_context = "INVOKE_DEFAULT" |
|
self.layout.operator(ExportOperator.bl_idname, text="Plasma Age (.age)") |
|
|
|
|
|
def register(): |
|
bpy.types.INFO_MT_file_export.append(menu_cb) |
|
|
|
def unregister(): |
|
bpy.types.INFO_MT_file_export.remove(menu_cb)
|
|
|