diff options
author | Nick Samarin <nicks1987@bigmir.net> | 2011-05-17 00:30:59 +0400 |
---|---|---|
committer | Nick Samarin <nicks1987@bigmir.net> | 2011-05-17 00:30:59 +0400 |
commit | a918040902bdeb7c9793168710871e4a3b7777a3 (patch) | |
tree | 7380f00bce5448d777d09f4be4d7127e8eecec49 /release/scripts | |
parent | daeca2f8262884c436c5678225704b594ce5347b (diff) | |
parent | 99ee18c684da65ba774175c0b57a086e8222464a (diff) |
synched with trunk at revision 36569
Diffstat (limited to 'release/scripts')
108 files changed, 4299 insertions, 7876 deletions
diff --git a/release/scripts/io/netrender/__init__.py b/release/scripts/io/netrender/__init__.py deleted file mode 100644 index 6c4ed4119fc..00000000000 --- a/release/scripts/io/netrender/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# This directory is a Python package. - -# To support reload properly, try to access a package var, if it's there, reload everything -if "init_data" in locals(): - import imp - imp.reload(model) - imp.reload(operators) - imp.reload(client) - imp.reload(slave) - imp.reload(master) - imp.reload(master_html) - imp.reload(utils) - imp.reload(balancing) - imp.reload(ui) - imp.reload(repath) - imp.reload(versioning) -else: - from netrender import model - from netrender import operators - from netrender import client - from netrender import slave - from netrender import master - from netrender import master_html - from netrender import utils - from netrender import balancing - from netrender import ui - from netrender import repath - from netrender import versioning - -jobs = [] -slaves = [] -blacklist = [] - -init_file = "" -valid_address = False -init_data = True - - -def register(): - ui.addProperties() - - import bpy - scene = bpy.context.scene - if scene: - netsettings = scene.network_render - ui.init_data(netsettings) - - -def unregister(): - import bpy - del bpy.types.Scene.network_render diff --git a/release/scripts/io/netrender/balancing.py b/release/scripts/io/netrender/balancing.py deleted file mode 100644 index dde3ad53084..00000000000 --- a/release/scripts/io/netrender/balancing.py +++ /dev/null @@ -1,195 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import time - -from netrender.utils import * -import netrender.model - -class RatingRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def rate(self, job): - return 0 - -class ExclusionRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def test(self, job): - return False - -class PriorityRule: - def __init__(self): - self.enabled = True - - def id(self): - return str(id(self)) - - def test(self, job): - return False - -class Balancer: - def __init__(self): - self.rules = [] - self.priorities = [] - self.exceptions = [] - - def ruleByID(self, rule_id): - for rule in self.rules: - if rule.id() == rule_id: - return rule - for rule in self.priorities: - if rule.id() == rule_id: - return rule - for rule in self.exceptions: - if rule.id() == rule_id: - return rule - - return None - - def addRule(self, rule): - self.rules.append(rule) - - def addPriority(self, priority): - self.priorities.append(priority) - - def addException(self, exception): - self.exceptions.append(exception) - - def applyRules(self, job): - return sum((rule.rate(job) for rule in self.rules if rule.enabled)) - - def applyPriorities(self, job): - for priority in self.priorities: - if priority.enabled and priority.test(job): - return True # priorities are first - - return False - - def applyExceptions(self, job): - for exception in self.exceptions: - if exception.enabled and exception.test(job): - return True # exceptions are last - - return False - - def sortKey(self, job): - return (1 if self.applyExceptions(job) else 0, # exceptions after - 0 if self.applyPriorities(job) else 1, # priorities first - self.applyRules(job)) - - def balance(self, jobs): - if jobs: - # use inline copy to make sure the list is still accessible while sorting - jobs[:] = sorted(jobs, key=self.sortKey) - return jobs[0] - else: - return None - -# ========================== - -class RatingUsage(RatingRule): - def __str__(self): - return "Usage per job" - - def rate(self, job): - # less usage is better - return job.usage / job.priority - -class RatingUsageByCategory(RatingRule): - def __init__(self, get_jobs): - super().__init__() - self.getJobs = get_jobs - - def __str__(self): - return "Usage per category" - - def rate(self, job): - total_category_usage = sum([j.usage for j in self.getJobs() if j.category == job.category]) - maximum_priority = max([j.priority for j in self.getJobs() if j.category == job.category]) - - # less usage is better - return total_category_usage / maximum_priority - -class NewJobPriority(PriorityRule): - def __init__(self, limit = 1): - super().__init__() - self.limit = limit - - def setLimit(self, value): - self.limit = int(value) - - def str_limit(self): - return "less than %i frame%s done" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to new jobs" - - def test(self, job): - return job.countFrames(status = DONE) < self.limit - -class MinimumTimeBetweenDispatchPriority(PriorityRule): - def __init__(self, limit = 10): - super().__init__() - self.limit = limit - - def setLimit(self, value): - self.limit = int(value) - - def str_limit(self): - return "more than %i minute%s since last" % (self.limit, "s" if self.limit > 1 else "") - - def __str__(self): - return "Priority to jobs that haven't been dispatched recently" - - def test(self, job): - return job.countFrames(status = DISPATCHED) == 0 and (time.time() - job.last_dispatched) / 60 > self.limit - -class ExcludeQueuedEmptyJob(ExclusionRule): - def __str__(self): - return "Exclude non queued or empty jobs" - - def test(self, job): - return job.status != JOB_QUEUED or job.countFrames(status = QUEUED) == 0 - -class ExcludeSlavesLimit(ExclusionRule): - def __init__(self, count_jobs, count_slaves, limit = 0.75): - super().__init__() - self.count_jobs = count_jobs - self.count_slaves = count_slaves - self.limit = limit - - def setLimit(self, value): - self.limit = float(value) - - def str_limit(self): - return "more than %.0f%% of all slaves" % (self.limit * 100) - - def __str__(self): - return "Exclude jobs that would use too many slaves" - - def test(self, job): - return not ( self.count_jobs() == 1 or self.count_slaves() <= 1 or float(job.countSlaves() + 1) / self.count_slaves() <= self.limit ) diff --git a/release/scripts/io/netrender/client.py b/release/scripts/io/netrender/client.py deleted file mode 100644 index bc43b8cfbb7..00000000000 --- a/release/scripts/io/netrender/client.py +++ /dev/null @@ -1,372 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import bpy -import sys, os, re -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib -import json - -import netrender -import netrender.model -import netrender.slave as slave -import netrender.master as master -from netrender.utils import * - -def addFluidFiles(job, path): - if os.path.exists(path): - pattern = re.compile("fluidsurface_(final|preview)_([0-9]+)\.(bobj|bvel)\.gz") - - for fluid_file in sorted(os.listdir(path)): - match = pattern.match(fluid_file) - - if match: - # fluid frames starts at 0, which explains the +1 - # This is stupid - current_frame = int(match.groups()[1]) + 1 - job.addFile(path + fluid_file, current_frame, current_frame) - -def addPointCache(job, ob, point_cache, default_path): - if not point_cache.use_disk_cache: - return - - - name = point_cache.name - if name == "": - name = "".join(["%02X" % ord(c) for c in ob.name]) - - cache_path = bpy.path.abspath(point_cache.filepath) if point_cache.use_external else default_path - - index = "%02i" % point_cache.index - - if os.path.exists(cache_path): - pattern = re.compile(name + "_([0-9]+)_" + index + "\.bphys") - - cache_files = [] - - for cache_file in sorted(os.listdir(cache_path)): - match = pattern.match(cache_file) - - if match: - cache_frame = int(match.groups()[0]) - cache_files.append((cache_frame, cache_file)) - - cache_files.sort() - - if len(cache_files) == 1: - cache_frame, cache_file = cache_files[0] - job.addFile(cache_path + cache_file, cache_frame, cache_frame) - else: - for i in range(len(cache_files)): - current_item = cache_files[i] - next_item = cache_files[i+1] if i + 1 < len(cache_files) else None - previous_item = cache_files[i - 1] if i > 0 else None - - current_frame, current_file = current_item - - if not next_item and not previous_item: - job.addFile(cache_path + current_file, current_frame, current_frame) - elif next_item and not previous_item: - next_frame = next_item[0] - job.addFile(cache_path + current_file, current_frame, next_frame - 1) - elif not next_item and previous_item: - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, current_frame) - else: - next_frame = next_item[0] - previous_frame = previous_item[0] - job.addFile(cache_path + current_file, previous_frame + 1, next_frame - 1) - -def fillCommonJobSettings(job, job_name, netsettings): - job.name = job_name - job.category = netsettings.job_category - - for slave in netrender.blacklist: - job.blacklist.append(slave.id) - - job.chunks = netsettings.chunks - job.priority = netsettings.priority - - if netsettings.job_type == "JOB_BLENDER": - job.type = netrender.model.JOB_BLENDER - elif netsettings.job_type == "JOB_PROCESS": - job.type = netrender.model.JOB_PROCESS - elif netsettings.job_type == "JOB_VCS": - job.type = netrender.model.JOB_VCS - -def clientSendJob(conn, scene, anim = False): - netsettings = scene.network_render - if netsettings.job_type == "JOB_BLENDER": - return clientSendJobBlender(conn, scene, anim) - elif netsettings.job_type == "JOB_VCS": - return clientSendJobVCS(conn, scene, anim) - -def clientSendJobVCS(conn, scene, anim = False): - netsettings = scene.network_render - job = netrender.model.RenderJob() - - if anim: - for f in range(scene.frame_start, scene.frame_end + 1): - job.addFrame(f) - else: - job.addFrame(scene.frame_current) - - filename = bpy.data.filepath - - if not filename.startswith(netsettings.vcs_wpath): - # this is an error, need better way to handle this - return - - filename = filename[len(netsettings.vcs_wpath):] - - if filename[0] in (os.sep, os.altsep): - filename = filename[1:] - - print("CREATING VCS JOB", filename) - - job.addFile(filename, signed=False) - - job_name = netsettings.job_name - path, name = os.path.split(filename) - if job_name == "[default]": - job_name = name - - - fillCommonJobSettings(job, job_name, netsettings) - - # VCS Specific code - job.version_info = netrender.model.VersioningInfo() - job.version_info.system = netsettings.vcs_system - job.version_info.wpath = netsettings.vcs_wpath - job.version_info.rpath = netsettings.vcs_rpath - job.version_info.revision = netsettings.vcs_revision - - # try to send path first - conn.request("POST", "/job", json.dumps(job.serialize())) - response = conn.getresponse() - response.read() - - job_id = response.getheader("job-id") - - # a VCS job is always good right now, need error handling - - return job_id - -def clientSendJobBlender(conn, scene, anim = False): - netsettings = scene.network_render - job = netrender.model.RenderJob() - - if anim: - for f in range(scene.frame_start, scene.frame_end + 1): - job.addFrame(f) - else: - job.addFrame(scene.frame_current) - - filename = bpy.data.filepath - job.addFile(filename) - - job_name = netsettings.job_name - path, name = os.path.split(filename) - if job_name == "[default]": - job_name = name - - ########################### - # LIBRARIES - ########################### - for lib in bpy.data.libraries: - file_path = bpy.path.abspath(lib.filepath) - if os.path.exists(file_path): - job.addFile(file_path) - - ########################### - # IMAGES - ########################### - for image in bpy.data.images: - if image.source == "FILE" and not image.packed_file: - file_path = bpy.path.abspath(image.filepath) - if os.path.exists(file_path): - job.addFile(file_path) - - tex_path = os.path.splitext(file_path)[0] + ".tex" - if os.path.exists(tex_path): - job.addFile(tex_path) - - ########################### - # FLUID + POINT CACHE - ########################### - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - addFluidFiles(job, bpy.path.abspath(modifier.settings.filepath)) - elif modifier.type == "CLOTH": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SOFT_BODY": - addPointCache(job, object, modifier.point_cache, default_path) - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - addPointCache(job, object, modifier.domain_settings.point_cache, default_path) - elif modifier.type == "MULTIRES" and modifier.is_external: - file_path = bpy.path.abspath(modifier.filepath) - job.addFile(file_path) - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - addPointCache(job, object, psys.point_cache, default_path) - - #print(job.files) - - fillCommonJobSettings(job, job_name, netsettings) - - # try to send path first - conn.request("POST", "/job", json.dumps(job.serialize())) - response = conn.getresponse() - response.read() - - job_id = response.getheader("job-id") - - # if not ACCEPTED (but not processed), send files - if response.status == http.client.ACCEPTED: - for rfile in job.files: - f = open(rfile.filepath, "rb") - conn.request("PUT", fileURL(job_id, rfile.index), f) - f.close() - response = conn.getresponse() - response.read() - - # server will reply with ACCEPTED until all files are found - - return job_id - -def requestResult(conn, job_id, frame): - conn.request("GET", renderURL(job_id, frame)) - -class NetworkRenderEngine(bpy.types.RenderEngine): - bl_idname = 'NET_RENDER' - bl_label = "Network Render" - bl_use_postprocess = False - def render(self, scene): - if scene.network_render.mode == "RENDER_CLIENT": - self.render_client(scene) - elif scene.network_render.mode == "RENDER_SLAVE": - self.render_slave(scene) - elif scene.network_render.mode == "RENDER_MASTER": - self.render_master(scene) - else: - print("UNKNOWN OPERATION MODE") - - def render_master(self, scene): - netsettings = scene.network_render - - address = "" if netsettings.server_address == "[default]" else netsettings.server_address - - master.runMaster((address, netsettings.server_port), netsettings.use_master_broadcast, netsettings.use_master_clear, bpy.path.abspath(netsettings.path), self.update_stats, self.test_break) - - - def render_slave(self, scene): - slave.render_slave(self, scene.network_render, scene.render.threads) - - def render_client(self, scene): - netsettings = scene.network_render - self.update_stats("", "Network render client initiation") - - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn: - # Sending file - - self.update_stats("", "Network render exporting") - - new_job = False - - job_id = netsettings.job_id - - # reading back result - - self.update_stats("", "Network render waiting for results") - - - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - if response.status == http.client.NO_CONTENT: - new_job = True - netsettings.job_id = clientSendJob(conn, scene) - job_id = netsettings.job_id - - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - while response.status == http.client.ACCEPTED and not self.test_break(): - time.sleep(1) - requestResult(conn, job_id, scene.frame_current) - response = conn.getresponse() - buf = response.read() - - # cancel new jobs (animate on network) on break - if self.test_break() and new_job: - conn.request("POST", cancelURL(job_id)) - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - netsettings.job_id = 0 - - if response.status != http.client.OK: - conn.close() - return - - r = scene.render - x= int(r.resolution_x*r.resolution_percentage*0.01) - y= int(r.resolution_y*r.resolution_percentage*0.01) - - result_path = os.path.join(bpy.path.abspath(netsettings.path), "output.exr") - - folder = os.path.split(result_path)[0] - - if not os.path.exists(folder): - os.mkdir(folder) - - f = open(result_path, "wb") - - f.write(buf) - - f.close() - - result = self.begin_result(0, 0, x, y) - result.load_from_file(result_path) - self.end_result(result) - - conn.close() - -def compatible(module): - module = __import__(module) - for subclass in module.__dict__.values(): - try: subclass.COMPAT_ENGINES.add('NET_RENDER') - except: pass - del module - -compatible("properties_world") -compatible("properties_material") -compatible("properties_data_mesh") -compatible("properties_data_camera") -compatible("properties_texture") diff --git a/release/scripts/io/netrender/master.py b/release/scripts/io/netrender/master.py deleted file mode 100644 index 793e3bb51bf..00000000000 --- a/release/scripts/io/netrender/master.py +++ /dev/null @@ -1,1079 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os -import http, http.client, http.server, urllib, socket, socketserver, threading -import subprocess, shutil, time, hashlib -import pickle -import select # for select.error -import json - -from netrender.utils import * -import netrender.model -import netrender.balancing -import netrender.master_html -import netrender.thumbnail as thumbnail - -class MRenderFile(netrender.model.RenderFile): - def __init__(self, filepath, index, start, end, signature): - super().__init__(filepath, index, start, end, signature) - self.found = False - - def test(self): - self.found = os.path.exists(self.filepath) - if self.found and self.signature != None: - found_signature = hashFile(self.filepath) - self.found = self.signature == found_signature - - return self.found - - -class MRenderSlave(netrender.model.RenderSlave): - def __init__(self, name, address, stats): - super().__init__() - self.id = hashlib.md5(bytes(repr(name) + repr(address), encoding='utf8')).hexdigest() - self.name = name - self.address = address - self.stats = stats - self.last_seen = time.time() - - self.job = None - self.job_frames = [] - - netrender.model.RenderSlave._slave_map[self.id] = self - - def seen(self): - self.last_seen = time.time() - - def finishedFrame(self, frame_number): - self.job_frames.remove(frame_number) - if not self.job_frames: - self.job = None - -class MRenderJob(netrender.model.RenderJob): - def __init__(self, job_id, job_info): - super().__init__(job_info) - self.id = job_id - self.last_dispatched = time.time() - - # force one chunk for process jobs - if self.type == netrender.model.JOB_PROCESS: - self.chunks = 1 - - # Force WAITING status on creation - self.status = JOB_WAITING - - # special server properties - self.last_update = 0 - self.save_path = "" - self.files = [MRenderFile(rfile.filepath, rfile.index, rfile.start, rfile.end, rfile.signature) for rfile in job_info.files] - - def initInfo(self): - if not self.resolution: - self.resolution = tuple(getFileInfo(self.files[0].filepath, ["bpy.context.scene.render.resolution_x", "bpy.context.scene.render.resolution_y", "bpy.context.scene.render.resolution_percentage"])) - - def save(self): - if self.save_path: - f = open(os.path.join(self.save_path, "job.txt"), "w") - f.write(json.dumps(self.serialize())) - f.close() - - def edit(self, info_map): - if "status" in info_map: - self.status = info_map["status"] - - if "priority" in info_map: - self.priority = info_map["priority"] - - if "chunks" in info_map: - self.chunks = info_map["chunks"] - - def testStart(self): - # Don't test files for versionned jobs - if not self.version_info: - for f in self.files: - if not f.test(): - return False - - self.start() - self.initInfo() - return True - - def testFinished(self): - for f in self.frames: - if f.status == QUEUED or f.status == DISPATCHED: - break - else: - self.status = JOB_FINISHED - - def pause(self, status = None): - if self.status not in {JOB_PAUSED, JOB_QUEUED}: - return - - if status is None: - self.status = JOB_PAUSED if self.status == JOB_QUEUED else JOB_QUEUED - elif status: - self.status = JOB_QUEUED - else: - self.status = JOB_PAUSED - - def start(self): - self.status = JOB_QUEUED - - def addLog(self, frames): - log_name = "_".join(("%06d" % f for f in frames)) + ".log" - log_path = os.path.join(self.save_path, log_name) - - for number in frames: - frame = self[number] - if frame: - frame.log_path = log_path - - def addFrame(self, frame_number, command): - frame = MRenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def reset(self, all): - for f in self.frames: - f.reset(all) - - def getFrames(self): - frames = [] - for f in self.frames: - if f.status == QUEUED: - self.last_dispatched = time.time() - frames.append(f) - if len(frames) >= self.chunks: - break - - return frames - -class MRenderFrame(netrender.model.RenderFrame): - def __init__(self, frame, command): - super().__init__() - self.number = frame - self.slave = None - self.time = 0 - self.status = QUEUED - self.command = command - - self.log_path = None - - def reset(self, all): - if all or self.status == ERROR: - self.log_path = None - self.slave = None - self.time = 0 - self.status = QUEUED - - -# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -# -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= -# =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- -file_pattern = re.compile("/file_([a-zA-Z0-9]+)_([0-9]+)") -render_pattern = re.compile("/render_([a-zA-Z0-9]+)_([0-9]+).exr") -thumb_pattern = re.compile("/thumb_([a-zA-Z0-9]+)_([0-9]+).jpg") -log_pattern = re.compile("/log_([a-zA-Z0-9]+)_([0-9]+).log") -reset_pattern = re.compile("/reset(all|)_([a-zA-Z0-9]+)_([0-9]+)") -cancel_pattern = re.compile("/cancel_([a-zA-Z0-9]+)") -pause_pattern = re.compile("/pause_([a-zA-Z0-9]+)") -edit_pattern = re.compile("/edit_([a-zA-Z0-9]+)") - -class RenderHandler(http.server.BaseHTTPRequestHandler): - def log_message(self, format, *args): - # override because the original calls self.address_string(), which - # is extremely slow due to some timeout.. - sys.stderr.write("[%s] %s\n" % (self.log_date_time_string(), format%args)) - - def getInfoMap(self): - length = int(self.headers['content-length']) - - if length > 0: - msg = str(self.rfile.read(length), encoding='utf8') - return json.loads(msg) - else: - return {} - - def send_head(self, code = http.client.OK, headers = {}, content = "application/octet-stream"): - self.send_response(code) - self.send_header("Content-type", content) - - for key, value in headers.items(): - self.send_header(key, value) - - self.end_headers() - - def do_HEAD(self): - - if self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - job = self.server.getJobID(job_id) - if job: - frame = job[job_frame] - - - if frame: - self.send_head(http.client.OK) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - def do_GET(self): - - if self.path == "/version": - self.send_head() - self.server.stats("", "Version check") - self.wfile.write(VERSION) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/render"): - match = render_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - self.server.stats("", "Sending result to client") - - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) - - f = open(filename, 'rb') - self.send_head(content = "image/x-exr") - shutil.copyfileobj(f, self.wfile) - f.close() - elif frame.status == ERROR: - self.send_head(http.client.PARTIAL_CONTENT) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/thumb"): - match = thumb_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.ACCEPTED) - elif frame.status == DONE: - filename = os.path.join(job.save_path, "%06d.exr" % frame_number) - - thumbname = thumbnail.generate(filename) - - if thumbname: - f = open(thumbname, 'rb') - self.send_head(content = "image/jpeg") - shutil.copyfileobj(f, self.wfile) - f.close() - else: # thumbnail couldn't be generated - self.send_head(http.client.PARTIAL_CONTENT) - return - elif frame.status == ERROR: - self.send_head(http.client.PARTIAL_CONTENT) - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - frame_number = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - frame = job[frame_number] - - if frame: - if not frame.log_path or frame.status in (QUEUED, DISPATCHED): - self.send_head(http.client.PROCESSING) - else: - self.server.stats("", "Sending log to client") - f = open(frame.log_path, 'rb') - - self.send_head(content = "text/plain") - - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid URL - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/status": - job_id = self.headers.get('job-id', "") - job_frame = int(self.headers.get('job-frame', -1)) - - if job_id: - - job = self.server.getJobID(job_id) - if job: - if job_frame != -1: - frame = job[frame] - - if frame: - message = frame.serialize() - else: - # no such frame - self.send_heat(http.client.NO_CONTENT) - return - else: - message = job.serialize() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - return - else: # status of all jobs - message = [] - - for job in self.server: - message.append(job.serialize()) - - - self.server.stats("", "Sending status") - self.send_head() - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/job": - self.server.balance() - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job, frames = self.server.newDispatch(slave_id) - - if job and frames: - for f in frames: - print("dispatch", f.number) - f.status = DISPATCHED - f.slave = slave - - slave.job = job - slave.job_frames = [f.number for f in frames] - - self.send_head(headers={"job-id": job.id}) - - message = job.serialize(frames) - - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - - self.server.stats("", "Sending job to slave") - else: - # no job available, return error code - slave.job = None - slave.job_frames = [] - - self.send_head(http.client.ACCEPTED) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - slave_id = self.headers['slave-id'] - slave = self.server.getSeenSlave(slave_id) - - if not slave: - # invalid slave id - print("invalid slave id") - - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - render_file = job.files[file_index] - - if render_file: - self.server.stats("", "Sending file to slave") - f = open(render_file.filepath, 'rb') - - self.send_head() - shutil.copyfileobj(f, self.wfile) - - f.close() - else: - # no such file - self.send_head(http.client.NO_CONTENT) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slaves": - message = [] - - self.server.stats("", "Sending slaves status") - - for slave in self.server.slaves: - message.append(slave.serialize()) - - self.send_head() - - self.wfile.write(bytes(json.dumps(message), encoding='utf8')) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - else: - # hand over the rest to the html section - netrender.master_html.get(self) - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_POST(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path == "/job": - - length = int(self.headers['content-length']) - - job_info = netrender.model.RenderJob.materialize(json.loads(str(self.rfile.read(length), encoding='utf8'))) - - job_id = self.server.nextJobID() - - job = MRenderJob(job_id, job_info) - - for frame in job_info.frames: - frame = job.addFrame(frame.number, frame.command) - - self.server.addJob(job) - - headers={"job-id": job_id} - - if job.testStart(): - self.server.stats("", "New job, started") - self.send_head(headers=headers) - else: - self.server.stats("", "New job, missing files (%i total)" % len(job.files)) - self.send_head(http.client.ACCEPTED, headers=headers) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/edit"): - match = edit_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - info_map = self.getInfoMap() - - job.edit(info_map) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/balance_limit": - info_map = self.getInfoMap() - for rule_id, limit in info_map.items(): - try: - rule = self.server.balancer.ruleByID(rule_id) - if rule: - rule.setLimit(limit) - except: - pass # invalid type - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/balance_enable": - info_map = self.getInfoMap() - for rule_id, enabled in info_map.items(): - rule = self.server.balancer.ruleByID(rule_id) - if rule: - rule.enabled = enabled - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/cancel"): - match = cancel_pattern.match(self.path) - - if match: - info_map = self.getInfoMap() - clear = info_map.get("clear", False) - - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - self.server.stats("", "Cancelling job") - self.server.removeJob(job, clear) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/pause"): - match = pause_pattern.match(self.path) - - if match: - info_map = self.getInfoMap() - status = info_map.get("status", None) - - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - self.server.stats("", "Pausing job") - job.pause(status) - self.send_head() - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: - # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/clear": - # cancel all jobs - info_map = self.getInfoMap() - clear = info_map.get("clear", False) - - self.server.stats("", "Clearing jobs") - self.server.clear(clear) - - self.send_head() - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/reset"): - match = reset_pattern.match(self.path) - - if match: - all = match.groups()[0] == 'all' - job_id = match.groups()[1] - job_frame = int(match.groups()[2]) - - job = self.server.getJobID(job_id) - - if job: - if job_frame != 0: - - frame = job[job_frame] - if frame: - self.server.stats("", "Reset job frame") - frame.reset(all) - self.send_head() - else: - # no such frame - self.send_head(http.client.NO_CONTENT) - - else: - self.server.stats("", "Reset job") - job.reset(all) - self.send_head() - - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/slave": - length = int(self.headers['content-length']) - job_frame_string = self.headers['job-frame'] - - self.server.stats("", "New slave connected") - - slave_info = netrender.model.RenderSlave.materialize(json.loads(str(self.rfile.read(length), encoding='utf8')), cache = False) - - slave_id = self.server.addSlave(slave_info.name, self.client_address, slave_info.stats) - - self.send_head(headers = {"slave-id": slave_id}) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/log": - length = int(self.headers['content-length']) - - log_info = netrender.model.LogFile.materialize(json.loads(str(self.rfile.read(length), encoding='utf8'))) - - slave_id = log_info.slave_id - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job = self.server.getJobID(log_info.job_id) - - if job: - self.server.stats("", "Log announcement") - job.addLog(log_info.frames) - self.send_head(http.client.OK) - else: - # no such job id - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - # -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - def do_PUT(self): - - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - if self.path.startswith("/file"): - match = file_pattern.match(self.path) - - if match: - self.server.stats("", "Receiving job") - - length = int(self.headers['content-length']) - job_id = match.groups()[0] - file_index = int(match.groups()[1]) - - job = self.server.getJobID(job_id) - - if job: - - render_file = job.files[file_index] - - if render_file: - main_file = job.files[0].filepath # filename of the first file - - main_path, main_name = os.path.split(main_file) - - if file_index > 0: - file_path = prefixPath(job.save_path, render_file.filepath, main_path) - else: - file_path = os.path.join(job.save_path, main_name) - - buf = self.rfile.read(length) - - # add same temp file + renames as slave - - f = open(file_path, "wb") - f.write(buf) - f.close() - del buf - - render_file.filepath = file_path # set the new path - - if job.testStart(): - self.server.stats("", "File upload, starting job") - self.send_head(http.client.OK) - else: - self.server.stats("", "File upload, file missings") - self.send_head(http.client.ACCEPTED) - else: # invalid file - print("file not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # job not found - print("job not found", job_id, file_index) - self.send_head(http.client.NO_CONTENT) - else: # invalid url - print("no match") - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/render": - self.server.stats("", "Receiving render result") - - # need some message content here or the slave doesn't like it - self.wfile.write(bytes("foo", encoding='utf8')) - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job_id = self.headers['job-id'] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(self.headers['job-frame']) - job_result = int(self.headers['job-result']) - job_time = float(self.headers['job-time']) - - frame = job[job_frame] - - if frame: - if job.hasRenderResult(): - if job_result == DONE: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(os.path.join(job.save_path, "%06d.exr" % job_frame), 'wb') - f.write(buf) - f.close() - - del buf - elif job_result == ERROR: - # blacklist slave on this job on error - # slaves might already be in blacklist if errors on the whole chunk - if not slave.id in job.blacklist: - job.blacklist.append(slave.id) - - slave.finishedFrame(job_frame) - - frame.status = job_result - frame.time = job_time - - job.testFinished() - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path == "/thumb": - self.server.stats("", "Receiving thumbnail result") - - # need some message content here or the slave doesn't like it - self.wfile.write(bytes("foo", encoding='utf8')) - - slave_id = self.headers['slave-id'] - - slave = self.server.getSeenSlave(slave_id) - - if slave: # only if slave id is valid - job_id = self.headers['job-id'] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(self.headers['job-frame']) - - frame = job[job_frame] - - if frame: - if job.hasRenderResult(): - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(os.path.join(job.save_path, "%06d.jpg" % job_frame), 'wb') - f.write(buf) - f.close() - - del buf - - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid slave id - self.send_head(http.client.NO_CONTENT) - # =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - elif self.path.startswith("/log"): - self.server.stats("", "Receiving log file") - - match = log_pattern.match(self.path) - - if match: - job_id = match.groups()[0] - - job = self.server.getJobID(job_id) - - if job: - job_frame = int(match.groups()[1]) - - frame = job[job_frame] - - if frame and frame.log_path: - length = int(self.headers['content-length']) - buf = self.rfile.read(length) - f = open(frame.log_path, 'ab') - f.write(buf) - f.close() - - del buf - - self.server.getSeenSlave(self.headers['slave-id']) - - self.send_head() - else: # frame not found - self.send_head(http.client.NO_CONTENT) - else: # job not found - self.send_head(http.client.NO_CONTENT) - else: # invalid url - self.send_head(http.client.NO_CONTENT) - -class RenderMasterServer(socketserver.ThreadingMixIn, http.server.HTTPServer): - def __init__(self, address, handler_class, path, subdir=True): - super().__init__(address, handler_class) - self.jobs = [] - self.jobs_map = {} - self.slaves = [] - self.slaves_map = {} - self.job_id = 0 - - if subdir: - self.path = os.path.join(path, "master_" + str(os.getpid())) - else: - self.path = path - - self.slave_timeout = 5 # 5 mins: need a parameter for that - - self.balancer = netrender.balancing.Balancer() - self.balancer.addRule(netrender.balancing.RatingUsageByCategory(self.getJobs)) - self.balancer.addRule(netrender.balancing.RatingUsage()) - self.balancer.addException(netrender.balancing.ExcludeQueuedEmptyJob()) - self.balancer.addException(netrender.balancing.ExcludeSlavesLimit(self.countJobs, self.countSlaves, limit = 0.9)) - self.balancer.addPriority(netrender.balancing.NewJobPriority()) - self.balancer.addPriority(netrender.balancing.MinimumTimeBetweenDispatchPriority(limit = 2)) - - if not os.path.exists(self.path): - os.mkdir(self.path) - - def restore(self, jobs, slaves, balancer = None): - self.jobs = jobs - self.jobs_map = {} - - for job in self.jobs: - self.jobs_map[job.id] = job - self.job_id = max(self.job_id, int(job.id)) - - self.slaves = slaves - for slave in self.slaves: - self.slaves_map[slave.id] = slave - - if balancer: - self.balancer = balancer - - - def nextJobID(self): - self.job_id += 1 - return str(self.job_id) - - def addSlave(self, name, address, stats): - slave = MRenderSlave(name, address, stats) - self.slaves.append(slave) - self.slaves_map[slave.id] = slave - - return slave.id - - def removeSlave(self, slave): - self.slaves.remove(slave) - self.slaves_map.pop(slave.id) - - def getSlave(self, slave_id): - return self.slaves_map.get(slave_id) - - def getSeenSlave(self, slave_id): - slave = self.getSlave(slave_id) - if slave: - slave.seen() - - return slave - - def timeoutSlaves(self): - removed = [] - - t = time.time() - - for slave in self.slaves: - if (t - slave.last_seen) / 60 > self.slave_timeout: - removed.append(slave) - - if slave.job: - for f in slave.job_frames: - slave.job[f].status = ERROR - - for slave in removed: - self.removeSlave(slave) - - def updateUsage(self): - blend = 0.5 - for job in self.jobs: - job.usage *= (1 - blend) - - if self.slaves: - slave_usage = blend / self.countSlaves() - - for slave in self.slaves: - if slave.job: - slave.job.usage += slave_usage - - - def clear(self, clear_files = False): - removed = self.jobs[:] - - for job in removed: - self.removeJob(job, clear_files) - - def balance(self): - self.balancer.balance(self.jobs) - - def getJobs(self): - return self.jobs - - def countJobs(self, status = JOB_QUEUED): - total = 0 - for j in self.jobs: - if j.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(self.slaves) - - def removeJob(self, job, clear_files = False): - self.jobs.remove(job) - self.jobs_map.pop(job.id) - - if clear_files: - shutil.rmtree(job.save_path) - - for slave in self.slaves: - if slave.job == job: - slave.job = None - slave.job_frames = [] - - def addJob(self, job): - self.jobs.append(job) - self.jobs_map[job.id] = job - - # create job directory - job.save_path = os.path.join(self.path, "job_" + job.id) - if not os.path.exists(job.save_path): - os.mkdir(job.save_path) - - job.save() - - def getJobID(self, id): - return self.jobs_map.get(id) - - def __iter__(self): - for job in self.jobs: - yield job - - def newDispatch(self, slave_id): - if self.jobs: - for job in self.jobs: - if not self.balancer.applyExceptions(job) and slave_id not in job.blacklist: - return job, job.getFrames() - - return None, None - -def clearMaster(path): - shutil.rmtree(path) - -def createMaster(address, clear, path): - filepath = os.path.join(path, "blender_master.data") - - if not clear and os.path.exists(filepath): - print("loading saved master:", filepath) - with open(filepath, 'rb') as f: - path, jobs, slaves = pickle.load(f) - - httpd = RenderMasterServer(address, RenderHandler, path, subdir=False) - httpd.restore(jobs, slaves) - - return httpd - - return RenderMasterServer(address, RenderHandler, path) - -def saveMaster(path, httpd): - filepath = os.path.join(path, "blender_master.data") - - with open(filepath, 'wb') as f: - pickle.dump((httpd.path, httpd.jobs, httpd.slaves), f, pickle.HIGHEST_PROTOCOL) - -def runMaster(address, broadcast, clear, path, update_stats, test_break): - httpd = createMaster(address, clear, path) - httpd.timeout = 1 - httpd.stats = update_stats - - if broadcast: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - - start_time = time.time() - 2 - - while not test_break(): - try: - httpd.handle_request() - except select.error: - pass - - if time.time() - start_time >= 2: # need constant here - httpd.timeoutSlaves() - - httpd.updateUsage() - - if broadcast: - print("broadcasting address") - s.sendto(bytes("%i" % address[1], encoding='utf8'), 0, ('<broadcast>', 8000)) - start_time = time.time() - - httpd.server_close() - if clear: - clearMaster(httpd.path) - else: - saveMaster(path, httpd) - diff --git a/release/scripts/io/netrender/master_html.py b/release/scripts/io/netrender/master_html.py deleted file mode 100644 index 877273207a8..00000000000 --- a/release/scripts/io/netrender/master_html.py +++ /dev/null @@ -1,315 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import os -import re -import shutil -from netrender.utils import * -import netrender.model - -src_folder = os.path.split(__file__)[0] - -def get(handler): - def output(text): - handler.wfile.write(bytes(text, encoding='utf8')) - - def head(title, refresh = False): - output("<html><head>") - if refresh: - output("<meta http-equiv='refresh' content=5>") - output("<script src='/html/netrender.js' type='text/javascript'></script>") -# output("<script src='/html/json2.js' type='text/javascript'></script>") - output("<title>") - output(title) - output("</title></head><body>") - output("<link rel='stylesheet' href='/html/netrender.css' type='text/css'>") - - - def link(text, url, script=""): - return "<a href='%s' %s>%s</a>" % (url, script, text) - - def tag(name, text, attr=""): - return "<%s %s>%s</%s>" % (name, attr, text, name) - - def startTable(border=1, class_style = None, caption = None): - output("<table border='%i'" % border) - - if class_style: - output(" class='%s'" % class_style) - - output(">") - - if caption: - output("<caption>%s</caption>" % caption) - - def headerTable(*headers): - output("<thead><tr>") - - for c in headers: - output("<td>" + c + "</td>") - - output("</tr></thead>") - - def rowTable(*data, id = None, class_style = None, extra = None): - output("<tr") - - if id: - output(" id='%s'" % id) - - if class_style: - output(" class='%s'" % class_style) - - if extra: - output(" %s" % extra) - - output(">") - - for c in data: - output("<td>" + str(c) + "</td>") - - output("</tr>") - - def endTable(): - output("</table>") - - def checkbox(title, value, script=""): - return """<input type="checkbox" title="%s" %s %s>""" % (title, "checked" if value else "", ("onclick=\"%s\"" % script) if script else "") - - if handler.path == "/html/netrender.js": - f = open(os.path.join(src_folder, "netrender.js"), 'rb') - - handler.send_head(content = "text/javascript") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html/netrender.css": - f = open(os.path.join(src_folder, "netrender.css"), 'rb') - - handler.send_head(content = "text/css") - shutil.copyfileobj(f, handler.wfile) - - f.close() - elif handler.path == "/html" or handler.path == "/": - handler.send_head(content = "text/html") - head("NetRender", refresh = True) - - output("<h2>Jobs</h2>") - - startTable() - headerTable( - " ", - "id", - "name", - "category", - "type", - "chunks", - "priority", - "usage", - "wait", - "status", - "length", - "done", - "dispatched", - "error", - "priority", - "exception" - ) - - handler.server.balance() - - for job in handler.server.jobs: - results = job.framesStatus() - rowTable( - """<button title="cancel job" onclick="cancel_job('%s');">X</button>""" % job.id + - """<button title="pause job" onclick="request('/pause_%s', null);">P</button>""" % job.id + - """<button title="reset all frames" onclick="request('/resetall_%s_0', null);">R</button>""" % job.id, - job.id, - link(job.name, "/html/job" + job.id), - job.category if job.category else "<i>None</i>", - netrender.model.JOB_TYPES[job.type], - str(job.chunks) + - """<button title="increase chunks size" onclick="request('/edit_%s', "{'chunks': %i}");">+</button>""" % (job.id, job.chunks + 1) + - """<button title="decrease chunks size" onclick="request('/edit_%s', "{'chunks': %i}");" %s>-</button>""" % (job.id, job.chunks - 1, "disabled=True" if job.chunks == 1 else ""), - str(job.priority) + - """<button title="increase priority" onclick="request('/edit_%s', "{'priority': %i}");">+</button>""" % (job.id, job.priority + 1) + - """<button title="decrease priority" onclick="request('/edit_%s', "{'priority': %i}");" %s>-</button>""" % (job.id, job.priority - 1, "disabled=True" if job.priority == 1 else ""), - "%0.1f%%" % (job.usage * 100), - "%is" % int(time.time() - job.last_dispatched), - job.statusText(), - len(job), - results[DONE], - results[DISPATCHED], - str(results[ERROR]) + - """<button title="reset error frames" onclick="request('/reset_%s_0', null);" %s>R</button>""" % (job.id, "disabled=True" if not results[ERROR] else ""), - "yes" if handler.server.balancer.applyPriorities(job) else "no", - "yes" if handler.server.balancer.applyExceptions(job) else "no" - ) - - endTable() - - output("<h2>Slaves</h2>") - - startTable() - headerTable("name", "address", "last seen", "stats", "job") - - for slave in handler.server.slaves: - rowTable(slave.name, slave.address[0], time.ctime(slave.last_seen), slave.stats, link(slave.job.name, "/html/job" + slave.job.id) if slave.job else "None") - - endTable() - - output("<h2>Configuration</h2>") - - output("""<button title="remove all jobs" onclick="clear_jobs();">CLEAR JOB LIST</button>""") - - startTable(caption = "Rules", class_style = "rules") - - headerTable("type", "enabled", "description", "limit") - - for rule in handler.server.balancer.rules: - rowTable( - "rating", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.priorities: - rowTable( - "priority", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - for rule in handler.server.balancer.exceptions: - rowTable( - "exception", - checkbox("", rule.enabled, "balance_enable('%s', '%s')" % (rule.id(), str(not rule.enabled).lower())), - rule, - rule.str_limit() + - """<button title="edit limit" onclick="balance_edit('%s', '%s');">edit</button>""" % (rule.id(), str(rule.limit)) if hasattr(rule, "limit") else " " - ) - - endTable() - - output("</body></html>") - - elif handler.path.startswith("/html/job"): - handler.send_head(content = "text/html") - job_id = handler.path[9:] - - head("NetRender") - - job = handler.server.getJobID(job_id) - - if job: - output("<h2>Render Information</h2>") - - job.initInfo() - - startTable() - - rowTable("resolution", "%ix%i at %i%%" % job.resolution) - - endTable() - - - if job.type == netrender.model.JOB_BLENDER: - output("<h2>Files</h2>") - - startTable() - headerTable("path") - - tot_cache = 0 - tot_fluid = 0 - - rowTable(job.files[0].filepath) - rowTable("Other Files", class_style = "toggle", extra = "onclick='toggleDisplay(".other", "none", "table-row")'") - - for file in job.files: - if file.filepath.endswith(".bphys"): - tot_cache += 1 - elif file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - tot_fluid += 1 - else: - if file != job.files[0]: - rowTable(file.filepath, class_style = "other") - - if tot_cache > 0: - rowTable("%i physic cache files" % tot_cache, class_style = "toggle", extra = "onclick='toggleDisplay(".cache", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bphys"): - rowTable(os.path.split(file.filepath)[1], class_style = "cache") - - if tot_fluid > 0: - rowTable("%i fluid bake files" % tot_fluid, class_style = "toggle", extra = "onclick='toggleDisplay(".fluid", "none", "table-row")'") - for file in job.files: - if file.filepath.endswith(".bobj.gz") or file.filepath.endswith(".bvel.gz"): - rowTable(os.path.split(file.filepath)[1], class_style = "fluid") - - endTable() - elif job.type == netrender.model.JOB_VCS: - output("<h2>Versioning</h2>") - - startTable() - - rowTable("System", job.version_info.system.name) - rowTable("Remote Path", job.version_info.rpath) - rowTable("Working Path", job.version_info.wpath) - rowTable("Revision", job.version_info.revision) - rowTable("Render File", job.files[0].filepath) - - endTable() - - if job.blacklist: - output("<h2>Blacklist</h2>") - - startTable() - headerTable("name", "address") - - for slave_id in job.blacklist: - slave = handler.server.slaves_map[slave_id] - rowTable(slave.name, slave.address[0]) - - endTable() - - output("<h2>Frames</h2>") - - startTable() - headerTable("no", "status", "render time", "slave", "log", "result", "") - - for frame in job.frames: - rowTable( - frame.number, - frame.statusText(), - "%.1fs" % frame.time, - frame.slave.name if frame.slave else " ", - link("view log", logURL(job_id, frame.number)) if frame.log_path else " ", - link("view result", renderURL(job_id, frame.number)) + " [" + - tag("span", "show", attr="class='thumb' onclick='showThumb(%s, %i)'" % (job.id, frame.number)) + "]" if frame.status == DONE else " ", - "<img name='thumb%i' title='hide thumbnails' src='' class='thumb' onclick='showThumb(%s, %i)'>" % (frame.number, job.id, frame.number) - ) - - endTable() - else: - output("no such job") - - output("</body></html>") - diff --git a/release/scripts/io/netrender/model.py b/release/scripts/io/netrender/model.py deleted file mode 100644 index 5fc0bc2a0bb..00000000000 --- a/release/scripts/io/netrender/model.py +++ /dev/null @@ -1,360 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib - -import netrender.versioning as versioning -from netrender.utils import * - -class LogFile: - def __init__(self, job_id = 0, slave_id = 0, frames = []): - self.job_id = job_id - self.slave_id = slave_id - self.frames = frames - - def serialize(self): - return { - "job_id": self.job_id, - "slave_id": self.slave_id, - "frames": self.frames - } - - @staticmethod - def materialize(data): - if not data: - return None - - logfile = LogFile() - logfile.job_id = data["job_id"] - logfile.slave_id = data["slave_id"] - logfile.frames = data["frames"] - - return logfile - -class RenderSlave: - _slave_map = {} - - def __init__(self): - self.id = "" - self.name = "" - self.address = ("",0) - self.stats = "" - self.total_done = 0 - self.total_error = 0 - self.last_seen = 0.0 - - def serialize(self): - return { - "id": self.id, - "name": self.name, - "address": self.address, - "stats": self.stats, - "total_done": self.total_done, - "total_error": self.total_error, - "last_seen": self.last_seen - } - - @staticmethod - def materialize(data, cache = True): - if not data: - return None - - slave_id = data["id"] - - if cache and slave_id in RenderSlave._slave_map: - return RenderSlave._slave_map[slave_id] - - slave = RenderSlave() - slave.id = slave_id - slave.name = data["name"] - slave.address = data["address"] - slave.stats = data["stats"] - slave.total_done = data["total_done"] - slave.total_error = data["total_error"] - slave.last_seen = data["last_seen"] - - if cache: - RenderSlave._slave_map[slave_id] = slave - - return slave - -JOB_BLENDER = 1 -JOB_PROCESS = 2 -JOB_VCS = 3 - -JOB_TYPES = { - JOB_BLENDER: "Blender", - JOB_PROCESS: "Process", - JOB_VCS: "Versioned", - } - -class VersioningInfo: - def __init__(self, info = None): - self._system = None - self.wpath = "" - self.rpath = "" - self.revision = "" - - @property - def system(self): - return self._system - - @system.setter - def system(self, value): - self._system = versioning.SYSTEMS[value] - - def update(self): - self.system.update(self) - - def serialize(self): - return { - "wpath": self.wpath, - "rpath": self.rpath, - "revision": self.revision, - "system": self.system.name - } - - @staticmethod - def generate(system, path): - vs = VersioningInfo() - vs.wpath = path - vs.system = system - - vs.rpath = vs.system.path(path) - vs.revision = vs.system.revision(path) - - return vs - - - @staticmethod - def materialize(data): - if not data: - return None - - vs = VersioningInfo() - vs.wpath = data["wpath"] - vs.rpath = data["rpath"] - vs.revision = data["revision"] - vs.system = data["system"] - - return vs - - -class RenderFile: - def __init__(self, filepath = "", index = 0, start = -1, end = -1, signature=0): - self.filepath = filepath - self.original_path = filepath - self.signature = signature - self.index = index - self.start = start - self.end = end - - def serialize(self): - return { - "filepath": self.filepath, - "original_path": self.original_path, - "index": self.index, - "start": self.start, - "end": self.end, - "signature": self.signature - } - - @staticmethod - def materialize(data): - if not data: - return None - - rfile = RenderFile(data["filepath"], data["index"], data["start"], data["end"], data["signature"]) - rfile.original_path = data["original_path"] - - return rfile - -class RenderJob: - def __init__(self, job_info = None): - self.id = "" - self.type = JOB_BLENDER - self.name = "" - self.category = "None" - self.status = JOB_WAITING - self.files = [] - self.chunks = 0 - self.priority = 0 - self.blacklist = [] - - self.version_info = None - - self.resolution = None - - self.usage = 0.0 - self.last_dispatched = 0.0 - self.frames = [] - - if job_info: - self.type = job_info.type - self.name = job_info.name - self.category = job_info.category - self.status = job_info.status - self.files = job_info.files - self.chunks = job_info.chunks - self.priority = job_info.priority - self.blacklist = job_info.blacklist - self.version_info = job_info.version_info - - def hasRenderResult(self): - return self.type in (JOB_BLENDER, JOB_VCS) - - def rendersWithBlender(self): - return self.type in (JOB_BLENDER, JOB_VCS) - - def addFile(self, file_path, start=-1, end=-1, signed=True): - if signed: - signature = hashFile(file_path) - else: - signature = None - self.files.append(RenderFile(file_path, len(self.files), start, end, signature)) - - def addFrame(self, frame_number, command = ""): - frame = RenderFrame(frame_number, command) - self.frames.append(frame) - return frame - - def __len__(self): - return len(self.frames) - - def countFrames(self, status=QUEUED): - total = 0 - for f in self.frames: - if f.status == status: - total += 1 - - return total - - def countSlaves(self): - return len(set((frame.slave for frame in self.frames if frame.status == DISPATCHED))) - - def statusText(self): - return JOB_STATUS_TEXT[self.status] - - def framesStatus(self): - results = { - QUEUED: 0, - DISPATCHED: 0, - DONE: 0, - ERROR: 0 - } - - for frame in self.frames: - results[frame.status] += 1 - - return results - - def __contains__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return True - else: - return False - - def __getitem__(self, frame_number): - for f in self.frames: - if f.number == frame_number: - return f - else: - return None - - def serialize(self, frames = None): - min_frame = min((f.number for f in frames)) if frames else -1 - max_frame = max((f.number for f in frames)) if frames else -1 - return { - "id": self.id, - "type": self.type, - "name": self.name, - "category": self.category, - "status": self.status, - "files": [f.serialize() for f in self.files if f.start == -1 or not frames or (f.start <= max_frame and f.end >= min_frame)], - "frames": [f.serialize() for f in self.frames if not frames or f in frames], - "chunks": self.chunks, - "priority": self.priority, - "usage": self.usage, - "blacklist": self.blacklist, - "last_dispatched": self.last_dispatched, - "version_info": self.version_info.serialize() if self.version_info else None, - "resolution": self.resolution - } - - @staticmethod - def materialize(data): - if not data: - return None - - job = RenderJob() - job.id = data["id"] - job.type = data["type"] - job.name = data["name"] - job.category = data["category"] - job.status = data["status"] - job.files = [RenderFile.materialize(f) for f in data["files"]] - job.frames = [RenderFrame.materialize(f) for f in data["frames"]] - job.chunks = data["chunks"] - job.priority = data["priority"] - job.usage = data["usage"] - job.blacklist = data["blacklist"] - job.last_dispatched = data["last_dispatched"] - job.resolution = data["resolution"] - - version_info = data.get("version_info", None) - if version_info: - job.version_info = VersioningInfo.materialize(version_info) - - return job - -class RenderFrame: - def __init__(self, number = 0, command = ""): - self.number = number - self.time = 0 - self.status = QUEUED - self.slave = None - self.command = command - - def statusText(self): - return FRAME_STATUS_TEXT[self.status] - - def serialize(self): - return { - "number": self.number, - "time": self.time, - "status": self.status, - "slave": None if not self.slave else self.slave.serialize(), - "command": self.command - } - - @staticmethod - def materialize(data): - if not data: - return None - - frame = RenderFrame() - frame.number = data["number"] - frame.time = data["time"] - frame.status = data["status"] - frame.slave = RenderSlave.materialize(data["slave"]) - frame.command = data["command"] - - return frame diff --git a/release/scripts/io/netrender/netrender.css b/release/scripts/io/netrender/netrender.css deleted file mode 100644 index 0c54690e002..00000000000 --- a/release/scripts/io/netrender/netrender.css +++ /dev/null @@ -1,88 +0,0 @@ -body { - background-color:#eee; - font-size:12px; - font-family: "Lucida Sans","Lucida Sans Unicode","Lucida Grande",Lucida,sans-serif; - -} -a { - /*text-decoration:none;*/ - color:#666; -} -a:hover { - color:#000; -} -h2 { - background-color:#ddd; - font-size:120%; - padding:5px; -} - -h2 { - background-color:#ddd; - font-size:110%; - padding:5px; -} - -table { - text-align:center; - border:0; - background-color:#ddd; - padding: 0px; - margin: 0px; -} -thead{ - font-size:90%; - color:#555; - background-color:#ccc; -} -td { - border:0; - padding:2px; - padding-left:10px; - padding-right:10px; - margin-left:20px; - background-color:#ddd; -} -td:hover { - background-color:#ccc; -} -tr { - border:0; -} -button { - color: #111; - width: auto; - height: auto; -} - -.toggle { - text-decoration: underline; - cursor: pointer; -} - -.cache { - display: none; -} - -.fluid { - display: none; -} - -.other { - display: none; -} - -.rules { - width: 60em; - text-align: left; -} - -img.thumb { - display: none; - cursor: pointer; -} - -span.thumb { - text-decoration: underline; - cursor: pointer; -} diff --git a/release/scripts/io/netrender/netrender.js b/release/scripts/io/netrender/netrender.js deleted file mode 100644 index 1024a169571..00000000000 --- a/release/scripts/io/netrender/netrender.js +++ /dev/null @@ -1,146 +0,0 @@ -lastFrame = -1 -maxFrame = -1 -minFrame = -1 - -function request(url, data) -{ - xmlhttp = new XMLHttpRequest(); - xmlhttp.open("POST", url, false); - xmlhttp.send(data); - window.location.reload() -} - -function edit(id, info) -{ - request("/edit_" + id, info) -} - -function clear_jobs() -{ - var r=confirm("Also delete files on master?"); - - if (r==true) { - request('/clear', '{"clear":true}'); - } else { - request('/clear', '{"clear":false}'); - } -} - -function cancel_job(id) -{ - var r=confirm("Also delete files on master?"); - - if (r==true) { - request('/cancel_' + id, '{"clear":true}'); - } else { - request('/cancel_' + id, '{"clear":false}'); - } -} - -function balance_edit(id, old_value) -{ - var new_value = prompt("New limit", old_value); - if (new_value != null && new_value != "") { - request("/balance_limit", '{"' + id + '":"' + new_value + '"}'); - } -} - -function balance_enable(id, value) -{ - request("/balance_enable", '{"' + id + '":' + value + "}"); -} - -function showThumb(job, frame) -{ - if (lastFrame != -1) { - if (maxFrame != -1 && minFrame != -1) { - if (frame >= minFrame && frame <= maxFrame) { - for(i = minFrame; i <= maxFrame; i=i+1) { - toggleThumb(job, i); - } - minFrame = -1; - maxFrame = -1; - lastFrame = -1; - } else if (frame > maxFrame) { - for(i = maxFrame+1; i <= frame; i=i+1) { - toggleThumb(job, i); - } - maxFrame = frame; - lastFrame = frame; - } else { - for(i = frame; i <= minFrame-1; i=i+1) { - toggleThumb(job, i); - } - minFrame = frame; - lastFrame = frame; - } - } else if (frame == lastFrame) { - toggleThumb(job, frame); - } else if (frame < lastFrame) { - minFrame = frame; - maxFrame = lastFrame; - - for(i = minFrame; i <= maxFrame-1; i=i+1) { - toggleThumb(job, i); - } - lastFrame = frame; - } else { - minFrame = lastFrame; - maxFrame = frame; - - for(i = minFrame+1; i <= maxFrame; i=i+1) { - toggleThumb(job, i); - } - lastFrame = frame; - } - } else { - toggleThumb(job, frame); - } -} - -function toggleThumb(job, frame) -{ - img = document.images["thumb" + frame]; - url = "/thumb_" + job + "_" + frame + ".jpg" - - if (img.style.display == "block") { - img.style.display = "none"; - img.src = ""; - lastFrame = -1; - } else { - img.src = url; - img.style.display = "block"; - lastFrame = frame; - } -} - -function returnObjById( id ) -{ - if (document.getElementById) - var returnVar = document.getElementById(id); - else if (document.all) - var returnVar = document.all[id]; - else if (document.layers) - var returnVar = document.layers[id]; - return returnVar; -} - -function toggleDisplay( className, value1, value2 ) -{ - style = getStyle(className) - - if (style.style["display"] == value1) { - style.style["display"] = value2; - } else { - style.style["display"] = value1; - } -} - -function getStyle(className) { - var classes = document.styleSheets[0].rules || document.styleSheets[0].cssRules - for(var x=0;x<classes.length;x++) { - if(classes[x].selectorText==className) { - return classes[x]; - } - } -}
\ No newline at end of file diff --git a/release/scripts/io/netrender/operators.py b/release/scripts/io/netrender/operators.py deleted file mode 100644 index f2c2fda7bde..00000000000 --- a/release/scripts/io/netrender/operators.py +++ /dev/null @@ -1,564 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import bpy -import sys, os -import http, http.client, http.server, urllib, socket -import webbrowser -import json - -import netrender -from netrender.utils import * -import netrender.client as client -import netrender.model -import netrender.versioning as versioning - -class RENDER_OT_netslave_bake(bpy.types.Operator): - '''NEED DESCRIPTION''' - bl_idname = "render.netslavebake" - bl_label = "Bake all in file" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - filename = bpy.data.filepath - path, name = os.path.split(filename) - root, ext = os.path.splitext(name) - default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that - relative_path = os.sep + os.sep + "blendcache_" + root + os.sep - - # Force all point cache next to the blend file - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - modifier.settings.path = relative_path - bpy.ops.fluid.bake({"active_object": object, "scene": scene}) - elif modifier.type == "CLOTH": - modifier.point_cache.frame_step = 1 - modifier.point_cache.use_disk_cache = True - modifier.point_cache.use_external = False - elif modifier.type == "SOFT_BODY": - modifier.point_cache.frame_step = 1 - modifier.point_cache.use_disk_cache = True - modifier.point_cache.use_external = False - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - modifier.domain_settings.point_cache.use_step = 1 - modifier.domain_settings.point_cache.use_disk_cache = True - modifier.domain_settings.point_cache.use_external = False - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - psys.point_cache.use_step = 1 - psys.point_cache.use_disk_cache = True - psys.point_cache.use_external = False - psys.point_cache.filepath = relative_path - - bpy.ops.ptcache.bake_all() - - #bpy.ops.wm.save_mainfile(filepath = path + os.sep + root + "_baked.blend") - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientanim(bpy.types.Operator): - '''Start rendering an animation on network''' - bl_idname = "render.netclientanim" - bl_label = "Animation on network" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - - bpy.ops.render.render('INVOKE_AREA', animation=True) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientrun(bpy.types.Operator): - '''Start network rendering service''' - bl_idname = "render.netclientstart" - bl_label = "Start Service" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - bpy.ops.render.render('INVOKE_AREA', animation=True) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientsend(bpy.types.Operator): - '''Send Render Job to the Network''' - bl_idname = "render.netclientsend" - bl_label = "Send job" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, True) - conn.close() - self.report('INFO', "Job sent to master") - except Exception as err: - self.report('ERROR', str(err)) - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientsendframe(bpy.types.Operator): - '''Send Render Job with current frame to the Network''' - bl_idname = "render.netclientsendframe" - bl_label = "Send current frame job" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - scene = context.scene - netsettings = scene.network_render - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - # Sending file - scene.network_render.job_id = client.clientSendJob(conn, scene, False) - conn.close() - self.report('INFO', "Job sent to master") - except Exception as err: - self.report('ERROR', str(err)) - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientstatus(bpy.types.Operator): - '''Refresh the status of the current jobs''' - bl_idname = "render.netclientstatus" - bl_label = "Client Status" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/status") - - response = conn.getresponse() - content = response.read() - print( response.status, response.reason ) - - jobs = (netrender.model.RenderJob.materialize(j) for j in json.loads(str(content, encoding='utf8'))) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - netrender.jobs = [] - - for j in jobs: - netrender.jobs.append(j) - netsettings.jobs.add() - job = netsettings.jobs[-1] - - j.results = j.framesStatus() # cache frame status - - job.name = j.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientblacklistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientblacklistslave" - bl_label = "Client Blacklist Slave" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_slave_index >= 0: - - # deal with data - slave = netrender.slaves.pop(netsettings.active_slave_index) - netrender.blacklist.append(slave) - - # deal with rna - netsettings.slaves_blacklist.add() - netsettings.slaves_blacklist[-1].name = slave.name - - netsettings.slaves.remove(netsettings.active_slave_index) - netsettings.active_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientwhitelistslave(bpy.types.Operator): - '''Operator documentation text, will be used for the operator tooltip and python docs.''' - bl_idname = "render.netclientwhitelistslave" - bl_label = "Client Whitelist Slave" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - if netsettings.active_blacklisted_slave_index >= 0: - - # deal with data - slave = netrender.blacklist.pop(netsettings.active_blacklisted_slave_index) - netrender.slaves.append(slave) - - # deal with rna - netsettings.slaves.add() - netsettings.slaves[-1].name = slave.name - - netsettings.slaves_blacklist.remove(netsettings.active_blacklisted_slave_index) - netsettings.active_blacklisted_slave_index = -1 - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - - -class RENDER_OT_netclientslaves(bpy.types.Operator): - '''Refresh status about available Render slaves''' - bl_idname = "render.netclientslaves" - bl_label = "Client Slaves" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("GET", "/slaves") - - response = conn.getresponse() - content = response.read() - print( response.status, response.reason ) - - slaves = (netrender.model.RenderSlave.materialize(s) for s in json.loads(str(content, encoding='utf8'))) - - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - netrender.slaves = [] - - for s in slaves: - for i in range(len(netrender.blacklist)): - slave = netrender.blacklist[i] - if slave.id == s.id: - netrender.blacklist[i] = s - netsettings.slaves_blacklist[i].name = s.name - break - else: - netrender.slaves.append(s) - - netsettings.slaves.add() - slave = netsettings.slaves[-1] - slave.name = s.name - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientcancel(bpy.types.Operator): - '''Cancel the selected network rendering job.''' - bl_idname = "render.netclientcancel" - bl_label = "Client Cancel" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > 0 - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job = netrender.jobs[netsettings.active_job_index] - - conn.request("POST", cancelURL(job.id), json.dumps({'clear':False})) - - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - - netsettings.jobs.remove(netsettings.active_job_index) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class RENDER_OT_netclientcancelall(bpy.types.Operator): - '''Cancel all running network rendering jobs.''' - bl_idname = "render.netclientcancelall" - bl_label = "Client Cancel All" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.request("POST", "/clear", json.dumps({'clear':False})) - - response = conn.getresponse() - response.read() - print( response.status, response.reason ) - - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientdownload(bpy.types.Operator): - '''Download render results from the network''' - bl_idname = "render.netclientdownload" - bl_label = "Client Download" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.active_job_index >= 0 and len(netsettings.jobs) > netsettings.active_job_index - - def execute(self, context): - netsettings = context.scene.network_render - rd = context.scene.render - - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - job_id = netrender.jobs[netsettings.active_job_index].id - - conn.request("GET", "/status", headers={"job-id":job_id}) - - response = conn.getresponse() - - if response.status != http.client.OK: - self.report('ERROR', "Job ID %i not defined on master" % job_id) - return {'ERROR'} - - content = response.read() - - job = netrender.model.RenderJob.materialize(json.loads(str(content, encoding='utf8'))) - - conn.close() - - finished_frames = [] - - nb_error = 0 - nb_missing = 0 - - for frame in job.frames: - if frame.status == DONE: - finished_frames.append(frame.number) - elif frame.status == ERROR: - nb_error += 1 - else: - nb_missing += 1 - - if not finished_frames: - return - - frame_ranges = [] - - first = None - last = None - - for i in range(len(finished_frames)): - current = finished_frames[i] - - if not first: - first = current - last = current - elif last + 1 == current: - last = current - - if last + 1 < current or i + 1 == len(finished_frames): - if first < last: - frame_ranges.append((first, last)) - else: - frame_ranges.append((first,)) - - first = current - last = current - - getResults(netsettings.server_address, netsettings.server_port, job_id, job.resolution[0], job.resolution[1], job.resolution[2], frame_ranges) - - if nb_error and nb_missing: - self.report('ERROR', "Results downloaded but skipped %i frames with errors and %i unfinished frames" % (nb_error, nb_missing)) - elif nb_error: - self.report('ERROR', "Results downloaded but skipped %i frames with errors" % nb_error) - elif nb_missing: - self.report('WARNING', "Results downloaded but skipped %i unfinished frames" % nb_missing) - else: - self.report('INFO', "All results downloaded") - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientscan(bpy.types.Operator): - '''Listen on network for master server broadcasting its address and port.''' - bl_idname = "render.netclientscan" - bl_label = "Client Scan" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - address, port = clientScan(self.report) - - if address: - scene = context.scene - netsettings = scene.network_render - netsettings.server_address = address - netsettings.server_port = port - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - -class netclientvcsguess(bpy.types.Operator): - '''Guess VCS setting for the current file''' - bl_idname = "render.netclientvcsguess" - bl_label = "VCS Guess" - - @classmethod - def poll(cls, context): - return True - - def execute(self, context): - netsettings = context.scene.network_render - - system = versioning.SYSTEMS.get(netsettings.vcs_system, None) - - if system: - wpath, name = os.path.split(os.path.abspath(bpy.data.filepath)) - - rpath = system.path(wpath) - revision = system.revision(wpath) - - netsettings.vcs_wpath = wpath - netsettings.vcs_rpath = rpath - netsettings.vcs_revision = revision - - - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) - - -class netclientweb(bpy.types.Operator): - '''Open new window with information about running rendering jobs''' - bl_idname = "render.netclientweb" - bl_label = "Open Master Monitor" - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return netsettings.server_address != "[default]" - - def execute(self, context): - netsettings = context.scene.network_render - - - # open connection to make sure server exists - conn = clientConnection(netsettings.server_address, netsettings.server_port, self.report) - - if conn: - conn.close() - - webbrowser.open("http://%s:%i" % (netsettings.server_address, netsettings.server_port)) - - return {'FINISHED'} - - def invoke(self, context, event): - return self.execute(context) diff --git a/release/scripts/io/netrender/repath.py b/release/scripts/io/netrender/repath.py deleted file mode 100644 index 3ac9636b628..00000000000 --- a/release/scripts/io/netrender/repath.py +++ /dev/null @@ -1,150 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os -import subprocess - -import bpy - -from netrender.utils import * -import netrender.model - -BLENDER_PATH = sys.argv[0] - -def reset(job): - main_file = job.files[0] - - job_full_path = main_file.filepath - - if os.path.exists(job_full_path + ".bak"): - os.remove(job_full_path) # repathed file - os.renames(job_full_path + ".bak", job_full_path) - -def update(job): - paths = [] - - main_file = job.files[0] - - job_full_path = main_file.filepath - - - path, ext = os.path.splitext(job_full_path) - - new_path = path + ".remap" + ext - - # Disable for now. Partial repath should work anyway - #all = main_file.filepath != main_file.original_path - all = False - - for rfile in job.files[1:]: - if all or rfile.original_path != rfile.filepath: - paths.append(rfile.original_path) - paths.append(rfile.filepath) - - # Only update if needed - if paths: - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-P", __file__, "--", new_path] + paths, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - process.wait() - - os.renames(job_full_path, job_full_path + ".bak") - os.renames(new_path, job_full_path) - -def process(paths): - def processPointCache(point_cache): - point_cache.use_external = False - - def processFluid(fluid): - new_path = path_map.get(fluid.filepath, None) - if new_path: - fluid.path = new_path - - path_map = {} - for i in range(0, len(paths), 2): - # special case for point cache - if paths[i].endswith(".bphys"): - pass # Don't need them in the map, they all use the default external path - # NOTE: This is probably not correct all the time, need to be fixed. - # special case for fluids - elif paths[i].endswith(".bobj.gz"): - path_map[os.path.split(paths[i])[0]] = os.path.split(paths[i+1])[0] - else: - path_map[os.path.split(paths[i])[1]] = paths[i+1] - - # TODO original paths aren't really the orignal path (they are the normalized path - # so we repath using the filenames only. - - ########################### - # LIBRARIES - ########################### - for lib in bpy.data.libraries: - file_path = bpy.path.abspath(lib.filepath) - new_path = path_map.get(os.path.split(file_path)[1], None) - if new_path: - lib.filepath = new_path - - ########################### - # IMAGES - ########################### - for image in bpy.data.images: - if image.source == "FILE" and not image.packed_file: - file_path = bpy.path.abspath(image.filepath) - new_path = path_map.get(os.path.split(file_path)[1], None) - if new_path: - image.filepath = new_path - - - ########################### - # FLUID + POINT CACHE - ########################### - for object in bpy.data.objects: - for modifier in object.modifiers: - if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN": - processFluid(settings) - elif modifier.type == "CLOTH": - processPointCache(modifier.point_cache) - elif modifier.type == "SOFT_BODY": - processPointCache(modifier.point_cache) - elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN": - processPointCache(modifier.domain_settings.point_cache_low) - if modifier.domain_settings.use_high_resolution: - processPointCache(modifier.domain_settings.point_cache_high) - elif modifier.type == "MULTIRES" and modifier.is_external: - file_path = bpy.path.abspath(modifier.filepath) - new_path = path_map.get(file_path, None) - if new_path: - modifier.filepath = new_path - - # particles modifier are stupid and don't contain data - # we have to go through the object property - for psys in object.particle_systems: - processPointCache(psys.point_cache) - - -if __name__ == "__main__": - try: - i = sys.argv.index("--") - except: - i = 0 - - if i: - new_path = sys.argv[i+1] - args = sys.argv[i+2:] - - process(args) - - bpy.ops.wm.save_as_mainfile(filepath=new_path, check_existing=False) diff --git a/release/scripts/io/netrender/slave.py b/release/scripts/io/netrender/slave.py deleted file mode 100644 index b05de0afeb9..00000000000 --- a/release/scripts/io/netrender/slave.py +++ /dev/null @@ -1,349 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os, platform, shutil -import http, http.client, http.server, urllib -import subprocess, time -import json - -import bpy - -from netrender.utils import * -import netrender.model -import netrender.repath -import netrender.thumbnail as thumbnail - -BLENDER_PATH = sys.argv[0] - -CANCEL_POLL_SPEED = 2 -MAX_TIMEOUT = 10 -INCREMENT_TIMEOUT = 1 -MAX_CONNECT_TRY = 10 -try: - system = platform.system() -except UnicodeDecodeError: - import sys - system = sys.platform - -if system in ('Windows', 'win32') and platform.version() >= '5': # Error mode is only available on Win2k or higher, that's version 5 - import ctypes - def SetErrorMode(): - val = ctypes.windll.kernel32.SetErrorMode(0x0002) - ctypes.windll.kernel32.SetErrorMode(val | 0x0002) - return val - - def RestoreErrorMode(val): - ctypes.windll.kernel32.SetErrorMode(val) -else: - def SetErrorMode(): - return 0 - - def RestoreErrorMode(val): - pass - -def clearSlave(path): - shutil.rmtree(path) - -def slave_Info(): - sysname, nodename, release, version, machine, processor = platform.uname() - slave = netrender.model.RenderSlave() - slave.name = nodename - slave.stats = sysname + " " + release + " " + machine + " " + processor - return slave - -def testCancel(conn, job_id, frame_number): - conn.request("HEAD", "/status", headers={"job-id":job_id, "job-frame": str(frame_number)}) - - # canceled if job isn't found anymore - if responseStatus(conn) == http.client.NO_CONTENT: - return True - else: - return False - -def testFile(conn, job_id, slave_id, rfile, JOB_PREFIX, main_path = None): - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path) - - found = os.path.exists(job_full_path) - - if found and rfile.signature != None: - found_signature = hashFile(job_full_path) - found = found_signature == rfile.signature - - if not found: - print("Found file %s at %s but signature mismatch!" % (rfile.filepath, job_full_path)) - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) - - if not found: - # Force prefix path if not found - job_full_path = prefixPath(JOB_PREFIX, rfile.filepath, main_path, force = True) - temp_path = os.path.join(JOB_PREFIX, "slave.temp") - conn.request("GET", fileURL(job_id, rfile.index), headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status != http.client.OK: - return None # file for job not returned by server, need to return an error code to server - - f = open(temp_path, "wb") - buf = response.read(1024) - - while buf: - f.write(buf) - buf = response.read(1024) - - f.close() - - os.renames(temp_path, job_full_path) - - rfile.filepath = job_full_path - - return job_full_path - -def breakable_timeout(timeout): - for i in range(timeout): - time.sleep(1) - if engine.test_break(): - break - -def render_slave(engine, netsettings, threads): - timeout = 1 - - bisleep = BreakableIncrementedSleep(INCREMENT_TIMEOUT, 1, MAX_TIMEOUT, engine.test_break) - - engine.update_stats("", "Network render node initiation") - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if not conn: - timeout = 1 - print("Connection failed, will try connecting again at most %i times" % MAX_CONNECT_TRY) - bisleep.reset() - - for i in range(MAX_CONNECT_TRY): - bisleep.sleep() - - conn = clientConnection(netsettings.server_address, netsettings.server_port) - - if conn or engine.test_break(): - break - - print("Retry %i failed, waiting %is before retrying" % (i + 1, bisleep.current)) - - if conn: - conn.request("POST", "/slave", json.dumps(slave_Info().serialize())) - response = conn.getresponse() - response.read() - - slave_id = response.getheader("slave-id") - - NODE_PREFIX = os.path.join(bpy.path.abspath(netsettings.path), "slave_" + slave_id) - if not os.path.exists(NODE_PREFIX): - os.mkdir(NODE_PREFIX) - - engine.update_stats("", "Network render connected to master, waiting for jobs") - - while not engine.test_break(): - conn.request("GET", "/job", headers={"slave-id":slave_id}) - response = conn.getresponse() - - if response.status == http.client.OK: - bisleep.reset() - - job = netrender.model.RenderJob.materialize(json.loads(str(response.read(), encoding='utf8'))) - engine.update_stats("", "Network render processing job from master") - - JOB_PREFIX = os.path.join(NODE_PREFIX, "job_" + job.id) - if not os.path.exists(JOB_PREFIX): - os.mkdir(JOB_PREFIX) - - # set tempdir for fsaa temp files - # have to set environ var because render is done in a subprocess and that's the easiest way to propagate the setting - os.environ["TMP"] = JOB_PREFIX - - - if job.type == netrender.model.JOB_BLENDER: - job_path = job.files[0].filepath # path of main file - main_path, main_file = os.path.split(job_path) - - job_full_path = testFile(conn, job.id, slave_id, job.files[0], JOB_PREFIX) - print("Fullpath", job_full_path) - print("File:", main_file, "and %i other files" % (len(job.files) - 1,)) - - for rfile in job.files[1:]: - testFile(conn, job.id, slave_id, rfile, JOB_PREFIX, main_path) - print("\t", rfile.filepath) - - netrender.repath.update(job) - - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) - elif job.type == netrender.model.JOB_VCS: - if not job.version_info: - # Need to return an error to server, incorrect job type - pass - - job_path = job.files[0].filepath # path of main file - main_path, main_file = os.path.split(job_path) - - job.version_info.update() - - # For VCS jobs, file path is relative to the working copy path - job_full_path = os.path.join(job.version_info.wpath, job_path) - - engine.update_stats("", "Render File "+ main_file+ " for job "+ job.id) - - # announce log to master - logfile = netrender.model.LogFile(job.id, slave_id, [frame.number for frame in job.frames]) - conn.request("POST", "/log", bytes(json.dumps(logfile.serialize()), encoding='utf8')) - response = conn.getresponse() - response.read() - - - first_frame = job.frames[0].number - - # start render - start_t = time.time() - - if job.rendersWithBlender(): - frame_args = [] - - for frame in job.frames: - print("frame", frame.number) - frame_args += ["-f", str(frame.number)] - - val = SetErrorMode() - process = subprocess.Popen([BLENDER_PATH, "-b", "-noaudio", job_full_path, "-t", str(threads), "-o", os.path.join(JOB_PREFIX, "######"), "-E", "BLENDER_RENDER", "-F", "MULTILAYER"] + frame_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - elif job.type == netrender.model.JOB_PROCESS: - command = job.frames[0].command - val = SetErrorMode() - process = subprocess.Popen(command.split(" "), stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - RestoreErrorMode(val) - - headers = {"slave-id":slave_id} - - cancelled = False - stdout = bytes() - run_t = time.time() - while not cancelled and process.poll() is None: - stdout += process.stdout.read(1024) - current_t = time.time() - cancelled = engine.test_break() - if current_t - run_t > CANCEL_POLL_SPEED: - - # update logs if needed - if stdout: - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - response = conn.getresponse() - response.read() - - # Also output on console - if netsettings.use_slave_output_log: - print(str(stdout, encoding='utf8'), end="") - - stdout = bytes() - - run_t = current_t - if testCancel(conn, job.id, first_frame): - cancelled = True - - if job.type == netrender.model.JOB_BLENDER: - netrender.repath.reset(job) - - # read leftovers if needed - stdout += process.stdout.read() - - if cancelled: - # kill process if needed - if process.poll() is None: - try: - process.terminate() - except OSError: - pass - continue # to next frame - - # flush the rest of the logs - if stdout: - # Also output on console - if netsettings.use_slave_thumb: - print(str(stdout, encoding='utf8'), end="") - - # (only need to update on one frame, they are linked - conn.request("PUT", logURL(job.id, first_frame), stdout, headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - total_t = time.time() - start_t - - avg_t = total_t / len(job.frames) - - status = process.returncode - - print("status", status) - - headers = {"job-id":job.id, "slave-id":slave_id, "job-time":str(avg_t)} - - - if status == 0: # non zero status is error - headers["job-result"] = str(DONE) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - if job.hasRenderResult(): - # send image back to server - - filename = os.path.join(JOB_PREFIX, "%06d.exr" % frame.number) - - # thumbnail first - if netsettings.use_slave_thumb: - thumbname = thumbnail.generate(filename) - - if thumbname: - f = open(thumbname, 'rb') - conn.request("PUT", "/thumb", f, headers=headers) - f.close() - responseStatus(conn) - - f = open(filename, 'rb') - conn.request("PUT", "/render", f, headers=headers) - f.close() - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - elif job.type == netrender.model.JOB_PROCESS: - conn.request("PUT", "/render", headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - else: - headers["job-result"] = str(ERROR) - for frame in job.frames: - headers["job-frame"] = str(frame.number) - # send error result back to server - conn.request("PUT", "/render", headers=headers) - if responseStatus(conn) == http.client.NO_CONTENT: - continue - - engine.update_stats("", "Network render connected to master, waiting for jobs") - else: - bisleep.sleep() - - conn.close() - - if netsettings.use_slave_clear: - clearSlave(NODE_PREFIX) - -if __name__ == "__main__": - pass diff --git a/release/scripts/io/netrender/thumbnail.py b/release/scripts/io/netrender/thumbnail.py deleted file mode 100644 index 2ead6e82745..00000000000 --- a/release/scripts/io/netrender/thumbnail.py +++ /dev/null @@ -1,81 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os -import subprocess - -import bpy - -def generate(filename, external=True): - if external: - process = subprocess.Popen([sys.argv[0], "-b", "-noaudio", "-P", __file__, "--", filename], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while process.poll() is None: - process.stdout.read(1024) # empty buffer to be sure - process.stdout.read() - - return _thumbname(filename) - else: - return _internal(filename) - -def _thumbname(filename): - root = os.path.splitext(filename)[0] - return root + ".jpg" - -def _internal(filename): - imagename = os.path.split(filename)[1] - thumbname = _thumbname(filename) - - if os.path.exists(thumbname): - return thumbname - - if bpy: - scene = bpy.data.scenes[0] # FIXME, this is dodgy! - scene.render.file_format = "JPEG" - scene.render.file_quality = 90 - - # remove existing image, if there's a leftover (otherwise open changes the name) - if imagename in bpy.data.images: - img = bpy.data.images[imagename] - bpy.data.images.remove(img) - - bpy.ops.image.open(filepath=filename) - img = bpy.data.images[imagename] - - img.save_render(thumbname, scene=scene) - - img.user_clear() - bpy.data.images.remove(img) - - try: - process = subprocess.Popen(["convert", thumbname, "-resize", "300x300", thumbname]) - process.wait() - return thumbname - except Exception as exp: - print("Error while generating thumbnail") - print(exp) - - return None - -if __name__ == "__main__": - import bpy - try: - start = sys.argv.index("--") + 1 - except ValueError: - start = 0 - for filename in sys.argv[start:]: - generate(filename, external=False) diff --git a/release/scripts/io/netrender/ui.py b/release/scripts/io/netrender/ui.py deleted file mode 100644 index 343c60e7865..00000000000 --- a/release/scripts/io/netrender/ui.py +++ /dev/null @@ -1,550 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import bpy -import sys, os -import http, http.client, http.server, urllib -import subprocess, shutil, time, hashlib - -import netrender -import netrender.slave as slave -import netrender.master as master - -from netrender.utils import * - -VERSION = b"0.3" - -PATH_PREFIX = "/tmp/" - -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 - -LAST_ADDRESS_TEST = 0 - -def base_poll(cls, context): - rd = context.scene.render - return (rd.use_game_engine==False) and (rd.engine in cls.COMPAT_ENGINES) - - -def init_file(): - if netrender.init_file != bpy.data.filepath: - netrender.init_file = bpy.data.filepath - netrender.init_data = True - netrender.valid_address = False - -def init_data(netsettings): - init_file() - - if netrender.init_data: - netrender.init_data = False - - netsettings.active_slave_index = 0 - while(len(netsettings.slaves) > 0): - netsettings.slaves.remove(0) - - netsettings.active_blacklisted_slave_index = 0 - while(len(netsettings.slaves_blacklist) > 0): - netsettings.slaves_blacklist.remove(0) - - netsettings.active_job_index = 0 - while(len(netsettings.jobs) > 0): - netsettings.jobs.remove(0) - -def verify_address(netsettings): - global LAST_ADDRESS_TEST - init_file() - - if LAST_ADDRESS_TEST + 30 < time.time(): - LAST_ADDRESS_TEST = time.time() - - try: - conn = clientConnection(netsettings.server_address, netsettings.server_port, scan = False, timeout = 1) - except: - conn = None - - if conn: - netrender.valid_address = True - conn.close() - else: - netrender.valid_address = False - - return netrender.valid_address - -class NeedValidAddress(): - @classmethod - def poll(cls, context): - return super().poll(context) and verify_address(context.scene.network_render) - -class NetRenderButtonsPanel(): - bl_space_type = "PROPERTIES" - bl_region_type = "WINDOW" - bl_context = "render" - # COMPAT_ENGINES must be defined in each subclass, external engines can add themselves here - - @classmethod - def poll(cls, context): - rd = context.scene.render - return rd.engine == 'NET_RENDER' and rd.use_game_engine == False - -# Setting panel, use in the scene for now. -class RENDER_PT_network_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Network Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - return super().poll(context) - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - verify_address(netsettings) - - layout.prop(netsettings, "mode", expand=True) - - if netsettings.mode in ("RENDER_MASTER", "RENDER_SLAVE"): - layout.operator("render.netclientstart", icon='PLAY') - - layout.prop(netsettings, "path") - - split = layout.split(percentage=0.7) - - col = split.column() - col.label(text="Server Address:") - col.prop(netsettings, "server_address", text="") - - col = split.column() - col.label(text="Port:") - col.prop(netsettings, "server_port", text="") - - if netsettings.mode != "RENDER_MASTER": - layout.operator("render.netclientscan", icon='FILE_REFRESH', text="") - - if not netrender.valid_address: - layout.label(text="No master at specified address") - - layout.operator("render.netclientweb", icon='QUESTION') - -class RENDER_PT_network_slave_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slave Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_SLAVE" - - def draw(self, context): - layout = self.layout - - scene = context.scene - rd = scene.render - netsettings = scene.network_render - - layout.prop(netsettings, "use_slave_clear") - layout.prop(netsettings, "use_slave_thumb") - layout.prop(netsettings, "use_slave_output_log") - layout.label(text="Threads:") - layout.prop(rd, "threads_mode", expand=True) - sub = layout.column() - sub.enabled = rd.threads_mode == 'FIXED' - sub.prop(rd, "threads") - -class RENDER_PT_network_master_settings(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Master Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_MASTER" - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - layout.prop(netsettings, "use_master_broadcast") - layout.prop(netsettings, "use_master_clear") - -class RENDER_PT_network_job(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Job Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return super().poll(context) and scene.network_render.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - verify_address(netsettings) - - if netsettings.server_address != "[default]": - layout.operator("render.netclientanim", icon='RENDER_ANIMATION') - layout.operator("render.netclientsend", icon='FILE_BLEND') - layout.operator("render.netclientsendframe", icon='RENDER_STILL') - if netsettings.job_id: - row = layout.row() - row.operator("render.render", text="Get Image", icon='RENDER_STILL') - row.operator("render.render", text="Get Animation", icon='RENDER_ANIMATION').animation = True - - split = layout.split(percentage=0.3) - - col = split.column() - col.label(text="Type:") - col.label(text="Name:") - col.label(text="Category:") - - col = split.column() - col.prop(netsettings, "job_type", text="") - col.prop(netsettings, "job_name", text="") - col.prop(netsettings, "job_category", text="") - - row = layout.row() - row.prop(netsettings, "priority") - row.prop(netsettings, "chunks") - -class RENDER_PT_network_job_vcs(NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "VCS Job Settings" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - scene = context.scene - return (super().poll(context) - and scene.network_render.mode == "RENDER_CLIENT" - and scene.network_render.job_type == "JOB_VCS") - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - layout.operator("render.netclientvcsguess", icon='FILE_REFRESH', text="") - - layout.prop(netsettings, "vcs_system") - layout.prop(netsettings, "vcs_revision") - layout.prop(netsettings, "vcs_rpath") - layout.prop(netsettings, "vcs_wpath") - -class RENDER_PT_network_slaves(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slaves Status" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves", netsettings, "active_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientslaves", icon='FILE_REFRESH', text="") - sub.operator("render.netclientblacklistslave", icon='ZOOMOUT', text="") - - if len(netrender.slaves) > netsettings.active_slave_index >= 0: - layout.separator() - - slave = netrender.slaves[netsettings.active_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) - -class RENDER_PT_network_slaves_blacklist(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Slaves Blacklist" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "slaves_blacklist", netsettings, "active_blacklisted_slave_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientwhitelistslave", icon='ZOOMOUT', text="") - - if len(netrender.blacklist) > netsettings.active_blacklisted_slave_index >= 0: - layout.separator() - - slave = netrender.blacklist[netsettings.active_blacklisted_slave_index] - - layout.label(text="Name: " + slave.name) - layout.label(text="Address: " + slave.address[0]) - layout.label(text="Seen: " + time.ctime(slave.last_seen)) - layout.label(text="Stats: " + slave.stats) - -class RENDER_PT_network_jobs(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Jobs" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - def draw(self, context): - layout = self.layout - - scene = context.scene - netsettings = scene.network_render - - row = layout.row() - row.template_list(netsettings, "jobs", netsettings, "active_job_index", rows=2) - - sub = row.column(align=True) - sub.operator("render.netclientstatus", icon='FILE_REFRESH', text="") - sub.operator("render.netclientcancel", icon='ZOOMOUT', text="") - sub.operator("render.netclientcancelall", icon='PANEL_CLOSE', text="") - sub.operator("render.netclientdownload", icon='RENDER_ANIMATION', text="") - - if len(netrender.jobs) > netsettings.active_job_index >= 0: - layout.separator() - - job = netrender.jobs[netsettings.active_job_index] - - layout.label(text="Name: %s" % job.name) - layout.label(text="Length: %04i" % len(job)) - layout.label(text="Done: %04i" % job.results[DONE]) - layout.label(text="Error: %04i" % job.results[ERROR]) - -import properties_render -class RENDER_PT_network_output(NeedValidAddress, NetRenderButtonsPanel, bpy.types.Panel): - bl_label = "Output" - COMPAT_ENGINES = {'NET_RENDER'} - - @classmethod - def poll(cls, context): - netsettings = context.scene.network_render - return super().poll(context) and netsettings.mode == "RENDER_CLIENT" - - draw = properties_render.RENDER_PT_output.draw - - -def addProperties(): - class NetRenderSettings(bpy.types.IDPropertyGroup): - pass - - class NetRenderSlave(bpy.types.IDPropertyGroup): - pass - - class NetRenderJob(bpy.types.IDPropertyGroup): - pass - - bpy.utils.register_class(NetRenderSettings) - bpy.utils.register_class(NetRenderSlave) - bpy.utils.register_class(NetRenderJob) - - from bpy.props import PointerProperty, StringProperty, BoolProperty, EnumProperty, IntProperty, CollectionProperty - bpy.types.Scene.network_render = PointerProperty(type=NetRenderSettings, name="Network Render", description="Network Render Settings") - - NetRenderSettings.server_address = StringProperty( - name="Server address", - description="IP or name of the master render server", - maxlen = 128, - default = "[default]") - - NetRenderSettings.server_port = IntProperty( - name="Server port", - description="port of the master render server", - default = 8000, - min=1, - max=65535) - - NetRenderSettings.use_master_broadcast = BoolProperty( - name="Broadcast", - description="broadcast master server address on local network", - default = True) - - NetRenderSettings.use_slave_clear = BoolProperty( - name="Clear on exit", - description="delete downloaded files on exit", - default = True) - - NetRenderSettings.use_slave_thumb = BoolProperty( - name="Generate thumbnails", - description="Generate thumbnails on slaves instead of master", - default = False) - - NetRenderSettings.use_slave_output_log = BoolProperty( - name="Output render log on console", - description="Output render text log to console as well as sending it to the master", - default = True) - - NetRenderSettings.use_master_clear = BoolProperty( - name="Clear on exit", - description="delete saved files on exit", - default = False) - - default_path = os.environ.get("TEMP") - - if not default_path: - if os.name == 'nt': - default_path = "c:/tmp/" - else: - default_path = "/tmp/" - elif not default_path.endswith(os.sep): - default_path += os.sep - - NetRenderSettings.path = StringProperty( - name="Path", - description="Path for temporary files", - maxlen = 128, - default = default_path, - subtype='FILE_PATH') - - NetRenderSettings.job_type = EnumProperty( - items=( - ("JOB_BLENDER", "Blender", "Standard Blender Job"), - ("JOB_PROCESS", "Process", "Custom Process Job"), - ("JOB_VCS", "VCS", "Version Control System Managed Job"), - ), - name="Job Type", - description="Type of render job", - default="JOB_BLENDER") - - NetRenderSettings.job_name = StringProperty( - name="Job name", - description="Name of the job", - maxlen = 128, - default = "[default]") - - NetRenderSettings.job_category = StringProperty( - name="Job category", - description="Category of the job", - maxlen = 128, - default = "") - - NetRenderSettings.chunks = IntProperty( - name="Chunks", - description="Number of frame to dispatch to each slave in one chunk", - default = 5, - min=1, - max=65535) - - NetRenderSettings.priority = IntProperty( - name="Priority", - description="Priority of the job", - default = 1, - min=1, - max=10) - - NetRenderSettings.vcs_wpath = StringProperty( - name="Working Copy", - description="Path of the local working copy", - maxlen = 1024, - default = "") - - NetRenderSettings.vcs_rpath = StringProperty( - name="Remote Path", - description="Path of the server copy (protocol specific)", - maxlen = 1024, - default = "") - - NetRenderSettings.vcs_revision = StringProperty( - name="Revision", - description="Revision for this job", - maxlen = 256, - default = "") - - NetRenderSettings.vcs_system = StringProperty( - name="VCS", - description="Version Control System", - maxlen = 64, - default = "Subversion") - - NetRenderSettings.job_id = StringProperty( - name="Network job id", - description="id of the last sent render job", - maxlen = 64, - default = "") - - NetRenderSettings.active_slave_index = IntProperty( - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.active_blacklisted_slave_index = IntProperty( - name="Index of the active slave", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.active_job_index = IntProperty( - name="Index of the active job", - description="", - default = -1, - min= -1, - max=65535) - - NetRenderSettings.mode = EnumProperty( - items=( - ("RENDER_CLIENT", "Client", "Act as render client"), - ("RENDER_MASTER", "Master", "Act as render master"), - ("RENDER_SLAVE", "Slave", "Act as render slave"), - ), - name="Network mode", - description="Mode of operation of this instance", - default="RENDER_CLIENT") - - NetRenderSettings.slaves = CollectionProperty(type=NetRenderSlave, name="Slaves", description="") - NetRenderSettings.slaves_blacklist = CollectionProperty(type=NetRenderSlave, name="Slaves Blacklist", description="") - NetRenderSettings.jobs = CollectionProperty(type=NetRenderJob, name="Job List", description="") - - NetRenderSlave.name = StringProperty( - name="Name of the slave", - description="", - maxlen = 64, - default = "") - - NetRenderJob.name = StringProperty( - name="Name of the job", - description="", - maxlen = 128, - default = "") diff --git a/release/scripts/io/netrender/utils.py b/release/scripts/io/netrender/utils.py deleted file mode 100644 index ed9fb2de812..00000000000 --- a/release/scripts/io/netrender/utils.py +++ /dev/null @@ -1,313 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -import sys, os -import re -import http, http.client, http.server, urllib, socket -import subprocess, shutil, time, hashlib, zlib - -import netrender.model - -try: - import bpy -except: - bpy = None - -VERSION = bytes("1.3", encoding='utf8') - -# Jobs status -JOB_WAITING = 0 # before all data has been entered -JOB_PAUSED = 1 # paused by user -JOB_FINISHED = 2 # finished rendering -JOB_QUEUED = 3 # ready to be dispatched - -JOB_STATUS_TEXT = { - JOB_WAITING: "Waiting", - JOB_PAUSED: "Paused", - JOB_FINISHED: "Finished", - JOB_QUEUED: "Queued" - } - - -# Frames status -QUEUED = 0 -DISPATCHED = 1 -DONE = 2 -ERROR = 3 - -FRAME_STATUS_TEXT = { - QUEUED: "Queued", - DISPATCHED: "Dispatched", - DONE: "Done", - ERROR: "Error" - } - -class DirectoryContext: - def __init__(self, path): - self.path = path - - def __enter__(self): - self.curdir = os.path.abspath(os.curdir) - os.chdir(self.path) - - def __exit__(self, exc_type, exc_value, traceback): - os.chdir(self.curdir) - -class BreakableIncrementedSleep: - def __init__(self, increment, default_timeout, max_timeout, break_fct): - self.increment = increment - self.default = default_timeout - self.max = max_timeout - self.current = self.default - self.break_fct = break_fct - - def reset(self): - self.current = self.default - - def increase(self): - self.current = min(self.current + self.increment, self.max) - - def sleep(self): - for i in range(self.current): - time.sleep(1) - if self.break_fct(): - break - - self.increase() - -def responseStatus(conn): - response = conn.getresponse() - response.read() - return response.status - -def reporting(report, message, errorType = None): - if errorType: - t = 'ERROR' - else: - t = 'INFO' - - if report: - report(t, message) - return None - elif errorType: - raise errorType(message) - else: - return None - -def clientScan(report = None): - try: - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) - s.settimeout(30) - - s.bind(('', 8000)) - - buf, address = s.recvfrom(64) - - address = address[0] - port = int(str(buf, encoding='utf8')) - - reporting(report, "Master server found") - - return (address, port) - except socket.timeout: - reporting(report, "No master server on network", IOError) - - return ("", 8000) # return default values - -def clientConnection(address, port, report = None, scan = True, timeout = 5): - if address == "[default]": -# calling operator from python is fucked, scene isn't in context -# if bpy: -# bpy.ops.render.netclientscan() -# else: - if not scan: - return None - - address, port = clientScan() - if address == "": - return None - - try: - conn = http.client.HTTPConnection(address, port, timeout = timeout) - - if conn: - if clientVerifyVersion(conn): - return conn - else: - conn.close() - reporting(report, "Incorrect master version", ValueError) - except BaseException as err: - if report: - report('ERROR', str(err)) - return None - else: - print(err) - return None - -def clientVerifyVersion(conn): - conn.request("GET", "/version") - response = conn.getresponse() - - if response.status != http.client.OK: - conn.close() - return False - - server_version = response.read() - - if server_version != VERSION: - print("Incorrect server version!") - print("expected", str(VERSION, encoding='utf8'), "received", str(server_version, encoding='utf8')) - return False - - return True - -def fileURL(job_id, file_index): - return "/file_%s_%i" % (job_id, file_index) - -def logURL(job_id, frame_number): - return "/log_%s_%i.log" % (job_id, frame_number) - -def renderURL(job_id, frame_number): - return "/render_%s_%i.exr" % (job_id, frame_number) - -def cancelURL(job_id): - return "/cancel_%s" % (job_id) - -def hashFile(path): - f = open(path, "rb") - value = hashData(f.read()) - f.close() - return value - -def hashData(data): - m = hashlib.md5() - m.update(data) - return m.hexdigest() - - -def prefixPath(prefix_directory, file_path, prefix_path, force = False): - if (os.path.isabs(file_path) or - len(file_path) >= 3 and (file_path[1:3] == ":/" or file_path[1:3] == ":\\") or # Windows absolute path don't count as absolute on unix, have to handle them myself - file_path[0] == "/" or file_path[0] == "\\"): # and vice versa - - # if an absolute path, make sure path exists, if it doesn't, use relative local path - full_path = file_path - if force or not os.path.exists(full_path): - p, n = os.path.split(os.path.normpath(full_path)) - - if prefix_path and p.startswith(prefix_path): - if len(prefix_path) < len(p): - directory = os.path.join(prefix_directory, p[len(prefix_path)+1:]) # +1 to remove separator - if not os.path.exists(directory): - os.mkdir(directory) - else: - directory = prefix_directory - full_path = os.path.join(directory, n) - else: - full_path = os.path.join(prefix_directory, n) - else: - full_path = os.path.join(prefix_directory, file_path) - - return full_path - -def getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage, frame_ranges): - if bpy.app.debug: - print("=============================================") - print("============= FETCHING RESULTS ==============") - - frame_arguments = [] - for r in frame_ranges: - if len(r) == 2: - frame_arguments.extend(["-s", str(r[0]), "-e", str(r[1]), "-a"]) - else: - frame_arguments.extend(["-f", str(r[0])]) - - filepath = os.path.join(bpy.app.tempdir, "netrender_temp.blend") - bpy.ops.wm.save_as_mainfile(filepath=filepath, copy=True, check_existing=False) - - arguments = [sys.argv[0], "-b", "-noaudio", filepath, "-o", bpy.path.abspath(bpy.context.scene.render.filepath), "-P", __file__] + frame_arguments + ["--", "GetResults", server_address, str(server_port), job_id, str(resolution_x), str(resolution_y), str(resolution_percentage)] - if bpy.app.debug: - print("Starting subprocess:") - print(" ".join(arguments)) - - process = subprocess.Popen(arguments, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - while process.poll() is None: - stdout = process.stdout.read(1024) - if bpy.app.debug: - print(str(stdout, encoding='utf-8'), end="") - - - # read leftovers if needed - stdout = process.stdout.read() - if bpy.app.debug: - print(str(stdout, encoding='utf-8')) - - os.remove(filepath) - - if bpy.app.debug: - print("=============================================") - return - -def _getResults(server_address, server_port, job_id, resolution_x, resolution_y, resolution_percentage): - render = bpy.context.scene.render - - netsettings = bpy.context.scene.network_render - - netsettings.server_address = server_address - netsettings.server_port = int(server_port) - netsettings.job_id = job_id - - render.engine = 'NET_RENDER' - render.resolution_x = int(resolution_x) - render.resolution_y = int(resolution_y) - render.resolution_percentage = int(resolution_percentage) - - render.use_full_sample = False - render.use_compositing = False - render.use_border = False - - -def getFileInfo(filepath, infos): - process = subprocess.Popen([sys.argv[0], "-b", "-noaudio", filepath, "-P", __file__, "--", "FileInfo"] + infos, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - stdout = bytes() - while process.poll() is None: - stdout += process.stdout.read(1024) - - # read leftovers if needed - stdout += process.stdout.read() - - stdout = str(stdout, encoding="utf8") - - values = [eval(v[1:].strip()) for v in stdout.split("\n") if v.startswith("$")] - - return values - - -if __name__ == "__main__": - try: - start = sys.argv.index("--") + 1 - except ValueError: - start = 0 - action, *args = sys.argv[start:] - - if action == "FileInfo": - for info in args: - print("$", eval(info)) - elif action == "GetResults": - _getResults(args[0], args[1], args[2], args[3], args[4], args[5]) diff --git a/release/scripts/io/netrender/versioning.py b/release/scripts/io/netrender/versioning.py deleted file mode 100644 index d4f8522cce8..00000000000 --- a/release/scripts/io/netrender/versioning.py +++ /dev/null @@ -1,72 +0,0 @@ -import sys, os -import re -import subprocess - -from netrender.utils import * - -class AbstractVCS: - name = "ABSTRACT VCS" - def __init__(self): - pass - - def update(self, info): - """update(info) - Update a working copy to the specified revision. - If working copy doesn't exist, do a full get from server to create it. - [info] model.VersioningInfo instance, specifies the working path, remote path and version number.""" - pass - - def revision(self, path): - """revision(path) - return the current revision of the specified working copy path""" - pass - - def path(self, path): - """path(path) - return the remote path of the specified working copy path""" - pass - -class Subversion(AbstractVCS): - name = "Subversion" - def __init__(self): - super().__init__() - self.version_exp = re.compile("([0-9]*)") - self.path_exp = re.compile("URL: (.*)") - - def update(self, info): - if not os.path.exists(info.wpath): - base, folder = os.path.split(info.wpath) - - with DirectoryContext(base): - subprocess.call(["svn", "co", "%s@%s" % (info.rpath, str(info.revision)), folder]) - else: - with DirectoryContext(info.wpath): - subprocess.call(["svn", "up", "--accept", "theirs-full", "-r", str(info.revision)]) - - def revision(self, path): - if not os.path.exists(path): - return - - with DirectoryContext(path): - stdout = subprocess.check_output(["svnversion"]) - - match = self.version_exp.match(str(stdout, encoding="utf-8")) - - if match: - return match.group(1) - - def path(self, path): - if not os.path.exists(path): - return - - with DirectoryContext(path): - stdout = subprocess.check_output(["svn", "info"]) - - match = self.path_exp.search(str(stdout, encoding="utf-8")) - - if match: - return match.group(1) - -SYSTEMS = { - Subversion.name: Subversion() - } diff --git a/release/scripts/modules/add_object_utils.py b/release/scripts/modules/add_object_utils.py index 10707734bc4..1cf7fc2f4d5 100644 --- a/release/scripts/modules/add_object_utils.py +++ b/release/scripts/modules/add_object_utils.py @@ -83,6 +83,19 @@ def object_data_add(context, obdata, operator=None): obj_act = scene.objects.active + # XXX + # caused because entering editmodedoes not add a empty undo slot! + if context.user_preferences.edit.use_enter_edit_mode: + if not (obj_act and obj_act.mode == 'EDIT' and obj_act.type == obj_new.type): + _obdata = bpy.data.meshes.new(obdata.name) + obj_act = bpy.data.objects.new(_obdata.name, _obdata) + obj_act.matrix_world = obj_new.matrix_world + scene.objects.link(obj_act) + scene.objects.active = obj_act + bpy.ops.object.mode_set(mode='EDIT') + bpy.ops.ed.undo_push(message="Enter Editmode") # need empty undo step + # XXX + if obj_act and obj_act.mode == 'EDIT' and obj_act.type == obj_new.type: bpy.ops.mesh.select_all(action='DESELECT') bpy.ops.object.mode_set(mode='OBJECT') @@ -92,6 +105,7 @@ def object_data_add(context, obdata, operator=None): #scene.objects.active = obj_new bpy.ops.object.join() # join into the active. + bpy.data.meshes.remove(obdata) bpy.ops.object.mode_set(mode='EDIT') else: diff --git a/release/scripts/modules/addon_utils.py b/release/scripts/modules/addon_utils.py new file mode 100644 index 00000000000..3877f711b7f --- /dev/null +++ b/release/scripts/modules/addon_utils.py @@ -0,0 +1,326 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +__all__ = ( + "paths", + "modules", + "check", + "enable", + "disable", + "reset_all", + "module_bl_info", +) + +import bpy as _bpy + + +def paths(): + # RELEASE SCRIPTS: official scripts distributed in Blender releases + paths = _bpy.utils.script_paths("addons") + + # CONTRIB SCRIPTS: good for testing but not official scripts yet + # if folder addons_contrib/ exists, scripts in there will be loaded too + paths += _bpy.utils.script_paths("addons_contrib") + + # EXTERN SCRIPTS: external projects scripts + # if folder addons_extern/ exists, scripts in there will be loaded too + paths += _bpy.utils.script_paths("addons_extern") + + return paths + + +def modules(module_cache): + import os + import sys + import time + + path_list = paths() + + # fake module importing + def fake_module(mod_name, mod_path, speedy=True): + if _bpy.app.debug: + print("fake_module", mod_path, mod_name) + import ast + ModuleType = type(ast) + file_mod = open(mod_path, "r", encoding='UTF-8') + if speedy: + lines = [] + line_iter = iter(file_mod) + l = "" + while not l.startswith("bl_info"): + l = line_iter.readline() + if len(l) == 0: + break + while l.rstrip(): + lines.append(l) + l = line_iter.readline() + data = "".join(lines) + + else: + data = file_mod.read() + + file_mod.close() + + try: + ast_data = ast.parse(data, filename=mod_path) + except: + print("Syntax error 'ast.parse' can't read %r" % mod_path) + import traceback + traceback.print_exc() + ast_data = None + + body_info = None + + if ast_data: + for body in ast_data.body: + if body.__class__ == ast.Assign: + if len(body.targets) == 1: + if getattr(body.targets[0], "id", "") == "bl_info": + body_info = body + break + + if body_info: + try: + mod = ModuleType(mod_name) + mod.bl_info = ast.literal_eval(body.value) + mod.__file__ = mod_path + mod.__time__ = os.path.getmtime(mod_path) + except: + print("AST error in module %s" % mod_name) + import traceback + traceback.print_exc() + raise + + return mod + else: + return None + + modules_stale = set(module_cache.keys()) + + for path in path_list: + for mod_name, mod_path in _bpy.path.module_names(path): + modules_stale -= {mod_name} + mod = module_cache.get(mod_name) + if mod: + if mod.__time__ != os.path.getmtime(mod_path): + print("reloading addon:", mod_name, mod.__time__, os.path.getmtime(mod_path), mod_path) + del module_cache[mod_name] + mod = None + + if mod is None: + mod = fake_module(mod_name, mod_path) + if mod: + module_cache[mod_name] = mod + + # just incase we get stale modules, not likely + for mod_stale in modules_stale: + del module_cache[mod_stale] + del modules_stale + + mod_list = list(module_cache.values()) + mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name'])) + return mod_list + + +def check(module_name): + """ + Returns the loaded state of the addon. + + :arg module_name: The name of the addon and module. + :type module_name: string + :return: (loaded_default, loaded_state) + :rtype: tuple of booleans + """ + import sys + loaded_default = module_name in _bpy.context.user_preferences.addons + + mod = sys.modules.get(module_name) + loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis) + + if loaded_state is Ellipsis: + print("Warning: addon-module %r found module but without" + " __addon_enabled__ field, possible name collision from file: %r" % + (module_name, getattr(mod, "__file__", "<unknown>"))) + + loaded_state = False + + return loaded_default, loaded_state + + +def enable(module_name, default_set=True): + """ + Enables an addon by name. + + :arg module_name: The name of the addon and module. + :type module_name: string + :return: the loaded module or None on failier. + :rtype: module + """ + # note, this still gets added to _bpy_types.TypeMap + + import os + import sys + import bpy_types as _bpy_types + import imp + + def handle_error(): + import traceback + traceback.print_exc() + + # reload if the mtime changes + mod = sys.modules.get(module_name) + if mod: + mod.__addon_enabled__ = False + mtime_orig = getattr(mod, "__time__", 0) + mtime_new = os.path.getmtime(mod.__file__) + if mtime_orig != mtime_new: + print("module changed on disk:", mod.__file__, "reloading...") + + try: + imp.reload(mod) + except: + handle_error() + del sys.modules[module_name] + return None + mod.__addon_enabled__ = False + + # Split registering up into 3 steps so we can undo if it fails par way through + # 1) try import + try: + mod = __import__(module_name) + mod.__time__ = os.path.getmtime(mod.__file__) + mod.__addon_enabled__ = False + except: + handle_error() + return None + + # 2) try register collected modules + # removed, addons need to handle own registration now. + + # 3) try run the modules register function + try: + mod.register() + except: + handle_error() + del sys.modules[module_name] + return None + + # * OK loaded successfully! * + if default_set: + # just incase its enabled alredy + ext = _bpy.context.user_preferences.addons.get(module_name) + if not ext: + ext = _bpy.context.user_preferences.addons.new() + ext.module = module_name + + mod.__addon_enabled__ = True + + if _bpy.app.debug: + print("\taddon_utils.enable", mod.__name__) + + return mod + + +def disable(module_name, default_set=True): + """ + Disables an addon by name. + + :arg module_name: The name of the addon and module. + :type module_name: string + """ + import sys + import bpy_types as _bpy_types + + mod = sys.modules.get(module_name) + + # possible this addon is from a previous session and didnt load a module this time. + # so even if the module is not found, still disable the addon in the user prefs. + if mod: + mod.__addon_enabled__ = False + + try: + mod.unregister() + except: + import traceback + traceback.print_exc() + else: + print("addon_utils.disable", module_name, "not loaded") + + # could be in more then once, unlikely but better do this just incase. + addons = _bpy.context.user_preferences.addons + + if default_set: + while module_name in addons: + addon = addons.get(module_name) + if addon: + addons.remove(addon) + + if _bpy.app.debug: + print("\taddon_utils.disable", module_name) + + +def reset_all(reload_scripts=False): + """ + Sets the addon state based on the user preferences. + """ + import sys + import imp + + # RELEASE SCRIPTS: official scripts distributed in Blender releases + paths_list = paths() + + for path in paths_list: + _bpy.utils._sys_path_ensure(path) + for mod_name, mod_path in _bpy.path.module_names(path): + is_enabled, is_loaded = check(mod_name) + + # first check if reload is needed before changing state. + if reload_scripts: + mod = sys.modules.get(mod_name) + if mod: + imp.reload(mod) + + if is_enabled == is_loaded: + pass + elif is_enabled: + enable(mod_name) + elif is_loaded: + print("\taddon_utils.reset_all unloading", mod_name) + disable(mod_name) + + +def module_bl_info(mod, info_basis={"name": "", "author": "", "version": (), "blender": (), "api": 0, "location": "", "description": "", "wiki_url": "", "tracker_url": "", "support": 'COMMUNITY', "category": "", "warning": "", "show_expanded": False}): + addon_info = getattr(mod, "bl_info", {}) + + # avoid re-initializing + if "_init" in addon_info: + return addon_info + + if not addon_info: + mod.bl_info = addon_info + + for key, value in info_basis.items(): + addon_info.setdefault(key, value) + + if not addon_info["name"]: + addon_info["name"] = mod.__name__ + + addon_info["_init"] = None + return addon_info diff --git a/release/scripts/modules/animsys_refactor.py b/release/scripts/modules/animsys_refactor.py index 464df870e87..5336a8b2b35 100644 --- a/release/scripts/modules/animsys_refactor.py +++ b/release/scripts/modules/animsys_refactor.py @@ -28,6 +28,12 @@ The main function to use is: update_data_paths(...) IS_TESTING = False +def drepr(string): + # is there a less crappy way to do this in python?, re.escape also escapes + # single quotes strings so cant use it. + return '"%s"' % repr(string)[1:-1].replace("\"", "\\\"").replace("\\'", "'") + + class DataPathBuilder(object): __slots__ = ("data_path", ) """ Dummy class used to parse fcurve and driver data paths. @@ -40,7 +46,12 @@ class DataPathBuilder(object): return DataPathBuilder(self.data_path + (str_value, )) def __getitem__(self, key): - str_value = '["%s"]' % key + if type(key) is int: + str_value = '[%d]' % key + elif type(key) is str: + str_value = '[%s]' % drepr(key) + else: + raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key))) return DataPathBuilder(self.data_path + (str_value, )) def resolve(self, real_base, rna_update_from_map=None): @@ -170,6 +181,15 @@ def update_data_paths(rna_update): continue for fcurve in anim_data.drivers: + data_path = fcurve.data_path + data_path_new = find_path_new(anim_data_base, data_path, rna_update_dict, rna_update_from_map) + # print(data_path_new) + if data_path_new != data_path: + if not IS_TESTING: + fcurve.data_path = data_path_new + fcurve.driver.is_valid = True # reset to allow this to work again + print("driver-fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new)) + for var in fcurve.driver.variables: if var.type == 'SINGLE_PROP': for tar in var.targets: diff --git a/release/scripts/modules/bpy/__init__.py b/release/scripts/modules/bpy/__init__.py index 5c636d3a0df..1df8e9e5588 100644 --- a/release/scripts/modules/bpy/__init__.py +++ b/release/scripts/modules/bpy/__init__.py @@ -18,6 +18,10 @@ # <pep8 compliant> +""" +Give access to blender data and utility functions. +""" + # internal blender C module import _bpy from _bpy import types, props, app @@ -37,18 +41,6 @@ import sys as _sys def _main(): - ## security issue, dont allow the $CWD in the path. - ## note: this removes "" but not "." which are the same, security - ## people need to explain how this is even a fix. - # _sys.path[:] = filter(None, _sys.path) - - # because of how the console works. we need our own help() pager func. - # replace the bold function because it adds crazy chars - import pydoc - pydoc.getpager = lambda: pydoc.plainpager - pydoc.Helper.getline = lambda self, prompt: None - pydoc.TextDoc.use_bold = lambda self, text: text - # Possibly temp. addons path from os.path import join, dirname, normpath _sys.path.append(normpath(join(dirname(__file__), "..", "..", "addons", "modules"))) diff --git a/release/scripts/modules/bpy/path.py b/release/scripts/modules/bpy/path.py index 9a29b713882..f7e5b988cc8 100644 --- a/release/scripts/modules/bpy/path.py +++ b/release/scripts/modules/bpy/path.py @@ -144,6 +144,9 @@ def resolve_ncase(path): dirpath = os.path.dirname(dirpath) if not os.path.exists(dirpath): + if dirpath == path: + return path, False + dirpath, found = _ncase_path_found(dirpath) if not found: diff --git a/release/scripts/modules/bpy/utils.py b/release/scripts/modules/bpy/utils.py index e3e93203235..a2d7b9e502f 100644 --- a/release/scripts/modules/bpy/utils.py +++ b/release/scripts/modules/bpy/utils.py @@ -23,10 +23,7 @@ This module contains utility functions specific to blender but not assosiated with blenders internal data. """ -from _bpy import register_class -from _bpy import unregister_class - -from _bpy import blend_paths +from _bpy import register_class, unregister_class, blend_paths, resource_path from _bpy import script_paths as _bpy_script_paths from _bpy import user_resource as _user_resource @@ -34,24 +31,32 @@ import bpy as _bpy import os as _os import sys as _sys +import addon_utils as _addon_utils + +_script_module_dirs = "startup", "modules" + def _test_import(module_name, loaded_modules): - import traceback - import time + use_time = _bpy.app.debug + if module_name in loaded_modules: return None if "." in module_name: print("Ignoring '%s', can't import files containing multiple periods." % module_name) return None - t = time.time() + if use_time: + import time + t = time.time() + try: mod = __import__(module_name) except: + import traceback traceback.print_exc() return None - if _bpy.app.debug: + if use_time: print("time %s %.4f" % (module_name, time.time() - t)) loaded_modules.add(mod.__name__) # should match mod.__name__ too @@ -74,9 +79,6 @@ def modules_from_path(path, loaded_modules): :return: all loaded modules. :rtype: list """ - import traceback - import time - modules = [] for mod_name, mod_path in _bpy.path.module_names(path): @@ -100,10 +102,11 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): :arg refresh_scripts: only load scripts which are not already loaded as modules. :type refresh_scripts: bool """ - import traceback - import time + use_time = _bpy.app.debug - t_main = time.time() + if use_time: + import time + t_main = time.time() loaded_modules = set() @@ -117,7 +120,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): # note that they will only actually reload of the modification time changes. # this `wont` work for packages so... its not perfect. for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]: - addon_disable(module_name, default_set=False) + _addon_utils.disable(module_name, default_set=False) def register_module_call(mod): register = getattr(mod, "register", None) @@ -125,6 +128,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): try: register() except: + import traceback traceback.print_exc() else: print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__) @@ -135,6 +139,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): try: unregister() except: + import traceback traceback.print_exc() def test_reload(mod): @@ -148,6 +153,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): try: return imp.reload(mod) except: + import traceback traceback.print_exc() def test_register(mod): @@ -178,10 +184,8 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): _global_loaded_modules[:] = [] - user_path = user_script_path() - for base_path in script_paths(): - for path_subdir in ("", "ui", "op", "io", "keyingsets", "modules"): + for path_subdir in _script_module_dirs: path = _os.path.join(base_path, path_subdir) if _os.path.isdir(path): _sys_path_ensure(path) @@ -190,14 +194,11 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): if path_subdir == "modules": continue - if user_path != base_path and path_subdir == "": - continue # avoid loading 2.4x scripts - for mod in modules_from_path(path, loaded_modules): test_register(mod) - # deal with addons seperately - addon_reset_all(reload_scripts) + # deal with addons separately + _addon_utils.reset_all(reload_scripts) # run the active integration preset filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig") @@ -208,7 +209,7 @@ def load_scripts(reload_scripts=False, refresh_scripts=False): import gc print("gc.collect() -> %d" % gc.collect()) - if _bpy.app.debug: + if use_time: print("Python Script Load Time %.4f" % (time.time() - t_main)) @@ -227,27 +228,41 @@ def user_script_path(): return None -def script_paths(subdir=None, user=True): +def script_paths(subdir=None, user_pref=True, all=False): """ - Returns a list of valid script paths from the home directory and user preferences. + Returns a list of valid script paths. - Accepts any number of string arguments which are joined to make a path. + :arg subdir: Optional subdir. + :type subdir: string + :arg user_pref: Include the user preference script path. + :type user_pref: bool + :arg all: Include local, user and system paths rather just the paths blender uses. + :type all: bool + :return: script paths. + :rtype: list """ scripts = list(_scripts) # add user scripts dir - if user: + if user_pref: user_script_path = _bpy.context.user_preferences.filepaths.script_directory else: user_script_path = None - for path in _bpy_script_paths() + (user_script_path, ): + if all: + # all possible paths + base_paths = tuple(_os.path.join(resource_path(res), "scripts") for res in ('LOCAL', 'USER', 'SYSTEM')) + else: + # only paths blender uses + base_paths = _bpy_script_paths() + + for path in base_paths + (user_script_path, ): if path: path = _os.path.normpath(path) if path not in scripts and _os.path.isdir(path): scripts.append(path) - if not subdir: + if subdir is None: return scripts script_paths = [] @@ -259,6 +274,24 @@ def script_paths(subdir=None, user=True): return script_paths +def refresh_script_paths(): + """ + Run this after creating new script paths to update sys.path + """ + + for base_path in script_paths(): + for path_subdir in _script_module_dirs: + path = _os.path.join(base_path, path_subdir) + if _os.path.isdir(path): + _sys_path_ensure(path) + + for path in _addon_utils.paths(): + _sys_path_ensure(path) + path = _os.path.join(path, "modules") + if _os.path.isdir(path): + _sys_path_ensure(path) + + _presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths @@ -267,7 +300,7 @@ def preset_paths(subdir): Returns a list of paths for a specific preset. """ dirs = [] - for path in script_paths("presets"): + for path in script_paths("presets", all=True): directory = _os.path.join(path, subdir) if _os.path.isdir(directory): dirs.append(directory) @@ -322,176 +355,6 @@ def smpte_from_frame(frame, fps=None, fps_base=None): return smpte_from_seconds((frame * fps_base) / fps, fps) -def addon_check(module_name): - """ - Returns the loaded state of the addon. - - :arg module_name: The name of the addon and module. - :type module_name: string - :return: (loaded_default, loaded_state) - :rtype: tuple of booleans - """ - loaded_default = module_name in _bpy.context.user_preferences.addons - - mod = _sys.modules.get(module_name) - loaded_state = mod and getattr(mod, "__addon_enabled__", Ellipsis) - - if loaded_state is Ellipsis: - print("Warning: addon-module %r found module but without" - " __addon_enabled__ field, possible name collision from file: %r" % - (module_name, getattr(mod, "__file__", "<unknown>"))) - - loaded_state = False - - return loaded_default, loaded_state - - -def addon_enable(module_name, default_set=True): - """ - Enables an addon by name. - - :arg module_name: The name of the addon and module. - :type module_name: string - :return: the loaded module or None on failier. - :rtype: module - """ - # note, this still gets added to _bpy_types.TypeMap - - import os - import sys - import bpy_types as _bpy_types - import imp - - def handle_error(): - import traceback - traceback.print_exc() - - # reload if the mtime changes - mod = sys.modules.get(module_name) - if mod: - mod.__addon_enabled__ = False - mtime_orig = getattr(mod, "__time__", 0) - mtime_new = os.path.getmtime(mod.__file__) - if mtime_orig != mtime_new: - print("module changed on disk:", mod.__file__, "reloading...") - - try: - imp.reload(mod) - except: - handle_error() - del sys.modules[module_name] - return None - mod.__addon_enabled__ = False - - # Split registering up into 3 steps so we can undo if it fails par way through - # 1) try import - try: - mod = __import__(module_name) - mod.__time__ = os.path.getmtime(mod.__file__) - mod.__addon_enabled__ = False - except: - handle_error() - return None - - # 2) try register collected modules - # removed, addons need to handle own registration now. - - # 3) try run the modules register function - try: - mod.register() - except: - handle_error() - del sys.modules[module_name] - return None - - # * OK loaded successfully! * - if default_set: - # just incase its enabled alredy - ext = _bpy.context.user_preferences.addons.get(module_name) - if not ext: - ext = _bpy.context.user_preferences.addons.new() - ext.module = module_name - - mod.__addon_enabled__ = True - - if _bpy.app.debug: - print("\tbpy.utils.addon_enable", mod.__name__) - - return mod - - -def addon_disable(module_name, default_set=True): - """ - Disables an addon by name. - - :arg module_name: The name of the addon and module. - :type module_name: string - """ - import traceback - import bpy_types as _bpy_types - - mod = _sys.modules.get(module_name) - - # possible this addon is from a previous session and didnt load a module this time. - # so even if the module is not found, still disable the addon in the user prefs. - if mod: - mod.__addon_enabled__ = False - - try: - mod.unregister() - except: - traceback.print_exc() - else: - print("addon_disable", module_name, "not loaded") - - # could be in more then once, unlikely but better do this just incase. - addons = _bpy.context.user_preferences.addons - - if default_set: - while module_name in addons: - addon = addons.get(module_name) - if addon: - addons.remove(addon) - - if _bpy.app.debug: - print("\tbpy.utils.addon_disable", module_name) - - -def addon_reset_all(reload_scripts=False): - """ - Sets the addon state based on the user preferences. - """ - import imp - - # RELEASE SCRIPTS: official scripts distributed in Blender releases - paths = script_paths("addons") - - # CONTRIB SCRIPTS: good for testing but not official scripts yet - paths += script_paths("addons_contrib") - - # EXTERN SCRIPTS: external projects scripts - paths += script_paths("addons_extern") - - for path in paths: - _sys_path_ensure(path) - for mod_name, mod_path in _bpy.path.module_names(path): - is_enabled, is_loaded = addon_check(mod_name) - - # first check if reload is needed before changing state. - if reload_scripts: - mod = _sys.modules.get(mod_name) - if mod: - imp.reload(mod) - - if is_enabled == is_loaded: - pass - elif is_enabled: - addon_enable(mod_name) - elif is_loaded: - print("\taddon_reset_all unloading", mod_name) - addon_disable(mod_name) - - def preset_find(name, preset_path, display_name=False): if not name: return None @@ -518,12 +381,13 @@ def keyconfig_set(filepath): print("loading preset:", filepath) keyconfigs = _bpy.context.window_manager.keyconfigs - kc_orig = keyconfigs.active keyconfigs_old = keyconfigs[:] try: - exec(compile(open(filepath).read(), filepath, 'exec'), {"__file__": filepath}) + file = open(filepath) + exec(compile(file.read(), filepath, 'exec'), {"__file__": filepath}) + file.close() except: import traceback traceback.print_exc() @@ -583,48 +447,47 @@ def _bpy_module_classes(module, is_registered=False): typemap_list = _bpy_types.TypeMap.get(module, ()) i = 0 while i < len(typemap_list): - cls_weakref, path, line = typemap_list[i] + cls_weakref = typemap_list[i] cls = cls_weakref() if cls is None: del typemap_list[i] else: - if is_registered == ("bl_rna" in cls.__dict__): - yield (cls, path, line) + if is_registered == cls.is_registered: + yield cls i += 1 def register_module(module, verbose=False): - import traceback if verbose: print("bpy.utils.register_module(%r): ..." % module) - for cls, path, line in _bpy_module_classes(module, is_registered=False): + cls = None + for cls in _bpy_module_classes(module, is_registered=False): if verbose: - print(" %s of %s:%s" % (cls, path, line)) + print(" %r" % cls) try: register_class(cls) except: - print("bpy.utils.register_module(): failed to registering class '%s.%s'" % (cls.__module__, cls.__name__)) - print("\t", path, "line", line) + print("bpy.utils.register_module(): failed to registering class %r" % cls) + import traceback traceback.print_exc() if verbose: print("done.\n") - if "cls" not in locals(): + if cls is None: raise Exception("register_module(%r): defines no classes" % module) def unregister_module(module, verbose=False): - import traceback if verbose: print("bpy.utils.unregister_module(%r): ..." % module) - for cls, path, line in _bpy_module_classes(module, is_registered=True): + for cls in _bpy_module_classes(module, is_registered=True): if verbose: - print(" %s of %s:%s" % (cls, path, line)) + print(" %r" % cls) try: unregister_class(cls) except: - print("bpy.utils.unregister_module(): failed to unregistering class '%s.%s'" % (cls.__module__, cls.__name__)) - print("\t", path, "line", line) + print("bpy.utils.unregister_module(): failed to unregistering class %r" % cls) + import traceback traceback.print_exc() if verbose: - print("done.\n")
\ No newline at end of file + print("done.\n") diff --git a/release/scripts/modules/bpy_types.py b/release/scripts/modules/bpy_types.py index 4eb712a65cc..c3352dd33ad 100644 --- a/release/scripts/modules/bpy_types.py +++ b/release/scripts/modules/bpy_types.py @@ -23,9 +23,11 @@ import _bpy from mathutils import Vector StructRNA = bpy_types.Struct.__bases__[0] -StructMetaIDProp = _bpy.StructMetaIDProp +StructMetaPropGroup = _bpy.StructMetaPropGroup # StructRNA = bpy_types.Struct +bpy_types.BlendDataLibraries.load = _bpy._library_load + class Context(StructRNA): __slots__ = () @@ -188,7 +190,7 @@ class _GenericBone: @length.setter def length(self, value): - self.tail = self.head + ((self.tail - self.head).normalize() * value) + self.tail = self.head + ((self.tail - self.head).normalized() * value) @property def vector(self): @@ -258,15 +260,15 @@ class _GenericBone: return bones -class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaIDProp): +class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): __slots__ = () -class Bone(StructRNA, _GenericBone, metaclass=StructMetaIDProp): +class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): __slots__ = () -class EditBone(StructRNA, _GenericBone, metaclass=StructMetaIDProp): +class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup): __slots__ = () def align_orientation(self, other): @@ -278,19 +280,29 @@ class EditBone(StructRNA, _GenericBone, metaclass=StructMetaIDProp): self.tail = self.head + vec self.roll = other.roll - def transform(self, matrix): + def transform(self, matrix, scale=True, roll=True): """ Transform the the bones head, tail, roll and envalope (when the matrix has a scale component). - Expects a 4x4 or 3x3 matrix. + + :arg matrix: 3x3 or 4x4 transformation matrix. + :type matrix: :class:`Matrix` + :arg scale: Scale the bone envalope by the matrix. + :type scale: bool + :arg roll: Correct the roll to point in the same relative direction to the head and tail. + :type roll: bool """ from mathutils import Vector z_vec = Vector((0.0, 0.0, 1.0)) * self.matrix.to_3x3() self.tail = self.tail * matrix self.head = self.head * matrix - scalar = matrix.median_scale - self.head_radius *= scalar - self.tail_radius *= scalar - self.align_roll(z_vec * matrix) + + if scale: + scalar = matrix.median_scale + self.head_radius *= scalar + self.tail_radius *= scalar + + if roll: + self.align_roll(z_vec * matrix) def ord_ind(i1, i2): @@ -557,37 +569,49 @@ TypeMap = {} class RNAMeta(type): def __new__(cls, name, bases, classdict, **args): result = type.__new__(cls, name, bases, classdict) - if bases and bases[0] != StructRNA: - import traceback - import weakref + if bases and bases[0] is not StructRNA: + from _weakref import ref as ref module = result.__module__ # first part of packages only if "." in module: module = module[:module.index(".")] - sf = traceback.extract_stack(limit=2)[0] - - TypeMap.setdefault(module, []).append((weakref.ref(result), sf[0], sf[1])) + TypeMap.setdefault(module, []).append(ref(result)) return result + @property + def is_registered(cls): + return "bl_rna" in cls.__dict__ + + +class OrderedDictMini(dict): + def __init__(self, *args): + self.order = [] + dict.__init__(self, args) + + def __setitem__(self, key, val): + dict.__setitem__(self, key, val) + if key not in self.order: + self.order.append(key) -import collections + def __delitem__(self, key): + dict.__delitem__(self, key) + self.order.remove(key) -class RNAMetaIDProp(RNAMeta, StructMetaIDProp): +class RNAMetaPropGroup(RNAMeta, StructMetaPropGroup): pass class OrderedMeta(RNAMeta): - def __init__(cls, name, bases, attributes): - super(OrderedMeta, cls).__init__(name, bases, attributes) - cls.order = list(attributes.keys()) + if attributes.__class__ is OrderedDictMini: + cls.order = attributes.order def __prepare__(name, bases, **kwargs): - return collections.OrderedDict() + return OrderedDictMini() # collections.OrderedDict() # Only defined so operators members can be used by accessing self.order @@ -634,7 +658,7 @@ class Macro(StructRNA, metaclass=OrderedMeta): return ops.macro_define(self, opname) -class IDPropertyGroup(StructRNA, metaclass=RNAMetaIDProp): +class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup): __slots__ = () @@ -656,6 +680,9 @@ class _GenericUI: if draw_funcs is None: def draw_ls(self, context): + # ensure menus always get default context + operator_context_default = self.layout.operator_context + for func in draw_ls._draw_funcs: # so bad menu functions dont stop the entire menu from drawing. try: @@ -664,6 +691,8 @@ class _GenericUI: import traceback traceback.print_exc() + self.layout.operator_context = operator_context_default + draw_funcs = draw_ls._draw_funcs = [cls.draw] cls.draw = draw_ls @@ -671,7 +700,7 @@ class _GenericUI: @classmethod def append(cls, draw_func): - """Prepend an draw function to this menu, takes the same arguments as the menus draw function.""" + """Append a draw function to this menu, takes the same arguments as the menus draw function.""" draw_funcs = cls._dyn_ui_initialize() draw_funcs.append(draw_func) diff --git a/release/scripts/modules/bpyml.py b/release/scripts/modules/bpyml.py index f1f72d50fd2..fdf5172a0b3 100644 --- a/release/scripts/modules/bpyml.py +++ b/release/scripts/modules/bpyml.py @@ -160,9 +160,9 @@ if __name__ == "__main__": from bpyml_test import * draw = [ - ui() [ - split() [ - column() [ + ui()[ + split()[ + column()[ prop(data='context.scene.render', property='use_stamp_time', text='Time'), prop(data='context.scene.render', property='use_stamp_date', text='Date'), prop(data='context.scene.render', property='use_stamp_render_time', text='RenderTime'), @@ -173,7 +173,7 @@ if __name__ == "__main__": prop(data='context.scene.render', property='use_stamp_marker', text='Marker'), prop(data='context.scene.render', property='use_stamp_sequencer_strip', text='Seq. Strip') ], - column() [ + column()[ active(expr='context.scene.render.use_stamp'), prop(data='context.scene.render', property='stamp_foreground', slider=True), prop(data='context.scene.render', property='stamp_background', slider=True), @@ -181,9 +181,9 @@ if __name__ == "__main__": prop(data='context.scene.render', property='stamp_font_size', text='Font Size') ] ], - split(percentage=0.2) [ + split(percentage=0.2)[ prop(data='context.scene.render', property='use_stamp_note', text='Note'), - row() [ + row()[ active(expr='context.scene.render.use_stamp_note'), prop(data='context.scene.render', property='stamp_note_text', text='') ] diff --git a/release/scripts/modules/bpyml_ui.py b/release/scripts/modules/bpyml_ui.py index 2462dd60e3e..1e0522974d1 100644 --- a/release/scripts/modules/bpyml_ui.py +++ b/release/scripts/modules/bpyml_ui.py @@ -65,7 +65,7 @@ def _parse_rna_args(base, py_node): def _call_recursive(context, base, py_node): - prop = base.bl_rna.properties.get(py_node[TAG]) + # prop = base.bl_rna.properties.get(py_node[TAG]) if py_node[TAG] in base.bl_rna.properties: value = py_node[ARGS].get("expr") if value: @@ -73,7 +73,7 @@ def _call_recursive(context, base, py_node): setattr(base, py_node[TAG], value) else: value = py_node[ARGS]['value'] # have to have this - setattr(base, name, value) + setattr(base, py_node[TAG], value) else: args = _parse_rna_args(base, py_node) func_new = getattr(base, py_node[TAG]) diff --git a/release/scripts/op/console_python.py b/release/scripts/modules/console_python.py index 98d3f3f74a3..3048fa1d597 100644 --- a/release/scripts/op/console_python.py +++ b/release/scripts/modules/console_python.py @@ -33,6 +33,20 @@ def add_scrollback(text, text_type): type=text_type) +def replace_help(namespace): + def _help(*args): + # because of how the console works. we need our own help() pager func. + # replace the bold function because it adds crazy chars + import pydoc + pydoc.getpager = lambda: pydoc.plainpager + pydoc.Helper.getline = lambda self, prompt: None + pydoc.TextDoc.use_bold = lambda self, text: text + + pydoc.help(*args) + + namespace["help"] = _help + + def get_console(console_id): ''' helper function for console operators @@ -83,11 +97,13 @@ def get_console(console_id): namespace["bpy"] = bpy namespace["C"] = bpy.context - namespace.update(__import__("mathutils").__dict__) # from mathutils import * - namespace.update(__import__("math").__dict__) # from math import * + replace_help(namespace) console = InteractiveConsole(locals=namespace, filename="<blender_console>") + console.push("from mathutils import *") + console.push("from math import *") + if _BPY_MAIN_OWN: console._bpy_main_mod = bpy_main_mod @@ -224,12 +240,20 @@ def autocomplete(context): # This function isnt aware of the text editor or being an operator # just does the autocomp then copy its results back - current_line.body, current_line.current_character, scrollback = \ - intellisense.expand( - line=current_line.body, + result = intellisense.expand( + line=line, cursor=current_line.current_character, namespace=console.locals, private=bpy.app.debug) + + line_new = result[0] + current_line.body, current_line.current_character, scrollback = result + del result + + # update sel. setting body should really do this! + ofs = len(line_new) - len(line) + sc.select_start += ofs + sc.select_end += ofs except: # unlikely, but this can happen with unicode errors for example. # or if the api attribute access its self causes an error. @@ -272,23 +296,11 @@ def banner(context): add_scrollback("Execute: Enter", 'OUTPUT') add_scrollback("Autocomplete: Ctrl+Space", 'OUTPUT') add_scrollback("Ctrl +/- Wheel: Zoom", 'OUTPUT') - add_scrollback("Builtin Modules: bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bgl, blf, mathutils", 'OUTPUT') + add_scrollback("Builtin Modules: bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bpy.utils, bgl, blf, mathutils", 'OUTPUT') add_scrollback("Convenience Imports: from mathutils import *; from math import *", 'OUTPUT') add_scrollback("", 'OUTPUT') - add_scrollback(" WARNING!!! Blender 2.5 API is subject to change, see API reference for more info.", 'ERROR') - add_scrollback("", 'OUTPUT') + # add_scrollback(" WARNING!!! Blender 2.5 API is subject to change, see API reference for more info.", 'ERROR') + # add_scrollback("", 'OUTPUT') sc.prompt = PROMPT return {'FINISHED'} - - -def register(): - pass - - -def unregister(): - pass - - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/console_shell.py b/release/scripts/modules/console_shell.py index 2c5b48acd34..7a6f45c426f 100644 --- a/release/scripts/op/console_shell.py +++ b/release/scripts/modules/console_shell.py @@ -76,14 +76,3 @@ def banner(context): sc.prompt = os.getcwd() + PROMPT return {'FINISHED'} - - -def register(): - pass - - -def unregister(): - pass - -if __name__ == "__main__": - register() diff --git a/release/scripts/modules/image_utils.py b/release/scripts/modules/image_utils.py index d74c89ac173..39e49ee1f96 100644 --- a/release/scripts/modules/image_utils.py +++ b/release/scripts/modules/image_utils.py @@ -23,5 +23,5 @@ def image_load(filepath, dirpath, place_holder=False, recursive=False, convert_c import bpy try: return bpy.data.images.load(filepath) - except SystemError: + except RuntimeError: return bpy.data.images.new("Untitled", 128, 128) diff --git a/release/scripts/modules/io_utils.py b/release/scripts/modules/io_utils.py index 39b38669188..820d7cfa39d 100644 --- a/release/scripts/modules/io_utils.py +++ b/release/scripts/modules/io_utils.py @@ -19,13 +19,17 @@ # <pep8 compliant> import bpy -from bpy.props import * +from bpy.props import StringProperty, BoolProperty, EnumProperty class ExportHelper: filepath = StringProperty(name="File Path", description="Filepath used for exporting the file", maxlen=1024, default="", subtype='FILE_PATH') check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) + # subclasses can override with decorator + # True == use ext, False == no ext, None == do nothing. + check_extension = True + def invoke(self, context, event): import os if not self.filepath: @@ -41,12 +45,18 @@ class ExportHelper: return {'RUNNING_MODAL'} def check(self, context): - filepath = bpy.path.ensure_ext(self.filepath, self.filename_ext) + check_extension = self.check_extension + + if check_extension is None: + return False + + filepath = bpy.path.ensure_ext(self.filepath, self.filename_ext if check_extension else "") + if filepath != self.filepath: self.filepath = filepath return True - else: - return False + + return False class ImportHelper: @@ -57,6 +67,86 @@ class ImportHelper: return {'RUNNING_MODAL'} +# Axis conversion function, not pretty LUT +# use lookup tabes to convert between any axis +_axis_convert_matrix = ( + ((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, 1.0)), + ((-1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, -1.0, 0.0)), + ((-1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)), + ((-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0)), + ((0.0, -1.0, 0.0), (-1.0, 0.0, 0.0), (0.0, 0.0, -1.0)), + ((0.0, -1.0, 0.0), (0.0, 0.0, -1.0), (1.0, 0.0, 0.0)), + ((0.0, -1.0, 0.0), (0.0, 0.0, 1.0), (-1.0, 0.0, 0.0)), + ((0.0, -1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)), + ((0.0, 0.0, -1.0), (-1.0, 0.0, 0.0), (0.0, 1.0, 0.0)), + ((0.0, 0.0, -1.0), (0.0, -1.0, 0.0), (-1.0, 0.0, 0.0)), + ((0.0, 0.0, -1.0), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0)), + ((0.0, 0.0, -1.0), (1.0, 0.0, 0.0), (0.0, -1.0, 0.0)), + ((0.0, 0.0, 1.0), (-1.0, 0.0, 0.0), (0.0, -1.0, 0.0)), + ((0.0, 0.0, 1.0), (0.0, -1.0, 0.0), (1.0, 0.0, 0.0)), + ((0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (-1.0, 0.0, 0.0)), + ((0.0, 0.0, 1.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)), + ((0.0, 1.0, 0.0), (-1.0, 0.0, 0.0), (0.0, 0.0, 1.0)), + ((0.0, 1.0, 0.0), (0.0, 0.0, -1.0), (-1.0, 0.0, 0.0)), + ((0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0)), + ((0.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, -1.0)), + ((1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)), + ((1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, 1.0, 0.0)), + ((1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, -1.0, 0.0)), + ) + +# store args as a single int +# (X Y Z -X -Y -Z) --> (0, 1, 2, 3, 4, 5) +# each value is ((src_forward, src_up), (dst_forward, dst_up)) +# where all 4 values are or'd into a single value... +# (i1<<0 | i1<<3 | i1<<6 | i1<<9) +_axis_convert_lut = ( + {0x5c, 0x9a, 0x119, 0x15d, 0x20b, 0x2a2, 0x2c8, 0x365, 0x413, 0x46c, 0x4d0, 0x529, 0x644, 0x682, 0x701, 0x745, 0x823, 0x88a, 0x8e0, 0x94d, 0xa2b, 0xa54, 0xae8, 0xb11}, + {0x9c, 0xac, 0x159, 0x169, 0x22b, 0x2e8, 0x40b, 0x465, 0x4c8, 0x522, 0x684, 0x694, 0x741, 0x751, 0x813, 0x8d0, 0xa23, 0xa4d, 0xae0, 0xb0a}, + {0x99, 0xa9, 0x15c, 0x16c, 0x213, 0x2d0, 0x423, 0x44a, 0x4e0, 0x50d, 0x681, 0x691, 0x744, 0x754, 0x82b, 0x8e8, 0xa0b, 0xa62, 0xac8, 0xb25}, + {0x59, 0x85, 0x11c, 0x142, 0x223, 0x28d, 0x2e0, 0x34a, 0x42b, 0x469, 0x4e8, 0x52c, 0x641, 0x69d, 0x704, 0x75a, 0x80b, 0x8a5, 0x8c8, 0x962, 0xa13, 0xa51, 0xad0, 0xb14}, + {0xa5, 0x162, 0x21c, 0x285, 0x2d9, 0x342, 0x463, 0x46b, 0x520, 0x528, 0x68d, 0x74a, 0x804, 0x89d, 0x8c1, 0x95a, 0xa4b, 0xa53, 0xb08, 0xb10}, + {0x4b, 0x53, 0x108, 0x110, 0x29c, 0x2ac, 0x359, 0x369, 0x41a, 0x422, 0x4dd, 0x4e5, 0x663, 0x66b, 0x720, 0x728, 0x884, 0x894, 0x941, 0x951, 0xa02, 0xa0a, 0xac5, 0xacd}, + {0x63, 0x6b, 0x120, 0x128, 0x299, 0x2a9, 0x35c, 0x36c, 0x405, 0x40d, 0x4c2, 0x4ca, 0x64b, 0x653, 0x708, 0x710, 0x881, 0x891, 0x944, 0x954, 0xa1d, 0xa25, 0xada, 0xae2}, + {0x8a, 0x14d, 0x219, 0x29a, 0x2dc, 0x35d, 0x44b, 0x453, 0x508, 0x510, 0x6a2, 0x765, 0x801, 0x882, 0x8c4, 0x945, 0xa63, 0xa6b, 0xb20, 0xb28}, + {0x5a, 0x62, 0x8b, 0x11d, 0x125, 0x148, 0x22c, 0x28b, 0x293, 0x2e9, 0x348, 0x350, 0x41c, 0x42c, 0x45a, 0x4d9, 0x4e9, 0x51d, 0x642, 0x64a, 0x6a3, 0x705, 0x70d, 0x760, 0x814, 0x8a3, 0x8ab, 0x8d1, 0x960, 0x968, 0xa04, 0xa14, 0xa42, 0xac1, 0xad1, 0xb05}, + {0x54, 0xab, 0x111, 0x168, 0x21d, 0x225, 0x2da, 0x2e2, 0x45c, 0x519, 0x66c, 0x693, 0x729, 0x750, 0x805, 0x80d, 0x8c2, 0x8ca, 0xa44, 0xb01}, + {0x51, 0x93, 0x114, 0x150, 0x202, 0x20a, 0x2c5, 0x2cd, 0x459, 0x51c, 0x669, 0x6ab, 0x72c, 0x768, 0x81a, 0x822, 0x8dd, 0x8e5, 0xa41, 0xb04}, + {0x45, 0x4d, 0xa3, 0x102, 0x10a, 0x160, 0x229, 0x2a3, 0x2ab, 0x2ec, 0x360, 0x368, 0x419, 0x429, 0x445, 0x4dc, 0x4ec, 0x502, 0x65d, 0x665, 0x68b, 0x71a, 0x722, 0x748, 0x811, 0x88b, 0x893, 0x8d4, 0x948, 0x950, 0xa01, 0xa11, 0xa5d, 0xac4, 0xad4, 0xb1a}, + {0x5d, 0x65, 0xa0, 0x11a, 0x122, 0x163, 0x214, 0x2a0, 0x2a8, 0x2d1, 0x363, 0x36b, 0x404, 0x414, 0x45d, 0x4c1, 0x4d1, 0x51a, 0x645, 0x64d, 0x688, 0x702, 0x70a, 0x74b, 0x82c, 0x888, 0x890, 0x8e9, 0x94b, 0x953, 0xa1c, 0xa2c, 0xa45, 0xad9, 0xae9, 0xb02}, + {0x6c, 0x90, 0x129, 0x153, 0x21a, 0x222, 0x2dd, 0x2e5, 0x444, 0x501, 0x654, 0x6a8, 0x711, 0x76b, 0x802, 0x80a, 0x8c5, 0x8cd, 0xa5c, 0xb19}, + {0x69, 0xa8, 0x12c, 0x16b, 0x205, 0x20d, 0x2c2, 0x2ca, 0x441, 0x504, 0x651, 0x690, 0x714, 0x753, 0x81d, 0x825, 0x8da, 0x8e2, 0xa59, 0xb1c}, + {0x42, 0x4a, 0x88, 0x105, 0x10d, 0x14b, 0x211, 0x288, 0x290, 0x2d4, 0x34b, 0x353, 0x401, 0x411, 0x442, 0x4c4, 0x4d4, 0x505, 0x65a, 0x662, 0x6a0, 0x71d, 0x725, 0x763, 0x829, 0x8a0, 0x8a8, 0x8ec, 0x963, 0x96b, 0xa19, 0xa29, 0xa5a, 0xadc, 0xaec, 0xb1d}, + {0xa2, 0x165, 0x204, 0x282, 0x2c1, 0x345, 0x448, 0x450, 0x50b, 0x513, 0x68a, 0x74d, 0x81c, 0x89a, 0x8d9, 0x95d, 0xa60, 0xa68, 0xb23, 0xb2b}, + {0x60, 0x68, 0x123, 0x12b, 0x284, 0x294, 0x341, 0x351, 0x41d, 0x425, 0x4da, 0x4e2, 0x648, 0x650, 0x70b, 0x713, 0x89c, 0x8ac, 0x959, 0x969, 0xa05, 0xa0d, 0xac2, 0xaca}, + {0x48, 0x50, 0x10b, 0x113, 0x281, 0x291, 0x344, 0x354, 0x402, 0x40a, 0x4c5, 0x4cd, 0x660, 0x668, 0x723, 0x72b, 0x899, 0x8a9, 0x95c, 0x96c, 0xa1a, 0xa22, 0xadd, 0xae5}, + {0x8d, 0x14a, 0x201, 0x29d, 0x2c4, 0x35a, 0x460, 0x468, 0x523, 0x52b, 0x6a5, 0x762, 0x819, 0x885, 0x8dc, 0x942, 0xa48, 0xa50, 0xb0b, 0xb13}, + {0x44, 0x9d, 0x101, 0x15a, 0x220, 0x2a5, 0x2e3, 0x362, 0x428, 0x454, 0x4eb, 0x511, 0x65c, 0x685, 0x719, 0x742, 0x808, 0x88d, 0x8cb, 0x94a, 0xa10, 0xa6c, 0xad3, 0xb29}, + {0x84, 0x94, 0x141, 0x151, 0x210, 0x2d3, 0x420, 0x462, 0x4e3, 0x525, 0x69c, 0x6ac, 0x759, 0x769, 0x828, 0x8eb, 0xa08, 0xa4a, 0xacb, 0xb0d}, + {0x81, 0x91, 0x144, 0x154, 0x228, 0x2eb, 0x408, 0x44d, 0x4cb, 0x50a, 0x699, 0x6a9, 0x75c, 0x76c, 0x810, 0x8d3, 0xa20, 0xa65, 0xae3, 0xb22}, + ) + +_axis_convert_num = {'X': 0, 'Y': 1, 'Z': 2, '-X': 3, '-Y': 4, '-Z': 5} + + +def axis_conversion(from_forward='Y', from_up='Z', to_forward='Y', to_up='Z'): + """ + Each argument us an axis in ['X', 'Y', 'Z', '-X', '-Y', '-Z'] + where the first 2 are a source and the second 2 are the target. + """ + from mathutils import Matrix + from functools import reduce + + if from_forward == to_forward and from_up == to_up: + return Matrix().to_3x3() + + value = reduce(int.__or__, (_axis_convert_num[a] << (i * 3) for i, a in enumerate((from_forward, from_up, to_forward, to_up)))) + for i, axis_lut in enumerate(_axis_convert_lut): + if value in axis_lut: + return Matrix(_axis_convert_matrix[i]) + assert("internal error") + + # limited replacement for BPyImage.comprehensiveImageLoad def load_image(imagepath, dirname): import os @@ -81,14 +171,14 @@ def create_derived_objects(scene, ob): return False, None if ob.dupli_type != 'NONE': - ob.create_dupli_list(scene) + ob.dupli_list_create(scene) return True, [(dob.object, dob.matrix) for dob in ob.dupli_list] else: return False, [(ob, ob.matrix_world)] def free_derived_objects(ob): - ob.free_dupli_list() + ob.dupli_list_clear() def unpack_list(list_of_tuples): @@ -116,3 +206,99 @@ def unpack_face_list(list_of_tuples): flat_ls[i:i + len(t)] = t i += 4 return flat_ls + + +path_reference_mode = EnumProperty( + name="Path Mode", + description="Method used to reference paths", + items=(('AUTO', "Auto", "Use Relative paths with subdirectories only"), + ('ABSOLUTE', "Absolute", "Always write absolute paths"), + ('RELATIVE', "Relative", "Always write relative patsh (where possible)"), + ('MATCH', "Match", "Match Absolute/Relative setting with input path"), + ('STRIP', "Strip Path", "Filename only"), + ('COPY', "Copy", "copy the file to the destination path (or subdirectory)"), + ), + default='AUTO' + ) + + +def path_reference(filepath, base_src, base_dst, mode='AUTO', copy_subdir="", copy_set=None): + """ + Return a filepath relative to a destination directory, for use with + exporters. + + :arg filepath: the file path to return, supporting blenders relative '//' prefix. + :type filepath: string + :arg base_src: the directory the *filepath* is relative too (normally the blend file). + :type base_src: string + :arg base_dst: the directory the *filepath* will be referenced from (normally the export path). + :type base_dst: string + :arg mode: the method used get the path in ['AUTO', 'ABSOLUTE', 'RELATIVE', 'MATCH', 'STRIP', 'COPY'] + :type mode: string + :arg copy_subdir: the subdirectory of *base_dst* to use when mode='COPY'. + :type copy_subdir: string + :arg copy_set: collect from/to pairs when mode='COPY', pass to *path_reference_copy* when exportign is done. + :type copy_set: set + :return: the new filepath. + :rtype: string + """ + import os + is_relative = filepath.startswith("//") + filepath_abs = os.path.normpath(bpy.path.abspath(filepath, base_src)) + + if mode in ('ABSOLUTE', 'RELATIVE', 'STRIP'): + pass + elif mode == 'MATCH': + mode = 'RELATIVE' if is_relative else 'ABSOLUTE' + elif mode == 'AUTO': + mode = 'RELATIVE' if bpy.path.is_subdir(filepath, base_dst) else 'ABSOLUTE' + elif mode == 'COPY': + if copy_subdir: + subdir_abs = os.path.join(os.path.normpath(base_dst), copy_subdir) + else: + subdir_abs = os.path.normpath(base_dst) + + filepath_cpy = os.path.join(subdir_abs, os.path.basename(filepath)) + + copy_set.add((filepath_abs, filepath_cpy)) + + filepath_abs = filepath_cpy + mode = 'RELATIVE' + else: + Excaption("invalid mode given %r" % mode) + + if mode == 'ABSOLUTE': + return filepath_abs + elif mode == 'RELATIVE': + return os.path.relpath(filepath_abs, base_dst) + elif mode == 'STRIP': + return os.path.basename(filepath_abs) + + +def path_reference_copy(copy_set, report=print): + """ + Execute copying files of path_reference + + :arg copy_set: set of (from, to) pairs to copy. + :type copy_set: set + :arg report: function used for reporting warnings, takes a string argument. + :type report: function + """ + if not copy_set: + return + + import os + import shutil + + for file_src, file_dst in copy_set: + if not os.path.exists(file_src): + report("missing %r, not copying" % file_src) + elif os.path.exists(file_dst) and os.path.samefile(file_src, file_dst): + pass + else: + dir_to = os.path.dirname(file_dst) + + if not os.path.isdir(dir_to): + os.makedirs(dir_to) + + shutil.copy(file_src, file_dst) diff --git a/release/scripts/modules/keyingsets_utils.py b/release/scripts/modules/keyingsets_utils.py index 00ad45cf9bb..dc61ce2a4af 100644 --- a/release/scripts/modules/keyingsets_utils.py +++ b/release/scripts/modules/keyingsets_utils.py @@ -43,7 +43,7 @@ import bpy # Append the specified property name on the the existing path def path_add_property(path, prop): - if len(path): + if path: return path + "." + prop else: return prop @@ -52,16 +52,21 @@ def path_add_property(path, prop): # Poll Callbacks -# selected objects +# selected objects (active object must be in object mode) def RKS_POLL_selected_objects(ksi, context): - return context.active_object or len(context.selected_objects) + ob = context.active_object + if ob: + return ob.mode == 'OBJECT' + else: + return bool(context.selected_objects) # selected bones def RKS_POLL_selected_bones(ksi, context): # we must be in Pose Mode, and there must be some bones selected - if (context.active_object) and (context.active_object.mode == 'POSE'): - if context.active_pose_bone or len(context.selected_pose_bones): + ob = context.active_object + if ob and ob.mode == 'POSE': + if context.active_pose_bone or context.selected_pose_bones: return True # nothing selected @@ -78,13 +83,20 @@ def RKS_POLL_selected_items(ksi, context): # all selected objects or pose bones, depending on which we've got def RKS_ITER_selected_item(ksi, context, ks): - if (context.active_object) and (context.active_object.mode == 'POSE'): + ob = context.active_object + if ob and ob.mode == 'POSE': for bone in context.selected_pose_bones: ksi.generate(context, ks, bone) else: for ob in context.selected_objects: ksi.generate(context, ks, ob) + +# all select objects only +def RKS_ITER_selected_objects(ksi, context, ks): + for ob in context.selected_objects: + ksi.generate(context, ks, ob) + ########################### # Generate Callbacks diff --git a/release/scripts/modules/mesh_utils.py b/release/scripts/modules/mesh_utils.py new file mode 100644 index 00000000000..5bacff7b0cc --- /dev/null +++ b/release/scripts/modules/mesh_utils.py @@ -0,0 +1,69 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + + +def mesh_linked_faces(mesh): + ''' + Splits the mesh into connected parts, + these parts are returned as lists of faces. + used for seperating cubes from other mesh elements in the 1 mesh + ''' + + # Build vert face connectivity + vert_faces = [[] for i in range(len(mesh.vertices))] + for f in mesh.faces: + for v in f.vertices: + vert_faces[v].append(f) + + # sort faces into connectivity groups + face_groups = [[f] for f in mesh.faces] + face_mapping = list(range(len(mesh.faces))) # map old, new face location + + # Now clump faces iterativly + ok = True + while ok: + ok = False + + for i, f in enumerate(mesh.faces): + mapped_index = face_mapping[f.index] + mapped_group = face_groups[mapped_index] + + for v in f.vertices: + for nxt_f in vert_faces[v]: + if nxt_f != f: + nxt_mapped_index = face_mapping[nxt_f.index] + + # We are not a part of the same group + if mapped_index != nxt_mapped_index: + ok = True + + # Assign mapping to this group so they all map to this group + for grp_f in face_groups[nxt_mapped_index]: + face_mapping[grp_f.index] = mapped_index + + # Move faces into this group + mapped_group.extend(face_groups[nxt_mapped_index]) + + # remove reference to the list + face_groups[nxt_mapped_index] = None + + # return all face groups that are not null + # this is all the faces that are connected in their own lists. + return [fg for fg in face_groups if fg] diff --git a/release/scripts/modules/rna_info.py b/release/scripts/modules/rna_info.py index e0298d30aa2..93a344f4b09 100644 --- a/release/scripts/modules/rna_info.py +++ b/release/scripts/modules/rna_info.py @@ -152,6 +152,14 @@ class InfoStructRNA: functions.append((identifier, attr)) return functions + def get_py_c_functions(self): + import types + functions = [] + for identifier, attr in self._get_py_visible_attrs(): + if type(attr) in (types.BuiltinMethodType, types.BuiltinFunctionType): + functions.append((identifier, attr)) + return functions + def __str__(self): txt = "" @@ -199,10 +207,15 @@ class InfoPropertyRNA: self.fixed_type = None if self.type == "enum": - self.enum_items[:] = rna_prop.items.keys() + self.enum_items[:] = rna_prop.enum_items.keys() + self.is_enum_flag = rna_prop.is_enum_flag + else: + self.is_enum_flag = False if self.array_length: self.default = tuple(getattr(rna_prop, "default_array", ())) + elif self.type == "enum" and self.is_enum_flag: + self.default = getattr(rna_prop, "default_flag", set()) else: self.default = getattr(rna_prop, "default", None) self.default_str = "" # fallback @@ -214,7 +227,10 @@ class InfoPropertyRNA: elif self.type == "string": self.default_str = "\"%s\"" % self.default elif self.type == "enum": - self.default_str = "'%s'" % self.default + if self.is_enum_flag: + self.default_str = "%r" % self.default # repr or set() + else: + self.default_str = "'%s'" % self.default elif self.array_length: self.default_str = '' # special case for floats @@ -237,7 +253,7 @@ class InfoPropertyRNA: return "%s=%s" % (self.identifier, default) return self.identifier - def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s"): + def get_type_description(self, as_ret=False, as_arg=False, class_fmt="%s", collection_id="Collection"): type_str = "" if self.fixed_type is None: type_str += self.type @@ -247,7 +263,10 @@ class InfoPropertyRNA: if self.type in ("float", "int"): type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max)) elif self.type == "enum": - type_str += " in [%s]" % ", ".join(("'%s'" % s) for s in self.enum_items) + if self.is_enum_flag: + type_str += " set in {%s}" % ", ".join(("'%s'" % s) for s in self.enum_items) + else: + type_str += " in [%s]" % ", ".join(("'%s'" % s) for s in self.enum_items) if not (as_arg or as_ret): # write default property, ignore function args for this @@ -258,9 +277,9 @@ class InfoPropertyRNA: else: if self.type == "collection": if self.collection_type: - collection_str = (class_fmt % self.collection_type.identifier) + " collection of " + collection_str = (class_fmt % self.collection_type.identifier) + (" %s of " % collection_id) else: - collection_str = "Collection of " + collection_str = "%s of " % collection_id else: collection_str = "" diff --git a/release/scripts/modules/sys_info.py b/release/scripts/modules/sys_info.py index 303277a5d75..1272d81872d 100644 --- a/release/scripts/modules/sys_info.py +++ b/release/scripts/modules/sys_info.py @@ -50,11 +50,9 @@ def textWrap(text, length=70): def write_sysinfo(op): output_filename = "system-info.txt" - warnings = 0 - notices = 0 - if output_filename in bpy.data.texts.keys(): - output = bpy.data.texts[output_filename] + output = bpy.data.texts.get(output_filename) + if output: output.clear() else: output = bpy.data.texts.new(name=output_filename) @@ -96,16 +94,19 @@ def write_sysinfo(op): output.write('autosave: {}\n'.format(bpy.utils.user_resource('AUTOSAVE'))) output.write('tempdir: {}\n'.format(bpy.app.tempdir)) - output.write('\nOpenGL\n') - output.write(lilies) - output.write('renderer:\t{}\n'.format(bgl.glGetString(bgl.GL_RENDERER))) - output.write('vendor:\t\t{}\n'.format(bgl.glGetString(bgl.GL_VENDOR))) - output.write('version:\t{}\n'.format(bgl.glGetString(bgl.GL_VERSION))) - output.write('extensions:\n') - - glext = bgl.glGetString(bgl.GL_EXTENSIONS) - glext = textWrap(glext, 70) - for l in glext: - output.write('\t\t{}\n'.format(l)) + if bpy.app.background: + output.write('\nOpenGL: missing, background mode\n') + else: + output.write('\nOpenGL\n') + output.write(lilies) + output.write('renderer:\t{}\n'.format(bgl.glGetString(bgl.GL_RENDERER))) + output.write('vendor:\t\t{}\n'.format(bgl.glGetString(bgl.GL_VENDOR))) + output.write('version:\t{}\n'.format(bgl.glGetString(bgl.GL_VERSION))) + output.write('extensions:\n') + + glext = bgl.glGetString(bgl.GL_EXTENSIONS) + glext = textWrap(glext, 70) + for l in glext: + output.write('\t\t{}\n'.format(l)) op.report({'INFO'}, "System information generated in 'system-info.txt'") diff --git a/release/scripts/op/fcurve_euler_filter.py b/release/scripts/op/fcurve_euler_filter.py deleted file mode 100644 index 6332bbe309c..00000000000 --- a/release/scripts/op/fcurve_euler_filter.py +++ /dev/null @@ -1,89 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# <pep8 compliant> - -from math import * -import bpy -from mathutils import * - - -def main(context): - def cleanupEulCurve(fcv): - keys = [] - - for k in fcv.keyframe_points: - keys.append([k.handle_left.copy(), k.co.copy(), k.handle_right.copy()]) - print(keys) - - for i in range(len(keys)): - cur = keys[i] - prev = keys[i - 1] if i > 0 else None - next = keys[i + 1] if i < len(keys) - 1 else None - - if prev is None: - continue - - th = pi - if abs(prev[1][1] - cur[1][1]) >= th: # more than 180 degree jump - fac = pi * 2.0 - if prev[1][1] > cur[1][1]: - while abs(cur[1][1] - prev[1][1]) >= th: # < prev[1][1]: - cur[0][1] += fac - cur[1][1] += fac - cur[2][1] += fac - elif prev[1][1] < cur[1][1]: - while abs(cur[1][1] - prev[1][1]) >= th: - cur[0][1] -= fac - cur[1][1] -= fac - cur[2][1] -= fac - - for i in range(len(keys)): - for x in range(2): - fcv.keyframe_points[i].handle_left[x] = keys[i][0][x] - fcv.keyframe_points[i].co[x] = keys[i][1][x] - fcv.keyframe_points[i].handle_right[x] = keys[i][2][x] - - flist = bpy.context.active_object.animation_data.action.fcurves - for f in flist: - if f.select and f.data_path.endswith("rotation_euler"): - cleanupEulCurve(f) - - -class DiscontFilterOp(bpy.types.Operator): - """Fixes the most common causes of gimbal lock in the fcurves of the active bone""" - bl_idname = "graph.euler_filter" - bl_label = "Filter out discontinuities in the active fcurves" - - @classmethod - def poll(cls, context): - return context.active_object != None - - def execute(self, context): - main(context) - return {'FINISHED'} - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/uv.py b/release/scripts/op/uv.py deleted file mode 100644 index 83282924b5b..00000000000 --- a/release/scripts/op/uv.py +++ /dev/null @@ -1,381 +0,0 @@ -# ##### BEGIN GPL LICENSE BLOCK ##### -# -# This program 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 2 -# of the License, or (at your option) any later version. -# -# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# -# ##### END GPL LICENSE BLOCK ##### - -# <pep8 compliant> - -import bpy -from bpy.props import * - - -def write_svg(fw, mesh, image_width, image_height, face_iter): - # for making an XML compatible string - from xml.sax.saxutils import escape - from os.path import basename - - 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="%dpx" height="%dpx" viewBox="0px 0px %dpx %dpx"\n' % (image_width, image_height, image_width, image_height)) - fw(' xmlns="http://www.w3.org/2000/svg" version="1.1">\n') - desc = "%r, %s, (Blender %s)" % (basename(bpy.data.filepath), mesh.name, bpy.app.version_string) - 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) - - faces = mesh.faces - for i, uvs in face_iter: - try: # rare cases material index is invalid. - fill = fill_settings[faces[i].material_index] - except IndexError: - fill = fill_default - - fw('<polygon %s fill-opacity="0.5" stroke="black" stroke-width="1px" \n' % fill) - fw(' points="') - - for j, uv in enumerate(uvs): - x, y = uv[0], 1.0 - uv[1] - fw('%.3f,%.3f ' % (x * image_width, y * image_height)) - fw('" />\n') - fw('\n') - fw('</svg>\n') - - -def write_eps(fw, mesh, image_width, image_height, face_iter): - 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" % (image_width, image_height)) - fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" % (image_width, 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') - fw('/DRAW {') - # can remove from here to next comment to disable filling, aparently alpha is not supported - fw('gsave\n') - fw('0.7 setgray\n') - fw('fill\n') - fw('grestore\n') - fw('0 setgray\n') - # remove to here - fw('stroke\n') - fw('} def\n') - fw('newpath\n') - - firstline = True - for i, uvs in face_iter: - for j, uv in enumerate(uvs): - x, y = uv[0], uv[1] - if j == 0: - if not firstline: - fw('closepath\n') - fw('DRAW\n') - fw('newpath\n') - firstline = False - fw('%.5f %.5f moveto\n' % (x * image_width, y * image_height)) - else: - fw('%.5f %.5f lineto\n' % (x * image_width, y * image_height)) - - fw('closepath\n') - fw('DRAW\n') - fw('showpage\n') - fw('%%EOF\n') - - -def write_png(fw, mesh_source, image_width, image_height, face_iter): - filepath = fw.__self__.name - fw.__self__.close() - - material_solids = [bpy.data.materials.new("uv_temp_solid") for i in range(max(1, len(mesh_source.materials)))] - material_wire = bpy.data.materials.new("uv_temp_wire") - - scene = bpy.data.scenes.new("uv_temp") - mesh = bpy.data.meshes.new("uv_temp") - for mat_solid in material_solids: - mesh.materials.append(mat_solid) - - tot_verts = 0 - face_lens = [] - for f in mesh_source.faces: - tot_verts += len(f.vertices) - - faces_source = mesh_source.faces - - # get unique UV's incase there are many overlapping which slow down filling. - face_hash_3 = set() - face_hash_4 = set() - for i, uv in face_iter: - material_index = faces_source[i].material_index - if len(uv) == 3: - face_hash_3.add((uv[0][0], uv[0][1], uv[1][0], uv[1][1], uv[2][0], uv[2][1], material_index)) - else: - face_hash_4.add((uv[0][0], uv[0][1], uv[1][0], uv[1][1], uv[2][0], uv[2][1], uv[3][0], uv[3][1], material_index)) - - # now set the faces coords and locations - # build mesh data - mesh_new_vertices = [] - mesh_new_materials = [] - mesh_new_face_vertices = [] - - current_vert = 0 - - for face_data in face_hash_3: - mesh_new_vertices.extend([face_data[0], face_data[1], 0.0, face_data[2], face_data[3], 0.0, face_data[4], face_data[5], 0.0]) - mesh_new_face_vertices.extend([current_vert, current_vert + 1, current_vert + 2, 0]) - mesh_new_materials.append(face_data[6]) - current_vert += 3 - for face_data in face_hash_4: - mesh_new_vertices.extend([face_data[0], face_data[1], 0.0, face_data[2], face_data[3], 0.0, face_data[4], face_data[5], 0.0, face_data[6], face_data[7], 0.0]) - mesh_new_face_vertices.extend([current_vert, current_vert + 1, current_vert + 2, current_vert + 3]) - mesh_new_materials.append(face_data[8]) - current_vert += 4 - - mesh.vertices.add(len(mesh_new_vertices) // 3) - mesh.faces.add(len(mesh_new_face_vertices) // 4) - - mesh.vertices.foreach_set("co", mesh_new_vertices) - mesh.faces.foreach_set("vertices_raw", mesh_new_face_vertices) - mesh.faces.foreach_set("material_index", mesh_new_materials) - - mesh.update(calc_edges=True) - - obj_solid = bpy.data.objects.new("uv_temp_solid", mesh) - obj_wire = bpy.data.objects.new("uv_temp_wire", mesh) - base_solid = scene.objects.link(obj_solid) - base_wire = scene.objects.link(obj_wire) - base_solid.layers[0] = True - base_wire.layers[0] = True - - # place behind the wire - obj_solid.location = 0, 0, -1 - - obj_wire.material_slots[0].link = 'OBJECT' - obj_wire.material_slots[0].material = material_wire - - # 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 - scene.objects.link(obj_cam) - scene.camera = obj_cam - - # setup materials - for i, mat_solid in enumerate(material_solids): - if mesh_source.materials and mesh_source.materials[i]: - mat_solid.diffuse_color = mesh_source.materials[i].diffuse_color - - mat_solid.use_shadeless = True - mat_solid.use_transparency = True - mat_solid.alpha = 0.25 - - material_wire.type = 'WIRE' - material_wire.use_shadeless = True - material_wire.diffuse_color = 0, 0, 0 - - # scene render settings - scene.render.use_raytrace = False - scene.render.alpha_mode = 'STRAIGHT' - scene.render.color_mode = 'RGBA' - - scene.render.resolution_x = image_width - scene.render.resolution_y = image_height - scene.render.resolution_percentage = 100 - - if image_width > image_height: - scene.render.pixel_aspect_y = image_width / image_height - elif image_width < image_height: - scene.render.pixel_aspect_x = image_height / image_width - - scene.frame_start = 1 - scene.frame_end = 1 - - scene.render.file_format = 'PNG' - scene.render.filepath = filepath - - data_context = {"blend_data": bpy.context.blend_data, "scene": scene} - bpy.ops.render.render(data_context, write_still=True) - - # cleanup - bpy.data.scenes.remove(scene) - bpy.data.objects.remove(obj_cam) - bpy.data.objects.remove(obj_solid) - bpy.data.objects.remove(obj_wire) - - bpy.data.cameras.remove(cam) - bpy.data.meshes.remove(mesh) - - bpy.data.materials.remove(material_wire) - for mat_solid in material_solids: - bpy.data.materials.remove(mat_solid) - - -class ExportUVLayout(bpy.types.Operator): - """Export UV layout to file""" - - bl_idname = "uv.export_layout" - bl_label = "Export UV Layout" - bl_options = {'REGISTER', 'UNDO'} - - filepath = StringProperty(name="File Path", description="File path used for exporting the SVG file", maxlen=1024, default="", subtype='FILE_PATH') - check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'}) - export_all = BoolProperty(name="All UV's", description="Export all UVs in this mesh (not just the visible ones)", 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 a bitmap image")), - name="Format", - description="File format to export the UV layout to", - default='PNG') - size = IntVectorProperty(size=2, default=(1024, 1024), min=8, max=32768, description="Dimensions of the exported file") - - @classmethod - def poll(cls, context): - obj = context.active_object - return (obj and obj.type == 'MESH' and obj.data.uv_textures) - - 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 - - space_data = self._space_image(context) - if space_data: - image = space_data.image - if image: - width, height = tuple(context.space_data.image.size) - # incase no data is found. - if width and height: - image_width, image_height = width, height - - return image_width, image_height - - def _face_uv_iter(self, context): - obj = context.active_object - mesh = obj.data - uv_layer = mesh.uv_textures.active.data - uv_layer_len = len(uv_layer) - - if not self.export_all: - - local_image = Ellipsis - - if context.tool_settings.show_uv_local_view: - space_data = self._space_image(context) - if space_data: - local_image = space_data.image - - faces = mesh.faces - - for i in range(uv_layer_len): - uv_elem = uv_layer[i] - # context checks - if faces[i].select and (local_image is Ellipsis or local_image == uv_elem.image): - #~ uv = uv_elem.uv - #~ if False not in uv_elem.select_uv[:len(uv)]: - #~ yield (i, uv) - - # just write what we see. - yield (i, uv_layer[i].uv) - else: - # all, simple - for i in range(uv_layer_len): - yield (i, uv_layer[i].uv) - - def execute(self, context): - - obj = context.active_object - is_editmode = (obj.mode == 'EDIT') - if is_editmode: - bpy.ops.object.mode_set(mode='OBJECT', toggle=False) - - mesh = obj.data - - mode = self.mode - - filepath = self.filepath - filepath = bpy.path.ensure_ext(filepath, "." + mode.lower()) - file = open(filepath, "w") - fw = file.write - - if mode == 'SVG': - func = write_svg - elif mode == 'EPS': - func = write_eps - elif mode == 'PNG': - func = write_png - - func(fw, mesh, self.size[0], self.size[1], self._face_uv_iter(context)) - - if is_editmode: - bpy.ops.object.mode_set(mode='EDIT', toggle=False) - - 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 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 menu_func(self, context): - self.layout.operator(ExportUVLayout.bl_idname) - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.IMAGE_MT_uvs.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.IMAGE_MT_uvs.remove(menu_func) - -if __name__ == "__main__": - register() diff --git a/release/scripts/presets/interaction/blender.py b/release/scripts/presets/interaction/blender.py index 7ac9cd1eb0b..3b6ce13d7e0 100644 --- a/release/scripts/presets/interaction/blender.py +++ b/release/scripts/presets/interaction/blender.py @@ -1,9 +1,6 @@ # Configuration Blender import bpy -wm = bpy.context.window_manager -wm.keyconfigs.active = wm.keyconfigs['Blender'] - bpy.context.user_preferences.view.use_mouse_auto_depth = False bpy.context.user_preferences.view.use_zoom_to_mouse = False bpy.context.user_preferences.view.use_rotate_around_active = False @@ -13,4 +10,4 @@ bpy.context.user_preferences.inputs.select_mouse = 'RIGHT' bpy.context.user_preferences.inputs.view_zoom_method = 'DOLLY' bpy.context.user_preferences.inputs.view_zoom_axis = 'VERTICAL' bpy.context.user_preferences.inputs.view_rotate_method = 'TRACKBALL' -bpy.context.user_preferences.inputs.invert_mouse_wheel_zoom = False +bpy.context.user_preferences.inputs.invert_mouse_zoom = False diff --git a/release/scripts/presets/interaction/maya.py b/release/scripts/presets/interaction/maya.py new file mode 100644 index 00000000000..62084645081 --- /dev/null +++ b/release/scripts/presets/interaction/maya.py @@ -0,0 +1,10 @@ +# Configuration Blender +import bpy + +bpy.context.user_preferences.edit.use_drag_immediately = True +bpy.context.user_preferences.edit.use_insertkey_xyz_to_rgb = False +bpy.context.user_preferences.inputs.select_mouse = 'LEFT' +bpy.context.user_preferences.inputs.view_zoom_method = 'DOLLY' +bpy.context.user_preferences.inputs.view_zoom_axis = 'HORIZONTAL' +bpy.context.user_preferences.inputs.view_rotate_method = 'TURNTABLE' +bpy.context.user_preferences.inputs.invert_mouse_zoom = True diff --git a/release/scripts/presets/keyconfig/maya.py b/release/scripts/presets/keyconfig/maya.py index 120597cdd2a..87f4791ec51 100644 --- a/release/scripts/presets/keyconfig/maya.py +++ b/release/scripts/presets/keyconfig/maya.py @@ -7,380 +7,372 @@ kc = wm.keyconfigs.new('maya') # Map 3D View km = kc.keymaps.new('3D View', space_type='VIEW_3D', region_type='WINDOW', modal=False) -kmi = km.items.new('view3d.manipulator', 'LEFTMOUSE', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.manipulator', 'LEFTMOUSE', 'PRESS', any=True) kmi.properties.release_confirm = True -kmi = km.items.new('view3d.cursor3d', 'ACTIONMOUSE', 'PRESS') -kmi = km.items.new('view3d.rotate', 'LEFTMOUSE', 'PRESS', alt=True) -kmi = km.items.new('view3d.move', 'MIDDLEMOUSE', 'PRESS', alt=True) -kmi = km.items.new('view3d.zoom', 'RIGHTMOUSE', 'PRESS', alt=True) -kmi = km.items.new('view3d.view_selected', 'NUMPAD_PERIOD', 'PRESS') -kmi = km.items.new('view3d.view_center_cursor', 'NUMPAD_PERIOD', 'PRESS', ctrl=True) -kmi = km.items.new('view3d.fly', 'F', 'PRESS', shift=True) -kmi = km.items.new('view3d.smoothview', 'TIMER1', 'ANY', any=True) -kmi = km.items.new('view3d.rotate', 'TRACKPADPAN', 'ANY', alt=True) -kmi = km.items.new('view3d.rotate', 'MOUSEROTATE', 'ANY') -kmi = km.items.new('view3d.move', 'TRACKPADPAN', 'ANY') -kmi = km.items.new('view3d.zoom', 'TRACKPADZOOM', 'ANY') -kmi = km.items.new('view3d.zoom', 'NUMPAD_PLUS', 'PRESS') +kmi = km.keymap_items.new('view3d.cursor3d', 'ACTIONMOUSE', 'PRESS') +kmi = km.keymap_items.new('view3d.rotate', 'LEFTMOUSE', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.move', 'MIDDLEMOUSE', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.zoom', 'RIGHTMOUSE', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.view_selected', 'NUMPAD_PERIOD', 'PRESS') +kmi = km.keymap_items.new('view3d.view_center_cursor', 'NUMPAD_PERIOD', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.fly', 'F', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.smoothview', 'TIMER1', 'ANY', any=True) +kmi = km.keymap_items.new('view3d.rotate', 'TRACKPADPAN', 'ANY', alt=True) +kmi = km.keymap_items.new('view3d.rotate', 'MOUSEROTATE', 'ANY') +kmi = km.keymap_items.new('view3d.move', 'TRACKPADPAN', 'ANY') +kmi = km.keymap_items.new('view3d.zoom', 'TRACKPADZOOM', 'ANY') +kmi = km.keymap_items.new('view3d.zoom', 'NUMPAD_PLUS', 'PRESS') kmi.properties.delta = 1 -kmi = km.items.new('view3d.zoom', 'NUMPAD_MINUS', 'PRESS') +kmi = km.keymap_items.new('view3d.zoom', 'NUMPAD_MINUS', 'PRESS') kmi.properties.delta = -1 -kmi = km.items.new('view3d.zoom', 'EQUAL', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.zoom', 'EQUAL', 'PRESS', ctrl=True) kmi.properties.delta = 1 -kmi = km.items.new('view3d.zoom', 'MINUS', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.zoom', 'MINUS', 'PRESS', ctrl=True) kmi.properties.delta = -1 -kmi = km.items.new('view3d.zoom', 'WHEELINMOUSE', 'PRESS') +kmi = km.keymap_items.new('view3d.zoom', 'WHEELINMOUSE', 'PRESS') kmi.properties.delta = 1 -kmi = km.items.new('view3d.zoom', 'WHEELOUTMOUSE', 'PRESS') +kmi = km.keymap_items.new('view3d.zoom', 'WHEELOUTMOUSE', 'PRESS') kmi.properties.delta = -1 -kmi = km.items.new('view3d.view_all', 'HOME', 'PRESS') +kmi = km.keymap_items.new('view3d.view_all', 'HOME', 'PRESS') kmi.properties.center = False -kmi = km.items.new('view3d.view_all', 'C', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.view_all', 'C', 'PRESS', shift=True) kmi.properties.center = True -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_0', 'PRESS') +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_0', 'PRESS') kmi.properties.type = 'CAMERA' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS') +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS') kmi.properties.type = 'FRONT' -kmi = km.items.new('view3d.view_orbit', 'NUMPAD_2', 'PRESS') +kmi = km.keymap_items.new('view3d.view_orbit', 'NUMPAD_2', 'PRESS') kmi.properties.type = 'ORBITDOWN' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS') +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS') kmi.properties.type = 'RIGHT' -kmi = km.items.new('view3d.view_orbit', 'NUMPAD_4', 'PRESS') +kmi = km.keymap_items.new('view3d.view_orbit', 'NUMPAD_4', 'PRESS') kmi.properties.type = 'ORBITLEFT' -kmi = km.items.new('view3d.view_persportho', 'NUMPAD_5', 'PRESS') -kmi = km.items.new('view3d.view_orbit', 'NUMPAD_6', 'PRESS') +kmi = km.keymap_items.new('view3d.view_persportho', 'NUMPAD_5', 'PRESS') +kmi = km.keymap_items.new('view3d.view_orbit', 'NUMPAD_6', 'PRESS') kmi.properties.type = 'ORBITRIGHT' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS') +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS') kmi.properties.type = 'TOP' -kmi = km.items.new('view3d.view_orbit', 'NUMPAD_8', 'PRESS') +kmi = km.keymap_items.new('view3d.view_orbit', 'NUMPAD_8', 'PRESS') kmi.properties.type = 'ORBITUP' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', ctrl=True) kmi.properties.type = 'BACK' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', ctrl=True) kmi.properties.type = 'LEFT' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', ctrl=True) kmi.properties.type = 'BOTTOM' -kmi = km.items.new('view3d.view_pan', 'NUMPAD_2', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'NUMPAD_2', 'PRESS', ctrl=True) kmi.properties.type = 'PANDOWN' -kmi = km.items.new('view3d.view_pan', 'NUMPAD_4', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'NUMPAD_4', 'PRESS', ctrl=True) kmi.properties.type = 'PANLEFT' -kmi = km.items.new('view3d.view_pan', 'NUMPAD_6', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'NUMPAD_6', 'PRESS', ctrl=True) kmi.properties.type = 'PANRIGHT' -kmi = km.items.new('view3d.view_pan', 'NUMPAD_8', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'NUMPAD_8', 'PRESS', ctrl=True) kmi.properties.type = 'PANUP' -kmi = km.items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', ctrl=True) kmi.properties.type = 'PANRIGHT' -kmi = km.items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True) kmi.properties.type = 'PANLEFT' -kmi = km.items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', shift=True) kmi.properties.type = 'PANUP' -kmi = km.items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', shift=True) kmi.properties.type = 'PANDOWN' -kmi = km.items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', ctrl=True, alt=True) kmi.properties.type = 'ORBITLEFT' -kmi = km.items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True, alt=True) kmi.properties.type = 'ORBITRIGHT' -kmi = km.items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', shift=True, alt=True) kmi.properties.type = 'ORBITUP' -kmi = km.items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', shift=True, alt=True) kmi.properties.type = 'ORBITDOWN' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True) kmi.properties.align_active = True kmi.properties.type = 'FRONT' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True) kmi.properties.align_active = True kmi.properties.type = 'RIGHT' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True) kmi.properties.align_active = True kmi.properties.type = 'TOP' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True, ctrl=True) kmi.properties.align_active = True kmi.properties.type = 'BACK' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True, ctrl=True) kmi.properties.align_active = True kmi.properties.type = 'LEFT' -kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True, ctrl=True) kmi.properties.align_active = True kmi.properties.type = 'BOTTOM' -kmi = km.items.new('view3d.localview', 'NUMPAD_SLASH', 'PRESS') -kmi = km.items.new('view3d.layers', 'ACCENT_GRAVE', 'PRESS') +kmi = km.keymap_items.new('view3d.localview', 'NUMPAD_SLASH', 'PRESS') +kmi = km.keymap_items.new('view3d.layers', 'ACCENT_GRAVE', 'PRESS') kmi.properties.nr = 0 -kmi = km.items.new('view3d.layers', 'ONE', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'ONE', 'PRESS', any=True) kmi.properties.nr = 1 -kmi = km.items.new('view3d.layers', 'TWO', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'TWO', 'PRESS', any=True) kmi.properties.nr = 2 -kmi = km.items.new('view3d.layers', 'THREE', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'THREE', 'PRESS', any=True) kmi.properties.nr = 3 -kmi = km.items.new('view3d.layers', 'FOUR', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'FOUR', 'PRESS', any=True) kmi.properties.nr = 4 -kmi = km.items.new('view3d.layers', 'FIVE', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'FIVE', 'PRESS', any=True) kmi.properties.nr = 5 -kmi = km.items.new('view3d.layers', 'SIX', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'SIX', 'PRESS', any=True) kmi.properties.nr = 6 -kmi = km.items.new('view3d.layers', 'SEVEN', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'SEVEN', 'PRESS', any=True) kmi.properties.nr = 7 -kmi = km.items.new('view3d.layers', 'EIGHT', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'EIGHT', 'PRESS', any=True) kmi.properties.nr = 8 -kmi = km.items.new('view3d.layers', 'NINE', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'NINE', 'PRESS', any=True) kmi.properties.nr = 9 -kmi = km.items.new('view3d.layers', 'ZERO', 'PRESS', any=True) +kmi = km.keymap_items.new('view3d.layers', 'ZERO', 'PRESS', any=True) kmi.properties.nr = 10 -kmi = km.items.new('wm.context_toggle_enum', 'Z', 'PRESS') +kmi = km.keymap_items.new('wm.context_toggle_enum', 'Z', 'PRESS') kmi.properties.data_path = 'space_data.viewport_shade' kmi.properties.value_1 = 'SOLID' kmi.properties.value_2 = 'WIREFRAME' -kmi = km.items.new('wm.context_toggle_enum', 'Z', 'PRESS', alt=True) +kmi = km.keymap_items.new('wm.context_toggle_enum', 'Z', 'PRESS', alt=True) kmi.properties.data_path = 'space_data.viewport_shade' kmi.properties.value_1 = 'TEXTURED' kmi.properties.value_2 = 'SOLID' -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS') -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS') +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True) kmi.properties.extend = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True) kmi.properties.center = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', alt=True) kmi.properties.enumerate = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True) kmi.properties.center = True kmi.properties.extend = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) kmi.properties.center = True kmi.properties.enumerate = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True) kmi.properties.enumerate = True kmi.properties.extend = True -kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True) kmi.properties.center = True kmi.properties.enumerate = True kmi.properties.extend = True -kmi = km.items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY') +kmi = km.keymap_items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY') kmi.properties.extend = False -kmi = km.items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', ctrl=True) -kmi = km.items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', ctrl=True) +kmi = km.keymap_items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', shift=True, ctrl=True) kmi.properties.deselect = True -kmi = km.items.new('view3d.select_circle', 'C', 'PRESS') -kmi = km.items.new('view3d.clip_border', 'B', 'PRESS', alt=True) -kmi = km.items.new('view3d.zoom_border', 'B', 'PRESS', shift=True) -kmi = km.items.new('view3d.render_border', 'B', 'PRESS', shift=True) -kmi = km.items.new('view3d.camera_to_view', 'NUMPAD_0', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('view3d.object_as_camera', 'NUMPAD_0', 'PRESS', ctrl=True) -kmi = km.items.new('wm.call_menu', 'S', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.select_circle', 'C', 'PRESS') +kmi = km.keymap_items.new('view3d.clip_border', 'B', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.zoom_border', 'B', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.render_border', 'B', 'PRESS', shift=True) +kmi = km.keymap_items.new('view3d.camera_to_view', 'NUMPAD_0', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('view3d.object_as_camera', 'NUMPAD_0', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'S', 'PRESS', shift=True) kmi.properties.name = 'VIEW3D_MT_snap' -kmi = km.items.new('wm.context_set_enum', 'COMMA', 'PRESS') +kmi = km.keymap_items.new('wm.context_set_enum', 'COMMA', 'PRESS') kmi.properties.data_path = 'space_data.pivot_point' kmi.properties.value = 'BOUNDING_BOX_CENTER' -kmi = km.items.new('wm.context_set_enum', 'COMMA', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.context_set_enum', 'COMMA', 'PRESS', ctrl=True) kmi.properties.data_path = 'space_data.pivot_point' kmi.properties.value = 'MEDIAN_POINT' -kmi = km.items.new('wm.context_toggle', 'COMMA', 'PRESS', alt=True) +kmi = km.keymap_items.new('wm.context_toggle', 'COMMA', 'PRESS', alt=True) kmi.properties.data_path = 'space_data.use_pivot_point' -kmi = km.items.new('wm.context_toggle', 'Q', 'PRESS') +kmi = km.keymap_items.new('wm.context_toggle', 'Q', 'PRESS') kmi.properties.data_path = 'space_data.show_manipulator' -kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS') +kmi = km.keymap_items.new('wm.context_set_enum', 'PERIOD', 'PRESS') kmi.properties.data_path = 'space_data.pivot_point' kmi.properties.value = 'CURSOR' -kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.context_set_enum', 'PERIOD', 'PRESS', ctrl=True) kmi.properties.data_path = 'space_data.pivot_point' kmi.properties.value = 'INDIVIDUAL_ORIGINS' -kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS', alt=True) +kmi = km.keymap_items.new('wm.context_set_enum', 'PERIOD', 'PRESS', alt=True) kmi.properties.data_path = 'space_data.pivot_point' kmi.properties.value = 'ACTIVE_ELEMENT' -kmi = km.items.new('transform.translate', 'G', 'PRESS', shift=True) -kmi = km.items.new('transform.translate', 'EVT_TWEAK_S', 'ANY') -kmi = km.items.new('transform.rotate', 'R', 'PRESS', shift=True) -kmi = km.items.new('transform.resize', 'S', 'PRESS', shift=True) -kmi = km.items.new('transform.warp', 'W', 'PRESS', shift=True) -kmi = km.items.new('transform.tosphere', 'S', 'PRESS', shift=True, alt=True) -kmi = km.items.new('transform.shear', 'S', 'PRESS', shift=True, ctrl=True, alt=True) -kmi = km.items.new('transform.select_orientation', 'SPACE', 'PRESS', alt=True) -kmi = km.items.new('transform.create_orientation', 'SPACE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('transform.translate', 'G', 'PRESS', shift=True) +kmi = km.keymap_items.new('transform.translate', 'EVT_TWEAK_S', 'ANY') +kmi = km.keymap_items.new('transform.rotate', 'R', 'PRESS', shift=True) +kmi = km.keymap_items.new('transform.resize', 'S', 'PRESS', shift=True) +kmi = km.keymap_items.new('transform.warp', 'W', 'PRESS', shift=True) +kmi = km.keymap_items.new('transform.tosphere', 'S', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('transform.shear', 'S', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('transform.select_orientation', 'SPACE', 'PRESS', alt=True) +kmi = km.keymap_items.new('transform.create_orientation', 'SPACE', 'PRESS', ctrl=True, alt=True) kmi.properties.use = True -kmi = km.items.new('transform.mirror', 'M', 'PRESS', ctrl=True) -kmi = km.items.new('wm.context_toggle', 'TAB', 'PRESS', shift=True) +kmi = km.keymap_items.new('transform.mirror', 'M', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.context_toggle', 'TAB', 'PRESS', shift=True) kmi.properties.data_path = 'tool_settings.use_snap' -kmi = km.items.new('transform.snap_type', 'TAB', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('view3d.enable_manipulator', 'W', 'PRESS') +kmi = km.keymap_items.new('transform.snap_type', 'TAB', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('view3d.enable_manipulator', 'W', 'PRESS') kmi.properties.translate = True -kmi = km.items.new('view3d.enable_manipulator', 'E', 'PRESS') +kmi = km.keymap_items.new('view3d.enable_manipulator', 'E', 'PRESS') kmi.properties.rotate = True -kmi = km.items.new('view3d.enable_manipulator', 'R', 'PRESS') +kmi = km.keymap_items.new('view3d.enable_manipulator', 'R', 'PRESS') kmi.properties.scale = True -kmi = km.items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY', shift=True) +kmi = km.keymap_items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY', shift=True) kmi.properties.extend = True # Map Object Mode km = kc.keymaps.new('Object Mode', space_type='EMPTY', region_type='WINDOW', modal=False) -kmi = km.items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True) +kmi = km.keymap_items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True) kmi.properties.data_path = 'tool_settings.proportional_edit_falloff' -kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS') +kmi = km.keymap_items.new('wm.context_toggle_enum', 'O', 'PRESS') kmi.properties.data_path = 'tool_settings.proportional_edit' kmi.properties.value_1 = 'DISABLED' kmi.properties.value_2 = 'ENABLED' -kmi = km.items.new('view3d.game_start', 'P', 'PRESS') -kmi = km.items.new('object.select_all', 'A', 'PRESS') -kmi = km.items.new('object.select_inverse', 'I', 'PRESS', ctrl=True) -kmi = km.items.new('object.select_linked', 'L', 'PRESS', shift=True) -kmi = km.items.new('object.select_grouped', 'G', 'PRESS', shift=True) -kmi = km.items.new('object.select_mirror', 'M', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS') +kmi = km.keymap_items.new('view3d.game_start', 'P', 'PRESS') +kmi = km.keymap_items.new('object.select_all', 'A', 'PRESS') +kmi = km.keymap_items.new('object.select_inverse', 'I', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.select_linked', 'L', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.select_grouped', 'G', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.select_mirror', 'M', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS') kmi.properties.direction = 'PARENT' -kmi = km.items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS', shift=True) kmi.properties.direction = 'PARENT' kmi.properties.extend = True -kmi = km.items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS') +kmi = km.keymap_items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS') kmi.properties.direction = 'CHILD' -kmi = km.items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS', shift=True) kmi.properties.direction = 'CHILD' kmi.properties.extend = True -kmi = km.items.new('object.parent_set', 'P', 'PRESS', ctrl=True) -kmi = km.items.new('object.parent_no_inverse_set', 'P', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('object.parent_clear', 'P', 'PRESS', alt=True) -kmi = km.items.new('object.track_set', 'T', 'PRESS', ctrl=True) -kmi = km.items.new('object.track_clear', 'T', 'PRESS', alt=True) -kmi = km.items.new('object.constraint_add_with_targets', 'C', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('object.constraints_clear', 'C', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('object.location_clear', 'G', 'PRESS', alt=True) -kmi = km.items.new('object.rotation_clear', 'R', 'PRESS', alt=True) -kmi = km.items.new('object.scale_clear', 'S', 'PRESS', alt=True) -kmi = km.items.new('object.origin_clear', 'O', 'PRESS', alt=True) -kmi = km.items.new('object.hide_view_clear', 'H', 'PRESS', alt=True) -kmi = km.items.new('object.hide_view_set', 'H', 'PRESS') -kmi = km.items.new('object.hide_view_set', 'H', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.parent_set', 'P', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.parent_no_inverse_set', 'P', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('object.parent_clear', 'P', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.track_set', 'T', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.track_clear', 'T', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.constraint_add_with_targets', 'C', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('object.constraints_clear', 'C', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('object.location_clear', 'G', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.rotation_clear', 'R', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.scale_clear', 'S', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.origin_clear', 'O', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.hide_view_clear', 'H', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.hide_view_set', 'H', 'PRESS') +kmi = km.keymap_items.new('object.hide_view_set', 'H', 'PRESS', shift=True) kmi.properties.unselected = True -kmi = km.items.new('object.move_to_layer', 'M', 'PRESS') -kmi = km.items.new('object.delete', 'X', 'PRESS') -kmi = km.items.new('object.delete', 'DEL', 'PRESS') -kmi = km.items.new('wm.call_menu', 'A', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.move_to_layer', 'M', 'PRESS') +kmi = km.keymap_items.new('object.delete', 'X', 'PRESS') +kmi = km.keymap_items.new('object.delete', 'DEL', 'PRESS') +kmi = km.keymap_items.new('wm.call_menu', 'A', 'PRESS', shift=True) kmi.properties.name = 'INFO_MT_add' -kmi = km.items.new('object.duplicates_make_real', 'A', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('wm.call_menu', 'A', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.duplicates_make_real', 'A', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'A', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_object_apply' -kmi = km.items.new('wm.call_menu', 'U', 'PRESS') +kmi = km.keymap_items.new('wm.call_menu', 'U', 'PRESS') kmi.properties.name = 'VIEW3D_MT_make_single_user' -kmi = km.items.new('wm.call_menu', 'L', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'L', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_make_links' -kmi = km.items.new('object.duplicate_move', 'D', 'PRESS', shift=True) -kmi = km.items.new('object.duplicate_move_linked', 'D', 'PRESS', alt=True) -kmi = km.items.new('object.join', 'J', 'PRESS', ctrl=True) -kmi = km.items.new('object.convert', 'C', 'PRESS', alt=True) -kmi = km.items.new('object.proxy_make', 'P', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('object.make_local', 'L', 'PRESS') -kmi = km.items.new('anim.keyframe_insert_menu', 'I', 'PRESS') -kmi = km.items.new('anim.keyframe_delete_v3d', 'I', 'PRESS', alt=True) -kmi = km.items.new('anim.keying_set_active_set', 'I', 'PRESS', shift=True, ctrl=True, alt=True) -kmi = km.items.new('group.create', 'G', 'PRESS', ctrl=True) -kmi = km.items.new('group.objects_remove', 'G', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('group.objects_add_active', 'G', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('group.objects_remove_active', 'G', 'PRESS', shift=True, alt=True) -kmi = km.items.new('wm.call_menu', 'W', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.duplicate_move', 'D', 'PRESS', shift=True) +kmi = km.keymap_items.new('object.duplicate_move_linked', 'D', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.join', 'J', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.convert', 'C', 'PRESS', alt=True) +kmi = km.keymap_items.new('object.proxy_make', 'P', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('object.make_local', 'L', 'PRESS') +kmi = km.keymap_items.new('anim.keyframe_insert_menu', 'I', 'PRESS') +kmi = km.keymap_items.new('anim.keyframe_delete_v3d', 'I', 'PRESS', alt=True) +kmi = km.keymap_items.new('anim.keying_set_active_set', 'I', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('group.create', 'G', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('group.objects_remove', 'G', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('group.objects_add_active', 'G', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('group.objects_remove_active', 'G', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_object_specials' -kmi = km.items.new('object.subdivision_set', 'ZERO', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'ZERO', 'PRESS', ctrl=True) kmi.properties.level = 0 -kmi = km.items.new('object.subdivision_set', 'ONE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'ONE', 'PRESS', ctrl=True) kmi.properties.level = 1 -kmi = km.items.new('object.subdivision_set', 'TWO', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'TWO', 'PRESS', ctrl=True) kmi.properties.level = 2 -kmi = km.items.new('object.subdivision_set', 'THREE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'THREE', 'PRESS', ctrl=True) kmi.properties.level = 3 -kmi = km.items.new('object.subdivision_set', 'FOUR', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'FOUR', 'PRESS', ctrl=True) kmi.properties.level = 4 -kmi = km.items.new('object.subdivision_set', 'FIVE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.subdivision_set', 'FIVE', 'PRESS', ctrl=True) kmi.properties.level = 5 -kmi = km.items.new('object.select_all', 'SELECTMOUSE', 'CLICK') +kmi = km.keymap_items.new('object.select_all', 'SELECTMOUSE', 'CLICK') kmi.properties.action = 'DESELECT' # Map Mesh km = kc.keymaps.new('Mesh', space_type='EMPTY', region_type='WINDOW', modal=False) -kmi = km.items.new('mesh.loopcut_slide', 'R', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('mesh.loopcut_slide', 'R', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True) kmi.properties.extend = True -kmi = km.items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True) kmi.properties.extend = True -kmi = km.items.new('mesh.select_shortest_path', 'SELECTMOUSE', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.select_all', 'A', 'PRESS') -kmi = km.items.new('mesh.select_more', 'NUMPAD_PLUS', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.select_less', 'NUMPAD_MINUS', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.select_inverse', 'I', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.select_non_manifold', 'M', 'PRESS', shift=True, ctrl=True, alt=True) -kmi = km.items.new('mesh.select_linked', 'L', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.select_linked_pick', 'L', 'PRESS') -kmi = km.items.new('mesh.select_linked_pick', 'L', 'PRESS', shift=True) +kmi = km.keymap_items.new('mesh.select_shortest_path', 'SELECTMOUSE', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_all', 'A', 'PRESS') +kmi = km.keymap_items.new('mesh.select_more', 'NUMPAD_PLUS', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_less', 'NUMPAD_MINUS', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_inverse', 'I', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_non_manifold', 'M', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.select_linked', 'L', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_linked_pick', 'L', 'PRESS') +kmi = km.keymap_items.new('mesh.select_linked_pick', 'L', 'PRESS', shift=True) kmi.properties.deselect = True -kmi = km.items.new('mesh.faces_select_linked_flat', 'F', 'PRESS', shift=True, ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.faces_select_linked_flat', 'F', 'PRESS', shift=True, ctrl=True, alt=True) kmi.properties.sharpness = 135.0 -kmi = km.items.new('mesh.select_similar', 'G', 'PRESS', shift=True) -kmi = km.items.new('wm.call_menu', 'TAB', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.select_similar', 'G', 'PRESS', shift=True) +kmi = km.keymap_items.new('wm.call_menu', 'TAB', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_select_mode' -kmi = km.items.new('mesh.hide', 'H', 'PRESS') -kmi = km.items.new('mesh.hide', 'H', 'PRESS', shift=True) +kmi = km.keymap_items.new('mesh.hide', 'H', 'PRESS') +kmi = km.keymap_items.new('mesh.hide', 'H', 'PRESS', shift=True) kmi.properties.unselected = True -kmi = km.items.new('mesh.reveal', 'H', 'PRESS', alt=True) -kmi = km.items.new('mesh.normals_make_consistent', 'N', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.normals_make_consistent', 'N', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('mesh.reveal', 'H', 'PRESS', alt=True) +kmi = km.keymap_items.new('mesh.normals_make_consistent', 'N', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.normals_make_consistent', 'N', 'PRESS', shift=True, ctrl=True) kmi.properties.inside = True -kmi = km.items.new('view3d.edit_mesh_extrude_move_normal', 'E', 'PRESS', ctrl=True) -kmi = km.items.new('view3d.edit_mesh_extrude_individual_move', 'E', 'PRESS', shift=True) -kmi = km.items.new('wm.call_menu', 'E', 'PRESS', alt=True) +kmi = km.keymap_items.new('view3d.edit_mesh_extrude_move_normal', 'E', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('view3d.edit_mesh_extrude_individual_move', 'E', 'PRESS', shift=True) +kmi = km.keymap_items.new('wm.call_menu', 'E', 'PRESS', alt=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_extrude' -kmi = km.items.new('mesh.spin', 'R', 'PRESS', alt=True) -kmi = km.items.new('mesh.fill', 'F', 'PRESS', alt=True) -kmi = km.items.new('mesh.beautify_fill', 'F', 'PRESS', shift=True, alt=True) -kmi = km.items.new('mesh.quads_convert_to_tris', 'T', 'PRESS', ctrl=True) -kmi = km.items.new('mesh.tris_convert_to_quads', 'J', 'PRESS', alt=True) -kmi = km.items.new('mesh.edge_flip', 'F', 'PRESS', shift=True, ctrl=True) -kmi = km.items.new('mesh.rip_move', 'V', 'PRESS') -kmi = km.items.new('mesh.merge', 'M', 'PRESS', alt=True) -kmi = km.items.new('transform.shrink_fatten', 'S', 'PRESS', ctrl=True, alt=True) -kmi = km.items.new('mesh.edge_face_add', 'F', 'PRESS') -kmi = km.items.new('mesh.duplicate_move', 'D', 'PRESS', shift=True) -kmi = km.items.new('wm.call_menu', 'A', 'PRESS', shift=True) +kmi = km.keymap_items.new('mesh.spin', 'R', 'PRESS', alt=True) +kmi = km.keymap_items.new('mesh.fill', 'F', 'PRESS', alt=True) +kmi = km.keymap_items.new('mesh.beautify_fill', 'F', 'PRESS', shift=True, alt=True) +kmi = km.keymap_items.new('mesh.quads_convert_to_tris', 'T', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('mesh.tris_convert_to_quads', 'J', 'PRESS', alt=True) +kmi = km.keymap_items.new('mesh.edge_flip', 'F', 'PRESS', shift=True, ctrl=True) +kmi = km.keymap_items.new('mesh.rip_move', 'V', 'PRESS') +kmi = km.keymap_items.new('mesh.merge', 'M', 'PRESS', alt=True) +kmi = km.keymap_items.new('transform.shrink_fatten', 'S', 'PRESS', ctrl=True, alt=True) +kmi = km.keymap_items.new('mesh.edge_face_add', 'F', 'PRESS') +kmi = km.keymap_items.new('mesh.duplicate_move', 'D', 'PRESS', shift=True) +kmi = km.keymap_items.new('wm.call_menu', 'A', 'PRESS', shift=True) kmi.properties.name = 'INFO_MT_mesh_add' -kmi = km.items.new('mesh.separate', 'P', 'PRESS') -kmi = km.items.new('mesh.split', 'Y', 'PRESS') -kmi = km.items.new('mesh.dupli_extrude_cursor', 'ACTIONMOUSE', 'CLICK', ctrl=True) -kmi = km.items.new('mesh.delete', 'X', 'PRESS') -kmi = km.items.new('mesh.delete', 'DEL', 'PRESS') -kmi = km.items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', key_modifier='K') -kmi = km.items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', shift=True, key_modifier='K') +kmi = km.keymap_items.new('mesh.separate', 'P', 'PRESS') +kmi = km.keymap_items.new('mesh.split', 'Y', 'PRESS') +kmi = km.keymap_items.new('mesh.dupli_extrude_cursor', 'ACTIONMOUSE', 'CLICK', ctrl=True) +kmi = km.keymap_items.new('mesh.delete', 'X', 'PRESS') +kmi = km.keymap_items.new('mesh.delete', 'DEL', 'PRESS') +kmi = km.keymap_items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', key_modifier='K') +kmi = km.keymap_items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', shift=True, key_modifier='K') kmi.properties.type = 'MIDPOINTS' -kmi = km.items.new('object.vertex_parent_set', 'P', 'PRESS', ctrl=True) -kmi = km.items.new('wm.call_menu', 'W', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('object.vertex_parent_set', 'P', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'W', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_specials' -kmi = km.items.new('wm.call_menu', 'F', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'F', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_faces' -kmi = km.items.new('wm.call_menu', 'E', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'E', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_edges' -kmi = km.items.new('wm.call_menu', 'V', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'V', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_edit_mesh_vertices' -kmi = km.items.new('wm.call_menu', 'H', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'H', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_hook' -kmi = km.items.new('wm.call_menu', 'U', 'PRESS') +kmi = km.keymap_items.new('wm.call_menu', 'U', 'PRESS') kmi.properties.name = 'VIEW3D_MT_uv_map' -kmi = km.items.new('wm.call_menu', 'G', 'PRESS', ctrl=True) +kmi = km.keymap_items.new('wm.call_menu', 'G', 'PRESS', ctrl=True) kmi.properties.name = 'VIEW3D_MT_vertex_group' -kmi = km.items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True) +kmi = km.keymap_items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True) kmi.properties.data_path = 'tool_settings.proportional_edit_falloff' -kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS') +kmi = km.keymap_items.new('wm.context_toggle_enum', 'O', 'PRESS') kmi.properties.data_path = 'tool_settings.proportional_edit' kmi.properties.value_1 = 'DISABLED' kmi.properties.value_2 = 'ENABLED' -kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS', alt=True) +kmi = km.keymap_items.new('wm.context_toggle_enum', 'O', 'PRESS', alt=True) kmi.properties.data_path = 'tool_settings.proportional_edit' kmi.properties.value_1 = 'DISABLED' kmi.properties.value_2 = 'CONNECTED' -kmi = km.items.new('mesh.select_all', 'SELECTMOUSE', 'CLICK') +kmi = km.keymap_items.new('mesh.select_all', 'SELECTMOUSE', 'CLICK') kmi.properties.action = 'DESELECT' wm.keyconfigs.active = kc - -bpy.context.user_preferences.edit.use_drag_immediately = True -bpy.context.user_preferences.edit.use_insertkey_xyz_to_rgb = False -bpy.context.user_preferences.inputs.select_mouse = 'LEFT' -bpy.context.user_preferences.inputs.view_zoom_method = 'DOLLY' -bpy.context.user_preferences.inputs.view_zoom_axis = 'HORIZONTAL' -bpy.context.user_preferences.inputs.view_rotate_method = 'TURNTABLE' -bpy.context.user_preferences.inputs.invert_mouse_wheel_zoom = True diff --git a/release/scripts/startup/bl_operators/__init__.py b/release/scripts/startup/bl_operators/__init__.py new file mode 100644 index 00000000000..d5f7a63366a --- /dev/null +++ b/release/scripts/startup/bl_operators/__init__.py @@ -0,0 +1,58 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +if "bpy" in locals(): + from imp import reload as _reload + for val in _modules_loaded.values(): + _reload(val) +_modules = ( + "add_mesh_torus", + "animsys_update", + "image", + "mesh", + "nla", + "object_align", + "object", + "object_randomize_transform", + "object_quick_effects", + "presets", + "screen_play_rendered_anim", + "sequencer", + "uvcalc_follow_active", + "uvcalc_lightmap", + "uvcalc_smart_project", + "vertexpaint_dirt", + "wm", +) +__import__(name=__name__, fromlist=_modules) +_namespace = globals() +_modules_loaded = {name: _namespace[name] for name in _modules} +del _namespace + + +import bpy + + +def register(): + bpy.utils.register_module(__name__) + + +def unregister(): + bpy.utils.unregister_module(__name__) diff --git a/release/scripts/op/add_mesh_torus.py b/release/scripts/startup/bl_operators/add_mesh_torus.py index db4f90f05d1..460330a56a1 100644 --- a/release/scripts/op/add_mesh_torus.py +++ b/release/scripts/startup/bl_operators/add_mesh_torus.py @@ -19,10 +19,11 @@ # <pep8 compliant> import bpy import mathutils -from math import cos, sin, pi def add_torus(major_rad, minor_rad, major_seg, minor_seg): + from math import cos, sin, pi + Vector = mathutils.Vector Quaternion = mathutils.Quaternion @@ -71,7 +72,7 @@ def add_torus(major_rad, minor_rad, major_seg, minor_seg): return verts, faces -from bpy.props import * +from bpy.props import FloatProperty, IntProperty, BoolProperty, FloatVectorProperty class AddTorus(bpy.types.Operator): @@ -135,20 +136,3 @@ class AddTorus(bpy.types.Operator): add_object_utils.object_data_add(context, mesh, operator=self) return {'FINISHED'} - - -def menu_func(self, context): - self.layout.operator(AddTorus.bl_idname, text="Torus", icon='MESH_TORUS') - - -def register(): - bpy.utils.register_class(AddTorus) - bpy.types.INFO_MT_mesh_add.append(menu_func) - - -def unregister(): - bpy.utils.unregister_class(AddTorus) - bpy.types.INFO_MT_mesh_add.remove(menu_func) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/animsys_update.py b/release/scripts/startup/bl_operators/animsys_update.py index 9262ff40a37..63d438a5066 100644 --- a/release/scripts/op/animsys_update.py +++ b/release/scripts/startup/bl_operators/animsys_update.py @@ -237,14 +237,16 @@ data_path_update = [ ("ImageTexture", "mirror_y", "use_mirror_y"), ("ImageTexture", "normal_map", "use_normal_map"), ("MarbleTexture", "noise_size", "noise_scale"), - ("MarbleTexture", "noisebasis2", "noisebasis_2"), + ("MarbleTexture", "noisebasis2", "noise_basis_2"), + ("MarbleTexture", "noisebasis_2", "noise_basis_2"), ("MusgraveTexture", "highest_dimension", "dimension_max"), ("MusgraveTexture", "noise_size", "noise_scale"), ("StucciTexture", "noise_size", "noise_scale"), ("VoronoiTexture", "coloring", "color_mode"), ("VoronoiTexture", "noise_size", "noise_scale"), ("WoodTexture", "noise_size", "noise_scale"), - ("WoodTexture", "noisebasis2", "noisebasis_2"), + ("WoodTexture", "noisebasis2", "noise_basis_2"), + ("WoodTexture", "noisebasis_2", "noise_basis_2"), ("World", "blend_sky", "use_sky_blend"), ("World", "paper_sky", "use_sky_paper"), ("World", "real_sky", "use_sky_real"), @@ -679,6 +681,7 @@ data_path_update = [ ("SpeedControlSequence", "global_speed", "multiply_speed"), ("SpeedControlSequence", "use_curve_velocity", "use_as_speed"), ("SpeedControlSequence", "use_curve_compress_y", "scale_to_length"), + ("Key", "keys", "key_blocks"), ] @@ -686,7 +689,7 @@ import bpy class UpdateAnimData(bpy.types.Operator): - '''Update data paths from 2.53 to edited data paths of drivers and fcurves''' + """Update data paths from 2.56 and previous versions, modifying data paths of drivers and fcurves""" bl_idname = "anim.update_data_paths" bl_label = "Update Animation Data" @@ -694,15 +697,3 @@ class UpdateAnimData(bpy.types.Operator): import animsys_refactor animsys_refactor.update_data_paths(data_path_update) return {'FINISHED'} - - -if __name__ == "__main__": - bpy.ops.anim.update_data_paths() - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) diff --git a/release/scripts/op/image.py b/release/scripts/startup/bl_operators/image.py index e1d471f8254..462db3a2c5e 100644 --- a/release/scripts/op/image.py +++ b/release/scripts/startup/bl_operators/image.py @@ -31,25 +31,20 @@ class EditExternally(bpy.types.Operator): filepath = StringProperty(name="File Path", description="Path to an image file", maxlen=1024, default="") def _editor_guess(self, context): - import platform - try: - system = platform.system() - except UnicodeDecodeError: - import sys - system = sys.platform + import sys image_editor = context.user_preferences.filepaths.image_editor # use image editor in the preferences when available. if not image_editor: - if system in ('Windows', 'win32'): + if sys.platform[:3] == "win": image_editor = ["start"] # not tested! - elif system == 'Darwin': + elif sys.platform == "darwin": image_editor = ["open"] else: image_editor = ["gimp"] else: - if system == 'Darwin': + if sys.platform == "darwin": # blender file selector treats .app as a folder # and will include a trailing backslash, so we strip it. image_editor.rstrip('\\') @@ -65,12 +60,18 @@ class EditExternally(bpy.types.Operator): filepath = bpy.path.abspath(self.filepath) if not os.path.exists(filepath): - self.report('ERROR', "Image path '%s' not found." % filepath) + self.report({'ERROR'}, "Image path %r not found." % filepath) return {'CANCELLED'} cmd = self._editor_guess(context) + [filepath] - subprocess.Popen(cmd) + try: + subprocess.Popen(cmd) + except: + import traceback + traceback.print_exc() + self.report({'ERROR'}, "Image editor not found, please specify in User Preferences > File") + return {'CANCELLED'} return {'FINISHED'} @@ -78,6 +79,8 @@ class EditExternally(bpy.types.Operator): try: filepath = context.space_data.image.filepath except: + import traceback + traceback.print_exc() self.report({'ERROR'}, "Image not found on disk") return {'CANCELLED'} @@ -109,7 +112,7 @@ class SaveDirty(bpy.types.Operator): class ProjectEdit(bpy.types.Operator): - """Edit a snapshot if the viewport in an external image editor""" + """Edit a snapshot of the viewport in an external image editor""" bl_idname = "image.project_edit" bl_label = "Project Edit" bl_options = {'REGISTER'} @@ -168,7 +171,10 @@ class ProjectEdit(bpy.types.Operator): image_new.file_format = 'PNG' image_new.save() - bpy.ops.image.external_edit(filepath=filepath_final) + try: + bpy.ops.image.external_edit(filepath=filepath_final) + except RuntimeError as re: + self.report({'ERROR'}, str(re)) return {'FINISHED'} @@ -185,6 +191,8 @@ class ProjectApply(bpy.types.Operator): try: image = bpy.data.images[image_name] except KeyError: + import traceback + traceback.print_exc() self.report({'ERROR'}, "Could not find image '%s'" % image_name) return {'CANCELLED'} @@ -192,14 +200,3 @@ class ProjectApply(bpy.types.Operator): bpy.ops.paint.project_image(image=image_name) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/mesh.py b/release/scripts/startup/bl_operators/mesh.py index 4906fd7fc6c..44d81ba53df 100644 --- a/release/scripts/op/mesh.py +++ b/release/scripts/startup/bl_operators/mesh.py @@ -20,6 +20,8 @@ import bpy +from bpy.props import EnumProperty + class MeshSelectInteriorFaces(bpy.types.Operator): '''Select faces where all edges have more then 2 face users.''' @@ -66,17 +68,23 @@ class MeshSelectInteriorFaces(bpy.types.Operator): class MeshMirrorUV(bpy.types.Operator): '''Copy mirror UV coordinates on the X axis based on a mirrored mesh''' - bl_idname = "mesh.faces_miror_uv" + bl_idname = "mesh.faces_mirror_uv" bl_label = "Copy Mirrored UV coords" bl_options = {'REGISTER', 'UNDO'} + direction = EnumProperty(items=( + ('POSITIVE', "Positive", ""), + ('NEGATIVE', "Negative", "")), + name="Axis Direction", + description="") + @classmethod def poll(cls, context): ob = context.active_object return (ob and ob.type == 'MESH') def execute(self, context): - DIR = 1 # TODO, make an option + DIR = (self.direction == 'NEGATIVE') from mathutils import Vector @@ -129,7 +137,7 @@ class MeshMirrorUV(bpy.types.Operator): # find mirror faces mirror_fm = {} for i, f in enumerate(faces): - verts = f.vertices[:] + verts = list(f.vertices) verts.sort() verts = tuple(verts) mirror_fm[verts] = i @@ -170,14 +178,3 @@ class MeshMirrorUV(bpy.types.Operator): bpy.ops.object.mode_set(mode='EDIT', toggle=False) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/nla.py b/release/scripts/startup/bl_operators/nla.py index 5711fdf12e5..923ca92a162 100644 --- a/release/scripts/op/nla.py +++ b/release/scripts/startup/bl_operators/nla.py @@ -98,7 +98,7 @@ def bake(frame_start, frame_end, step=1, only_selected=False): pose_items = pose.bones.items() for name, pbone in pose_items: - if only_selected and not pbone.select: + if only_selected and not pbone.bone.select: continue for f in frame_range: @@ -124,7 +124,7 @@ def bake(frame_start, frame_end, step=1, only_selected=False): return action -from bpy.props import * +from bpy.props import IntProperty, BoolProperty class BakeAction(bpy.types.Operator): @@ -168,18 +168,3 @@ class BakeAction(bpy.types.Operator): def invoke(self, context, event): wm = context.window_manager return wm.invoke_props_dialog(self) - - -#def menu_func(self, context): -# self.layout.operator(BakeAction.bl_idname, text="Bake Armature Action") - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/object.py b/release/scripts/startup/bl_operators/object.py index 92886689972..0342a14a1b2 100644 --- a/release/scripts/op/object.py +++ b/release/scripts/startup/bl_operators/object.py @@ -19,7 +19,7 @@ # <pep8 compliant> import bpy -from bpy.props import * +from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty class SelectPattern(bpy.types.Operator): @@ -177,6 +177,9 @@ class SubdivisionSet(bpy.types.Operator): if relative and level == 0: return {'CANCELLED'} # nothing to do + if not relative and level < 0: + self.level = level = 0 + def set_object_subd(obj): for mod in obj.modifiers: if mod.type == 'MULTIRES': @@ -250,11 +253,11 @@ class ShapeTransfer(bpy.types.Operator): def ob_add_shape(ob, name): me = ob.data key = ob.shape_key_add(from_mix=False) - if len(me.shape_keys.keys) == 1: + if len(me.shape_keys.key_blocks) == 1: key.name = "Basis" key = ob.shape_key_add(from_mix=False) # we need a rest key.name = name - ob.active_shape_key_index = len(me.shape_keys.keys) - 1 + ob.active_shape_key_index = len(me.shape_keys.key_blocks) - 1 ob.show_only_shape_key = True from mathutils.geometry import barycentric_transform @@ -270,7 +273,7 @@ class ShapeTransfer(bpy.types.Operator): orig_normals = me_nos(me.vertices) # orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S - orig_coords = me_cos(me.shape_keys.keys[0].data) + orig_coords = me_cos(me.shape_keys.key_blocks[0].data) for ob_other in objects: me_other = ob_other.data @@ -280,7 +283,7 @@ class ShapeTransfer(bpy.types.Operator): target_normals = me_nos(me_other.vertices) if me_other.shape_keys: - target_coords = me_cos(me_other.shape_keys.keys[0].data) + target_coords = me_cos(me_other.shape_keys.key_blocks[0].data) else: target_coords = me_cos(me_other.vertices) @@ -476,7 +479,6 @@ class MakeDupliFace(bpy.types.Operator): def _main(self, context): from mathutils import Vector - from math import sqrt SCALE_FAC = 0.01 offset = 0.5 * SCALE_FAC @@ -562,14 +564,3 @@ class ClearAllRestrictRender(bpy.types.Operator): for obj in context.scene.objects: obj.hide_render = False return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/object_align.py b/release/scripts/startup/bl_operators/object_align.py index d98fe584c1a..644f30a4745 100644 --- a/release/scripts/op/object_align.py +++ b/release/scripts/startup/bl_operators/object_align.py @@ -230,7 +230,7 @@ def align_objects(align_x, align_y, align_z, align_mode, relative_to): return True -from bpy.props import * +from bpy.props import EnumProperty class AlignObjects(bpy.types.Operator): @@ -239,7 +239,7 @@ class AlignObjects(bpy.types.Operator): bl_label = "Align Objects" bl_options = {'REGISTER', 'UNDO'} - align_mode = bpy.props.EnumProperty(items=( + align_mode = EnumProperty(items=( ('OPT_1', "Negative Sides", ""), ('OPT_2', "Centers", ""), ('OPT_3', "Positive Sides", "")), @@ -247,7 +247,7 @@ class AlignObjects(bpy.types.Operator): description="", default='OPT_2') - relative_to = bpy.props.EnumProperty(items=( + relative_to = EnumProperty(items=( ('OPT_1', "Scene Origin", ""), ('OPT_2', "3D Cursor", ""), ('OPT_3', "Selection", ""), @@ -278,23 +278,3 @@ class AlignObjects(bpy.types.Operator): return {'CANCELLED'} else: return {'FINISHED'} - - -def menu_func(self, context): - if context.mode == 'OBJECT': - self.layout.operator(AlignObjects.bl_idname, - text="Align Objects") - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_transform.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_transform.remove(menu_func) - - -if __name__ == "__main__": - register() diff --git a/release/scripts/startup/bl_operators/object_quick_effects.py b/release/scripts/startup/bl_operators/object_quick_effects.py new file mode 100644 index 00000000000..21640fa3ee6 --- /dev/null +++ b/release/scripts/startup/bl_operators/object_quick_effects.py @@ -0,0 +1,296 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +from mathutils import Vector +import bpy +from bpy.props import BoolProperty, EnumProperty, IntProperty, FloatProperty, FloatVectorProperty + + +class MakeFur(bpy.types.Operator): + bl_idname = "object.make_fur" + bl_label = "Make Fur" + bl_options = {'REGISTER', 'UNDO'} + + density = EnumProperty(items=( + ('LIGHT', "Light", ""), + ('MEDIUM', "Medium", ""), + ('HEAVY', "Heavy", "")), + name="Fur Density", + description="", + default='MEDIUM') + + view_percentage = IntProperty(name="View %", + default=10, min=1, max=100, soft_min=1, soft_max=100) + + length = FloatProperty(name="Length", + default=0.1, min=0.001, max=100, soft_min=0.01, soft_max=10) + + def execute(self, context): + fake_context = bpy.context.copy() + mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH'] + + if not mesh_objects: + self.report({'ERROR'}, "Select at least one mesh object.") + return {'CANCELLED'} + + mat = bpy.data.materials.new("Fur Material") + mat.strand.tip_size = 0.25 + mat.strand.blend_distance = 0.5 + + for obj in mesh_objects: + fake_context["object"] = obj + bpy.ops.object.particle_system_add(fake_context) + + psys = obj.particle_systems[-1] + psys.settings.type = 'HAIR' + + if self.density == 'LIGHT': + psys.settings.count = 100 + elif self.density == 'MEDIUM': + psys.settings.count = 1000 + elif self.density == 'HEAVY': + psys.settings.count = 10000 + + psys.settings.child_nbr = self.view_percentage + psys.settings.hair_length = self.length + psys.settings.use_strand_primitive = True + psys.settings.use_hair_bspline = True + psys.settings.child_type = 'INTERPOLATED' + + obj.data.materials.append(mat) + obj.particle_systems[-1].settings.material = len(obj.data.materials) + + return {'FINISHED'} + + +def obj_bb_minmax(obj, min_co, max_co): + for i in range(0, 8): + bb_vec = Vector((obj.bound_box[i][0], obj.bound_box[i][1], obj.bound_box[i][2])) * obj.matrix_world + + min_co[0] = min(bb_vec[0], min_co[0]) + min_co[1] = min(bb_vec[1], min_co[1]) + min_co[2] = min(bb_vec[2], min_co[2]) + max_co[0] = max(bb_vec[0], max_co[0]) + max_co[1] = max(bb_vec[1], max_co[1]) + max_co[2] = max(bb_vec[2], max_co[2]) + + +class MakeSmoke(bpy.types.Operator): + bl_idname = "object.make_smoke" + bl_label = "Make Smoke" + bl_options = {'REGISTER', 'UNDO'} + + style = EnumProperty(items=( + ('STREAM', "Stream", ""), + ('PUFF', "Puff", ""), + ('FIRE', "Fire", "")), + name="Smoke Style", + description="", + default='STREAM') + + show_flows = BoolProperty(name="Render Smoke Objects", + description="Keep the smoke objects visible during rendering.", + default=False) + + def execute(self, context): + fake_context = bpy.context.copy() + mesh_objects = [obj for obj in context.selected_objects if obj.type == 'MESH'] + min_co = Vector((100000, 100000, 100000)) + max_co = Vector((-100000, -100000, -100000)) + + if not mesh_objects: + self.report({'ERROR'}, "Select at least one mesh object.") + return {'CANCELLED'} + + for obj in mesh_objects: + fake_context["object"] = obj + # make each selected object a smoke flow + bpy.ops.object.modifier_add(fake_context, type='SMOKE') + obj.modifiers[-1].smoke_type = 'FLOW' + + psys = obj.particle_systems[-1] + if self.style == 'PUFF': + psys.settings.frame_end = psys.settings.frame_start + psys.settings.emit_from = 'VOLUME' + psys.settings.distribution = 'RAND' + elif self.style == 'FIRE': + psys.settings.effector_weights.gravity = -1 + psys.settings.lifetime = 5 + psys.settings.count = 100000 + + obj.modifiers[-2].flow_settings.initial_velocity = True + obj.modifiers[-2].flow_settings.temperature = 2 + + psys.settings.use_render_emitter = self.show_flows + if not self.show_flows: + obj.draw_type = 'WIRE' + + # store bounding box min/max for the domain object + obj_bb_minmax(obj, min_co, max_co) + + # add the smoke domain object + bpy.ops.mesh.primitive_cube_add() + obj = context.active_object + obj.name = "Smoke Domain" + + # give the smoke some room above the flows + obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, 1.0)) + obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0)) + + # setup smoke domain + bpy.ops.object.modifier_add(type='SMOKE') + obj.modifiers[-1].smoke_type = 'DOMAIN' + if self.style == 'FIRE': + obj.modifiers[-1].domain_settings.use_dissolve_smoke = True + obj.modifiers[-1].domain_settings.dissolve_speed = 20 + obj.modifiers[-1].domain_settings.use_high_resolution = True + + # create a volume material with a voxel data texture for the domain + bpy.ops.object.material_slot_add() + + mat = bpy.data.materials.new("Smoke Domain Material") + obj.material_slots[0].material = mat + mat.type = 'VOLUME' + mat.volume.density = 0 + mat.volume.density_scale = 5 + + mat.texture_slots.add() + mat.texture_slots[0].texture = bpy.data.textures.new("Smoke Density", 'VOXEL_DATA') + mat.texture_slots[0].texture.voxel_data.domain_object = obj + mat.texture_slots[0].use_map_color_emission = False + mat.texture_slots[0].use_map_density = True + + # for fire add a second texture for emission and emission color + if self.style == 'FIRE': + mat.volume.emission = 5 + mat.texture_slots.add() + mat.texture_slots[1].texture = bpy.data.textures.new("Smoke Heat", 'VOXEL_DATA') + mat.texture_slots[1].texture.voxel_data.domain_object = obj + mat.texture_slots[1].texture.use_color_ramp = True + + ramp = mat.texture_slots[1].texture.color_ramp + + elem = ramp.elements.new(0.333) + elem.color[0] = elem.color[3] = 1 + elem.color[1] = elem.color[2] = 0 + + elem = ramp.elements.new(0.666) + elem.color[0] = elem.color[1] = elem.color[3] = 1 + elem.color[2] = 0 + + mat.texture_slots[1].use_map_emission = True + mat.texture_slots[1].blend_type = 'MULTIPLY' + + return {'FINISHED'} + + +class MakeFluid(bpy.types.Operator): + bl_idname = "object.make_fluid" + bl_label = "Make Fluid" + bl_options = {'REGISTER', 'UNDO'} + + style = EnumProperty(items=( + ('INFLOW', "Inflow", ""), + ('BASIC', "Basic", "")), + name="Fluid Style", + description="", + default='BASIC') + + initial_velocity = FloatVectorProperty(name="Initial Velocity", + description="Initial velocity of the fluid", + default=(0.0, 0.0, 0.0), min=-100.0, max=100.0, subtype='VELOCITY') + + show_flows = BoolProperty(name="Render Fluid Objects", + description="Keep the fluid objects visible during rendering.", + default=False) + + start_baking = BoolProperty(name="Start Fluid Bake", + description="Start baking the fluid immediately after creating the domain object.", + default=False) + + def execute(self, context): + fake_context = bpy.context.copy() + mesh_objects = [obj for obj in context.selected_objects if (obj.type == 'MESH' and not 0 in obj.dimensions)] + min_co = Vector((100000, 100000, 100000)) + max_co = Vector((-100000, -100000, -100000)) + + if not mesh_objects: + self.report({'ERROR'}, "Select at least one mesh object.") + return {'CANCELLED'} + + for obj in mesh_objects: + fake_context["object"] = obj + # make each selected object a fluid + bpy.ops.object.modifier_add(fake_context, type='FLUID_SIMULATION') + + # fluid has to be before constructive modifiers, so it might not be the last modifier + for mod in obj.modifiers: + if mod.type == 'FLUID_SIMULATION': + break + + if self.style == 'INFLOW': + mod.settings.type = 'INFLOW' + mod.settings.inflow_velocity = self.initial_velocity.copy() + else: + mod.settings.type = 'FLUID' + mod.settings.initial_velocity = self.initial_velocity.copy() + + obj.hide_render = not self.show_flows + if not self.show_flows: + obj.draw_type = 'WIRE' + + # store bounding box min/max for the domain object + obj_bb_minmax(obj, min_co, max_co) + + # add the fluid domain object + bpy.ops.mesh.primitive_cube_add() + obj = context.active_object + obj.name = "Fluid Domain" + + # give the fluid some room below the flows and scale with initial velocity + v = 0.5 * self.initial_velocity + obj.location = 0.5 * (max_co + min_co) + Vector((0.0, 0.0, -1.0)) + v + obj.scale = 0.5 * (max_co - min_co) + Vector((1.0, 1.0, 2.0)) + Vector((abs(v[0]), abs(v[1]), abs(v[2]))) + + # setup smoke domain + bpy.ops.object.modifier_add(type='FLUID_SIMULATION') + obj.modifiers[-1].settings.type = 'DOMAIN' + + # make the domain smooth so it renders nicely + bpy.ops.object.shade_smooth() + + # create a ray-transparent material for the domain + bpy.ops.object.material_slot_add() + + mat = bpy.data.materials.new("Fluid Domain Material") + obj.material_slots[0].material = mat + + mat.specular_intensity = 1 + mat.specular_hardness = 100 + mat.use_transparency = True + mat.alpha = 0.0 + mat.transparency_method = 'RAYTRACE' + mat.raytrace_transparency.ior = 1.33 + mat.raytrace_transparency.depth = 4 + + if self.start_baking: + bpy.ops.fluid.bake() + + return {'FINISHED'} diff --git a/release/scripts/op/object_randomize_transform.py b/release/scripts/startup/bl_operators/object_randomize_transform.py index ee7a5f98b91..9dc5086086f 100644 --- a/release/scripts/op/object_randomize_transform.py +++ b/release/scripts/startup/bl_operators/object_randomize_transform.py @@ -84,7 +84,7 @@ def randomize_selected(seed, delta, loc, rot, scale, scale_even): uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0) -from bpy.props import * +from bpy.props import IntProperty, BoolProperty, FloatVectorProperty class RandomizeLocRotSize(bpy.types.Operator): @@ -145,23 +145,3 @@ class RandomizeLocRotSize(bpy.types.Operator): randomize_selected(seed, delta, loc, rot, scale, scale_even) return {'FINISHED'} - - -def menu_func(self, context): - if context.mode == 'OBJECT': - self.layout.operator(RandomizeLocRotSize.bl_idname, - text="Randomize Transform") - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_transform.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_transform.remove(menu_func) - - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/presets.py b/release/scripts/startup/bl_operators/presets.py index 65653aeee3e..493c51ad237 100644 --- a/release/scripts/op/presets.py +++ b/release/scripts/startup/bl_operators/presets.py @@ -19,7 +19,6 @@ # <pep8 compliant> import bpy -import os class AddPresetBase(): @@ -49,11 +48,11 @@ class AddPresetBase(): preset_menu_class = getattr(bpy.types, self.preset_menu) if not self.remove_active: - - if not self.name: + name = self.name.strip() + if not name: return {'FINISHED'} - filename = self.as_filename(self.name) + filename = self.as_filename(name) target_path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", self.preset_subdir), create=True) @@ -119,7 +118,7 @@ class AddPresetBase(): return {'FINISHED'} def check(self, context): - self.name = self.as_filename(self.name) + self.name = self.as_filename(self.name.strip()) def invoke(self, context, event): if not self.remove_active: @@ -243,10 +242,10 @@ class AddPresetSunSky(AddPresetBase, bpy.types.Operator): "sky.sun_brightness", "sky.sun_intensity", "sky.sun_size", - "sky.use_sky_blend", - "sky.use_sky_blend_type", - "sky.use_sky_color_space", - "sky.use_sky_exposure", + "sky.sky_blend", + "sky.sky_blend_type", + "sky.sky_color_space", + "sky.sky_exposure", ] preset_subdir = "sunsky" @@ -265,7 +264,7 @@ class AddPresetInteraction(AddPresetBase, bpy.types.Operator): preset_values = [ "user_preferences.edit.use_drag_immediately", "user_preferences.edit.use_insertkey_xyz_to_rgb", - "user_preferences.inputs.invert_mouse_wheel_zoom", + "user_preferences.inputs.invert_mouse_zoom", "user_preferences.inputs.select_mouse", "user_preferences.inputs.use_emulate_numpad", "user_preferences.inputs.use_mouse_continuous", @@ -328,7 +327,7 @@ class AddPresetOperator(AddPresetBase, bpy.types.Operator): ret = [] for prop_id, prop in operator_rna.properties.items(): if (not prop.is_hidden) and prop_id not in properties_blacklist: - ret.append("op.%s" % prop_id) + ret.append("op.%s" % prop_id) return ret @@ -351,14 +350,3 @@ class WM_MT_operator_presets(bpy.types.Menu): return AddPresetOperator.operator_path(self.operator) preset_operator = "script.execute_preset" - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/screen_play_rendered_anim.py b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py index 579af9b34b3..64af25e7b0f 100644 --- a/release/scripts/op/screen_play_rendered_anim.py +++ b/release/scripts/startup/bl_operators/screen_play_rendered_anim.py @@ -29,19 +29,14 @@ import os def guess_player_path(preset): - import platform - try: - system = platform.system() - except UnicodeDecodeError: - import sys - system = sys.platform + import sys if preset == 'BLENDER24': player_path = "blender" - if system == 'Darwin': + if sys.platform == "darwin": test_path = "/Applications/blender 2.49.app/Contents/MacOS/blender" - elif system in ('Windows', 'win32'): + elif sys.platform[:3] == "win": test_path = "/Program Files/Blender Foundation/Blender/blender.exe" if os.path.exists(test_path): @@ -50,7 +45,8 @@ def guess_player_path(preset): elif preset == 'DJV': player_path = "djv_view" - if system == 'Darwin': + if sys.platform == "darwin": + # TODO, crummy supporting only 1 version, could find the newest installed version test_path = '/Applications/djv-0.8.2.app/Contents/Resources/bin/djv_view' if os.path.exists(test_path): player_path = test_path @@ -144,14 +140,3 @@ class PlayRenderedAnim(bpy.types.Operator): #raise OSError("Couldn't find an external animation player.") return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/sequencer.py b/release/scripts/startup/bl_operators/sequencer.py index de341bef269..16b72406c49 100644 --- a/release/scripts/op/sequencer.py +++ b/release/scripts/startup/bl_operators/sequencer.py @@ -20,7 +20,7 @@ import bpy -from bpy.props import * +from bpy.props import IntProperty class SequencerCrossfadeSounds(bpy.types.Operator): @@ -132,15 +132,3 @@ class SequencerDeinterlaceSelectedMovies(bpy.types.Operator): s.use_deinterlace = True return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/uvcalc_follow_active.py b/release/scripts/startup/bl_operators/uvcalc_follow_active.py index 12895fae1c5..ad5ec15ff80 100644 --- a/release/scripts/op/uvcalc_follow_active.py +++ b/release/scripts/startup/bl_operators/uvcalc_follow_active.py @@ -245,20 +245,6 @@ class FollowActiveQuads(bpy.types.Operator): main(context, self) return {'FINISHED'} - -# Add to a menu -menu_func = (lambda self, context: self.layout.operator(FollowActiveQuads.bl_idname)) - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_uv_map.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_uv_map.remove(menu_func) - - -if __name__ == "__main__": - register() + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) diff --git a/release/scripts/startup/bl_operators/uvcalc_lightmap.py b/release/scripts/startup/bl_operators/uvcalc_lightmap.py new file mode 100644 index 00000000000..3893612437a --- /dev/null +++ b/release/scripts/startup/bl_operators/uvcalc_lightmap.py @@ -0,0 +1,582 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +import bpy +import mathutils + + +class prettyface(object): + __slots__ = "uv", "width", "height", "children", "xoff", "yoff", "has_parent", "rot" + + def __init__(self, data): + self.has_parent = False + self.rot = False # only used for triables + self.xoff = 0 + self.yoff = 0 + + if type(data) == list: # list of data + self.uv = None + + # join the data + if len(data) == 2: + # 2 vertical blocks + data[1].xoff = data[0].width + self.width = data[0].width * 2 + self.height = data[0].height + + elif len(data) == 4: + # 4 blocks all the same size + d = data[0].width # dimension x/y are the same + + data[1].xoff += d + data[2].yoff += d + + data[3].xoff += d + data[3].yoff += d + + self.width = self.height = d * 2 + + #else: + # print(len(data), data) + # raise "Error" + + for pf in data: + pf.has_parent = True + + self.children = data + + elif type(data) == tuple: + # 2 blender faces + # f, (len_min, len_mid, len_max) + self.uv = data + + f1, lens1, lens1ord = data[0] + if data[1]: + f2, lens2, lens2ord = data[1] + self.width = (lens1[lens1ord[0]] + lens2[lens2ord[0]]) / 2.0 + self.height = (lens1[lens1ord[1]] + lens2[lens2ord[1]]) / 2.0 + else: # 1 tri :/ + self.width = lens1[0] + self.height = lens1[1] + + self.children = [] + + else: # blender face + # self.uv = data.uv + self.uv = data.id_data.uv_textures.active.data[data.index].uv # XXX25 + + # cos = [v.co for v in data] + cos = [data.id_data.vertices[v].co for v in data.vertices] # XXX25 + + self.width = ((cos[0] - cos[1]).length + (cos[2] - cos[3]).length) / 2.0 + self.height = ((cos[1] - cos[2]).length + (cos[0] - cos[3]).length) / 2.0 + + self.children = [] + + def spin(self): + if self.uv and len(self.uv) == 4: + self.uv = self.uv[1], self.uv[2], self.uv[3], self.uv[0] + + self.width, self.height = self.height, self.width + self.xoff, self.yoff = self.yoff, self.xoff # not needed? + self.rot = not self.rot # only for tri pairs. + # print("spinning") + for pf in self.children: + pf.spin() + + def place(self, xoff, yoff, xfac, yfac, margin_w, margin_h): + from math import pi + + xoff += self.xoff + yoff += self.yoff + + for pf in self.children: + pf.place(xoff, yoff, xfac, yfac, margin_w, margin_h) + + uv = self.uv + if not uv: + return + + x1 = xoff + y1 = yoff + x2 = xoff + self.width + y2 = yoff + self.height + + # Scale the values + x1 = x1 / xfac + margin_w + x2 = x2 / xfac - margin_w + y1 = y1 / yfac + margin_h + y2 = y2 / yfac - margin_h + + # 2 Tri pairs + if len(uv) == 2: + # match the order of angle sizes of the 3d verts with the UV angles and rotate. + def get_tri_angles(v1, v2, v3): + a1 = (v2 - v1).angle(v3 - v1, pi) + a2 = (v1 - v2).angle(v3 - v2, pi) + a3 = pi - (a1 + a2) # a3= (v2 - v3).angle(v1 - v3) + + return [(a1, 0), (a2, 1), (a3, 2)] + + def set_uv(f, p1, p2, p3): + + # cos = + #v1 = cos[0]-cos[1] + #v2 = cos[1]-cos[2] + #v3 = cos[2]-cos[0] + + # angles_co = get_tri_angles(*[v.co for v in f]) + angles_co = get_tri_angles(*[f.id_data.vertices[v].co for v in f.vertices]) # XXX25 + + angles_co.sort() + I = [i for a, i in angles_co] + + # fuv = f.uv + fuv = f.id_data.uv_textures.active.data[f.index].uv # XXX25 + + if self.rot: + fuv[I[2]] = p1 + fuv[I[1]] = p2 + fuv[I[0]] = p3 + else: + fuv[I[2]] = p1 + fuv[I[0]] = p2 + fuv[I[1]] = p3 + + f, lens, lensord = uv[0] + + set_uv(f, (x1, y1), (x1, y2 - margin_h), (x2 - margin_w, y1)) + + if uv[1]: + f, lens, lensord = uv[1] + set_uv(f, (x2, y2), (x2, y1 + margin_h), (x1 + margin_w, y2)) + + else: # 1 QUAD + uv[1][0], uv[1][1] = x1, y1 + uv[2][0], uv[2][1] = x1, y2 + uv[3][0], uv[3][1] = x2, y2 + uv[0][0], uv[0][1] = x2, y1 + + def __hash__(self): + # None unique hash + return self.width, self.height + + +def lightmap_uvpack(meshes, + PREF_SEL_ONLY=True, + PREF_NEW_UVLAYER=False, + PREF_PACK_IN_ONE=False, + PREF_APPLY_IMAGE=False, + PREF_IMG_PX_SIZE=512, + PREF_BOX_DIV=8, + PREF_MARGIN_DIV=512 + ): + ''' + BOX_DIV if the maximum division of the UV map that + a box may be consolidated into. + Basicly, a lower value will be slower but waist less space + and a higher value will have more clumpy boxes but more waisted space + ''' + import time + from math import sqrt + + if not meshes: + return + + t = time.time() + + if PREF_PACK_IN_ONE: + if PREF_APPLY_IMAGE: + image = bpy.data.images.new(name="lightmap", width=PREF_IMG_PX_SIZE, height=PREF_IMG_PX_SIZE, alpha=False) + face_groups = [[]] + else: + face_groups = [] + + for me in meshes: + # Add face UV if it does not exist. + # All new faces are selected. + if not me.uv_textures: + me.uv_textures.new() + + if PREF_SEL_ONLY: + faces = [f for f in me.faces if f.select] + else: + faces = me.faces[:] + + if PREF_PACK_IN_ONE: + face_groups[0].extend(faces) + else: + face_groups.append(faces) + + if PREF_NEW_UVLAYER: + me.uv_textures.new() + + for face_sel in face_groups: + print("\nStarting unwrap") + + if len(face_sel) < 4: + print("\tWarning, less then 4 faces, skipping") + continue + + pretty_faces = [prettyface(f) for f in face_sel if len(f.vertices) == 4] + + # Do we have any tri's + if len(pretty_faces) != len(face_sel): + + # Now add tri's, not so simple because we need to pair them up. + def trylens(f): + # f must be a tri + + # cos = [v.co for v in f] + cos = [f.id_data.vertices[v].co for v in f.vertices] # XXX25 + + lens = [(cos[0] - cos[1]).length, (cos[1] - cos[2]).length, (cos[2] - cos[0]).length] + + lens_min = lens.index(min(lens)) + lens_max = lens.index(max(lens)) + for i in range(3): + if i != lens_min and i != lens_max: + lens_mid = i + break + lens_order = lens_min, lens_mid, lens_max + + return f, lens, lens_order + + tri_lengths = [trylens(f) for f in face_sel if len(f.vertices) == 3] + del trylens + + def trilensdiff(t1, t2): + return\ + abs(t1[1][t1[2][0]] - t2[1][t2[2][0]]) + \ + abs(t1[1][t1[2][1]] - t2[1][t2[2][1]]) + \ + abs(t1[1][t1[2][2]] - t2[1][t2[2][2]]) + + while tri_lengths: + tri1 = tri_lengths.pop() + + if not tri_lengths: + pretty_faces.append(prettyface((tri1, None))) + break + + best_tri_index = -1 + best_tri_diff = 100000000.0 + + for i, tri2 in enumerate(tri_lengths): + diff = trilensdiff(tri1, tri2) + if diff < best_tri_diff: + best_tri_index = i + best_tri_diff = diff + + pretty_faces.append(prettyface((tri1, tri_lengths.pop(best_tri_index)))) + + # Get the min, max and total areas + max_area = 0.0 + min_area = 100000000.0 + tot_area = 0 + for f in face_sel: + area = f.area + if area > max_area: + max_area = area + if area < min_area: + min_area = area + tot_area += area + + max_len = sqrt(max_area) + min_len = sqrt(min_area) + side_len = sqrt(tot_area) + + # Build widths + + curr_len = max_len + + print("\tGenerating lengths...", end="") + + lengths = [] + while curr_len > min_len: + lengths.append(curr_len) + curr_len = curr_len / 2.0 + + # Dont allow boxes smaller then the margin + # since we contract on the margin, boxes that are smaller will create errors + # print(curr_len, side_len/MARGIN_DIV) + if curr_len / 4.0 < side_len / PREF_MARGIN_DIV: + break + + if not lengths: + lengths.append(curr_len) + + # convert into ints + lengths_to_ints = {} + + l_int = 1 + for l in reversed(lengths): + lengths_to_ints[l] = l_int + l_int *= 2 + + lengths_to_ints = list(lengths_to_ints.items()) + lengths_to_ints.sort() + print("done") + + # apply quantized values. + + for pf in pretty_faces: + w = pf.width + h = pf.height + bestw_diff = 1000000000.0 + besth_diff = 1000000000.0 + new_w = 0.0 + new_h = 0.0 + for l, i in lengths_to_ints: + d = abs(l - w) + if d < bestw_diff: + bestw_diff = d + new_w = i # assign the int version + + d = abs(l - h) + if d < besth_diff: + besth_diff = d + new_h = i # ditto + + pf.width = new_w + pf.height = new_h + + if new_w > new_h: + pf.spin() + + print("...done") + + # Since the boxes are sized in powers of 2, we can neatly group them into bigger squares + # this is done hierarchily, so that we may avoid running the pack function + # on many thousands of boxes, (under 1k is best) because it would get slow. + # Using an off and even dict us usefull because they are packed differently + # where w/h are the same, their packed in groups of 4 + # where they are different they are packed in pairs + # + # After this is done an external pack func is done that packs the whole group. + + print("\tConsolidating Boxes...", end="") + even_dict = {} # w/h are the same, the key is an int (w) + odd_dict = {} # w/h are different, the key is the (w,h) + + for pf in pretty_faces: + w, h = pf.width, pf.height + if w == h: + even_dict.setdefault(w, []).append(pf) + else: + odd_dict.setdefault((w, h), []).append(pf) + + # Count the number of boxes consolidated, only used for stats. + c = 0 + + # This is tricky. the total area of all packed boxes, then squt that to get an estimated size + # this is used then converted into out INT space so we can compare it with + # the ints assigned to the boxes size + # and divided by BOX_DIV, basicly if BOX_DIV is 8 + # ...then the maximum box consolidataion (recursive grouping) will have a max width & height + # ...1/8th of the UV size. + # ...limiting this is needed or you end up with bug unused texture spaces + # ...however if its too high, boxpacking is way too slow for high poly meshes. + float_to_int_factor = lengths_to_ints[0][0] + if float_to_int_factor > 0: + max_int_dimension = int(((side_len / float_to_int_factor)) / PREF_BOX_DIV) + ok = True + else: + max_int_dimension = 0.0 # wont be used + ok = False + + # RECURSIVE prettyface grouping + while ok: + ok = False + + # Tall boxes in groups of 2 + for d, boxes in odd_dict.items(): + if d[1] < max_int_dimension: + #\boxes.sort(key = lambda a: len(a.children)) + while len(boxes) >= 2: + # print("foo", len(boxes)) + ok = True + c += 1 + pf_parent = prettyface([boxes.pop(), boxes.pop()]) + pretty_faces.append(pf_parent) + + w, h = pf_parent.width, pf_parent.height + + if w > h: + raise "error" + + if w == h: + even_dict.setdefault(w, []).append(pf_parent) + else: + odd_dict.setdefault((w, h), []).append(pf_parent) + + # Even boxes in groups of 4 + for d, boxes in even_dict.items(): + if d < max_int_dimension: + boxes.sort(key=lambda a: len(a.children)) + + while len(boxes) >= 4: + # print("bar", len(boxes)) + ok = True + c += 1 + + pf_parent = prettyface([boxes.pop(), boxes.pop(), boxes.pop(), boxes.pop()]) + pretty_faces.append(pf_parent) + w = pf_parent.width # width and weight are the same + even_dict.setdefault(w, []).append(pf_parent) + + del even_dict + del odd_dict + + orig = len(pretty_faces) + + pretty_faces = [pf for pf in pretty_faces if not pf.has_parent] + + # spin every second prettyface + # if there all vertical you get less efficiently used texture space + i = len(pretty_faces) + d = 0 + while i: + i -= 1 + pf = pretty_faces[i] + if pf.width != pf.height: + d += 1 + if d % 2: # only pack every second + pf.spin() + # pass + + print("Consolidated", c, "boxes, done") + # print("done", orig, len(pretty_faces)) + + # boxes2Pack.append([islandIdx, w,h]) + print("\tPacking Boxes", len(pretty_faces), end="...") + boxes2Pack = [[0.0, 0.0, pf.width, pf.height, i] for i, pf in enumerate(pretty_faces)] + packWidth, packHeight = mathutils.geometry.box_pack_2d(boxes2Pack) + + # print(packWidth, packHeight) + + packWidth = float(packWidth) + packHeight = float(packHeight) + + margin_w = ((packWidth) / PREF_MARGIN_DIV) / packWidth + margin_h = ((packHeight) / PREF_MARGIN_DIV) / packHeight + + # print(margin_w, margin_h) + print("done") + + # Apply the boxes back to the UV coords. + print("\twriting back UVs", end="") + for i, box in enumerate(boxes2Pack): + pretty_faces[i].place(box[0], box[1], packWidth, packHeight, margin_w, margin_h) + # pf.place(box[1][1], box[1][2], packWidth, packHeight, margin_w, margin_h) + print("done") + + if PREF_APPLY_IMAGE: + if not PREF_PACK_IN_ONE: + image = Image.New("lightmap", PREF_IMG_PX_SIZE, PREF_IMG_PX_SIZE, 24) + + for f in face_sel: + # f.image = image + f.id_data.uv_textures.active.data[f.index].image = image # XXX25 + + for me in meshes: + me.update() + + print("finished all %.2f " % (time.time() - t)) + + # Window.RedrawAll() + + +def unwrap(operator, context, **kwargs): + + is_editmode = (bpy.context.object.mode == 'EDIT') + if is_editmode: + bpy.ops.object.mode_set(mode='OBJECT', toggle=False) + + PREF_ACT_ONLY = kwargs.pop("PREF_ACT_ONLY") + + meshes = [] + if PREF_ACT_ONLY: + obj = context.scene.objects.active + if obj and obj.type == 'MESH': + meshes = [obj.data] + else: + meshes = {me.name: me for obj in context.selected_objects if obj.type == 'MESH' for me in (obj.data,) if not me.library if len(me.faces)}.values() + + if not meshes: + operator.report({'ERROR'}, "No mesh object.") + return {'CANCELLED'} + + lightmap_uvpack(meshes, **kwargs) + + if is_editmode: + bpy.ops.object.mode_set(mode='EDIT', toggle=False) + + return {'FINISHED'} + +from bpy.props import BoolProperty, FloatProperty, IntProperty, EnumProperty + + +class LightMapPack(bpy.types.Operator): + '''Follow UVs from active quads along continuous face loops''' + bl_idname = "uv.lightmap_pack" + bl_label = "Lightmap Pack" + bl_options = {'REGISTER', 'UNDO'} + + PREF_CONTEXT = bpy.props.EnumProperty( + items=(("SEL_FACES", "Selected Faces", "Space all UVs evently"), + ("ALL_FACES", "All Faces", "Average space UVs edge length of each loop"), + ("ALL_OBJECTS", "Selected Mesh Object", "Average space UVs edge length of each loop") + ), + name="Selection", + description="") + + # Image & UVs... + PREF_PACK_IN_ONE = BoolProperty(name="Share Tex Space", default=True, description="Objects Share texture space, map all objects into 1 uvmap") + PREF_NEW_UVLAYER = BoolProperty(name="New UV Layer", default=False, description="Create a new UV layer for every mesh packed") + PREF_APPLY_IMAGE = BoolProperty(name="New Image", default=False, description="Assign new images for every mesh (only one if shared tex space enabled)") + PREF_IMG_PX_SIZE = IntProperty(name="Image Size", min=64, max=5000, default=512, description="Width and Height for the new image") + + # UV Packing... + PREF_BOX_DIV = IntProperty(name="Pack Quality", min=1, max=48, default=12, description="Pre Packing before the complex boxpack") + PREF_MARGIN_DIV = FloatProperty(name="Margin", min=0.001, max=1.0, default=0.1, description="Size of the margin as a division of the UV") + + def execute(self, context): + kwargs = self.as_keywords() + PREF_CONTEXT = kwargs.pop("PREF_CONTEXT") + + if PREF_CONTEXT == 'SEL_FACES': + kwargs["PREF_ACT_ONLY"] = True + kwargs["PREF_SEL_ONLY"] = True + elif PREF_CONTEXT == 'ALL_FACES': + kwargs["PREF_ACT_ONLY"] = True + kwargs["PREF_SEL_ONLY"] = False + elif PREF_CONTEXT == 'ALL_OBJECTS': + kwargs["PREF_ACT_ONLY"] = False + kwargs["PREF_SEL_ONLY"] = False + else: + raise Exception("invalid context") + + kwargs["PREF_MARGIN_DIV"] = int(1.0 / (kwargs["PREF_MARGIN_DIV"] / 100.0)) + + return unwrap(self, context, **kwargs) + + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) diff --git a/release/scripts/op/uvcalc_smart_project.py b/release/scripts/startup/bl_operators/uvcalc_smart_project.py index 8cdf593f98d..4f5b1d8b233 100644 --- a/release/scripts/op/uvcalc_smart_project.py +++ b/release/scripts/startup/bl_operators/uvcalc_smart_project.py @@ -1,31 +1,25 @@ -# -------------------------------------------------------------------------- -# Smart Projection UV Projection Unwrapper v1.2 by Campbell Barton (AKA Ideasman) -# -------------------------------------------------------------------------- -# ***** BEGIN GPL LICENSE BLOCK ***** +# ##### BEGIN GPL LICENSE BLOCK ##### # -# This program 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 2 -# of the License, or (at your option) any later version. +# This program 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 2 +# of the License, or (at your option) any later version. # -# This program 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. +# This program 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 this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # -# ***** END GPL LICENCE BLOCK ***** -# -------------------------------------------------------------------------- +# ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> from mathutils import Matrix, Vector, geometry -import time import bpy -from math import cos, radians DEG_TO_RAD = 0.017453292519943295 # pi/180.0 SMALL_NUM = 0.000000001 @@ -36,14 +30,10 @@ global USER_FILL_HOLES_QUALITY USER_FILL_HOLES = None USER_FILL_HOLES_QUALITY = None -dict_matrix = {} - def pointInTri2D(v, v1, v2, v3): - global dict_matrix - key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y - # Commented because its slower to do teh bounds check, we should realy cache the bounds info for each face. + # Commented because its slower to do the bounds check, we should realy cache the bounds info for each face. ''' # BOUNDS CHECK xmin= 1000000 @@ -268,21 +258,6 @@ def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1): h = maxy-miny return (w*h, w,h), vecs # Area, vecs -# Takes a list of faces that make up a UV island and rotate -# until they optimally fit inside a square. -ROTMAT_2D_POS_90D = Matrix.Rotation( radians(90.0), 2) -ROTMAT_2D_POS_45D = Matrix.Rotation( radians(45.0), 2) - -RotMatStepRotation = [] -rot_angle = 22.5 #45.0/2 -while rot_angle > 0.1: - RotMatStepRotation.append([\ - Matrix.Rotation( radians(rot_angle), 2),\ - Matrix.Rotation( radians(-rot_angle), 2)]) - - rot_angle = rot_angle/2.0 - - def optiRotateUvIsland(faces): global currentArea @@ -464,7 +439,7 @@ def mergeUvIslands(islandList): # if targetIsland[3] > (sourceIsland[2]) and\ # - # print USER_FREE_SPACE_TO_TEST_QUALITY, 'ass' + # print USER_FREE_SPACE_TO_TEST_QUALITY if targetIsland[2] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\ targetIsland[4] > sourceIsland[4] and\ targetIsland[5] > sourceIsland[5]: @@ -734,7 +709,7 @@ def packIslands(islandList): #print '\tPacking UV Islands...' #XXX Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) ) - time1 = time.time() + # time1 = time.time() packWidth, packHeight = geometry.box_pack_2d(packBoxes) # print 'Box Packing Time:', time.time() - time1 @@ -793,6 +768,27 @@ class thickface(object): self.area = face.area self.edge_keys = face.edge_keys + +def main_consts(): + from math import radians + + global ROTMAT_2D_POS_90D + global ROTMAT_2D_POS_45D + global RotMatStepRotation + + ROTMAT_2D_POS_90D = Matrix.Rotation( radians(90.0), 2) + ROTMAT_2D_POS_45D = Matrix.Rotation( radians(45.0), 2) + + RotMatStepRotation = [] + rot_angle = 22.5 #45.0/2 + while rot_angle > 0.1: + RotMatStepRotation.append([\ + Matrix.Rotation( radians(rot_angle), 2),\ + Matrix.Rotation( radians(-rot_angle), 2)]) + + rot_angle = rot_angle/2.0 + + global ob ob = None def main(context, island_margin, projection_limit): @@ -800,6 +796,21 @@ def main(context, island_margin, projection_limit): global USER_FILL_HOLES_QUALITY global USER_STRETCH_ASPECT global USER_ISLAND_MARGIN + + from math import cos + import time + + global dict_matrix + dict_matrix = {} + + + # Constants: + # Takes a list of faces that make up a UV island and rotate + # until they optimally fit inside a square. + global ROTMAT_2D_POS_90D + global ROTMAT_2D_POS_45D + global RotMatStepRotation + main_consts() #XXX objects= bpy.data.scenes.active.objects objects = context.selected_editable_objects @@ -868,7 +879,7 @@ def main(context, island_margin, projection_limit): time1 = time.time() - # Tag as False se we dont operate on teh same mesh twice. + # Tag as False se we dont operate on the same mesh twice. #XXX bpy.data.meshes.tag = False for me in bpy.data.meshes: me.tag = False @@ -1074,6 +1085,8 @@ def main(context, island_margin, projection_limit): if is_editmode: bpy.ops.object.mode_set(mode='EDIT') + dict_matrix.clear() + #XXX Window.DrawProgressBar(1.0, "") #XXX Window.WaitCursor(0) #XXX Window.RedrawAll() @@ -1098,7 +1111,7 @@ def main(context, island_margin, projection_limit): ] """ -from bpy.props import * +from bpy.props import FloatProperty class SmartProject(bpy.types.Operator): @@ -1123,20 +1136,6 @@ class SmartProject(bpy.types.Operator): main(context, self.island_margin, self.angle_limit) return {'FINISHED'} - -# Add to a menu -menu_func = (lambda self, context: self.layout.operator(SmartProject.bl_idname, - text="Smart Project")) - - -def register(): - bpy.utils.register_module(__name__) - bpy.types.VIEW3D_MT_uv_map.append(menu_func) - - -def unregister(): - bpy.utils.unregister_module(__name__) - bpy.types.VIEW3D_MT_uv_map.remove(menu_func) - -if __name__ == "__main__": - register() + def invoke(self, context, event): + wm = context.window_manager + return wm.invoke_props_dialog(self) diff --git a/release/scripts/op/vertexpaint_dirt.py b/release/scripts/startup/bl_operators/vertexpaint_dirt.py index d5060e913ae..672db71e361 100644 --- a/release/scripts/op/vertexpaint_dirt.py +++ b/release/scripts/startup/bl_operators/vertexpaint_dirt.py @@ -30,16 +30,10 @@ # but results are far more accurate # -import bpy -import math -import time - -from mathutils import Vector -from bpy.props import * - def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, dirt_only): -## Window.WaitCursor(1) + from mathutils import Vector + from math import acos #BPyMesh.meshCalcNormals(me) @@ -76,7 +70,7 @@ def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, vec /= tot_con # angle is the acos of the dot product between vert and connected verts normals - ang = math.acos(no.dot(vec)) + ang = acos(no.dot(vec)) # enforce min/max ang = max(clamp_dirt, ang) @@ -146,11 +140,12 @@ def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, col[1] = tone * col[1] col[2] = tone * col[2] -## Window.WaitCursor(0) +import bpy +from bpy.props import FloatProperty, IntProperty, BoolProperty -class VertexPaintDirt(bpy.types.Operator): +class VertexPaintDirt(bpy.types.Operator): bl_idname = "paint.vertex_color_dirt" bl_label = "Dirty Vertex Colors" bl_options = {'REGISTER', 'UNDO'} @@ -162,29 +157,20 @@ class VertexPaintDirt(bpy.types.Operator): dirt_only = BoolProperty(name="Dirt Only", description="Dont calculate cleans for convex areas", default=False) def execute(self, context): + import time + from math import radians obj = context.object if not obj or obj.type != 'MESH': - print('Error, no active mesh object, aborting') - return('CANCELLED',) + self.report({'ERROR'}, "Error, no active mesh object, aborting") + return {'CANCELLED'} mesh = obj.data t = time.time() - applyVertexDirt(mesh, self.blur_iterations, self.blur_strength, math.radians(self.dirt_angle), math.radians(self.clean_angle), self.dirt_only) + applyVertexDirt(mesh, self.blur_iterations, self.blur_strength, radians(self.dirt_angle), radians(self.clean_angle), self.dirt_only) print('Dirt calculated in %.6f' % (time.time() - t)) return {'FINISHED'} - - -def register(): - bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/op/wm.py b/release/scripts/startup/bl_operators/wm.py index cbc9e3cd55f..53c8d562297 100644 --- a/release/scripts/op/wm.py +++ b/release/scripts/startup/bl_operators/wm.py @@ -19,8 +19,7 @@ # <pep8 compliant> import bpy - -from bpy.props import * +from bpy.props import StringProperty, BoolProperty, IntProperty, FloatProperty from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear @@ -51,7 +50,7 @@ rna_relative_prop = BoolProperty(name="Relative", def context_path_validate(context, data_path): import sys try: - value = eval("context.%s" % data_path) + value = eval("context.%s" % data_path) if data_path else Ellipsis except AttributeError: if "'NoneType'" in str(sys.exc_info()[1]): # One of the items in the rna path is None, just ignore this @@ -75,20 +74,20 @@ def execute_context_assign(self, context): return {'FINISHED'} -class BRUSH_OT_set_active_number(bpy.types.Operator): +class BRUSH_OT_active_index_set(bpy.types.Operator): '''Set active sculpt/paint brush from it's number''' - bl_idname = "brush.set_active_number" + bl_idname = "brush.active_index_set" bl_label = "Set Brush Number" mode = StringProperty(name="mode", description="Paint mode to set brush for", maxlen=1024) - number = IntProperty(name="number", + index = IntProperty(name="number", description="Brush number") _attr_dict = {"sculpt": "use_paint_sculpt", "vertex_paint": "use_paint_vertex", "weight_paint": "use_paint_weight", - "image_paint": "use_paint_texture"} + "image_paint": "use_paint_image"} def execute(self, context): attr = self._attr_dict.get(self.mode) @@ -96,7 +95,7 @@ class BRUSH_OT_set_active_number(bpy.types.Operator): return {'CANCELLED'} for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))): - if i == self.number: + if i == self.index: getattr(context.tool_settings, self.mode).brush = brush return {'FINISHED'} @@ -336,7 +335,7 @@ class WM_OT_context_cycle_enum(bpy.types.Operator): if type(rna_prop) != bpy.types.EnumProperty: raise Exception("expected an enum property") - enums = rna_struct.properties[rna_prop_str].items.keys() + enums = rna_struct.properties[rna_prop_str].enum_items.keys() orig_index = enums.index(orig_value) # Have the info we need, advance to the next item @@ -384,6 +383,39 @@ class WM_OT_context_cycle_array(bpy.types.Operator): return {'FINISHED'} +class WM_MT_context_menu_enum(bpy.types.Menu): + bl_label = "" + data_path = "" # BAD DESIGN, set from operator below. + + def draw(self, context): + data_path = self.data_path + value = context_path_validate(bpy.context, data_path) + if value is Ellipsis: + return {'PASS_THROUGH'} + base_path, prop_string = data_path.rsplit(".", 1) + value_base = context_path_validate(context, base_path) + + values = [(i.name, i.identifier) for i in value_base.bl_rna.properties[prop_string].enum_items] + + for name, identifier in values: + prop = self.layout.operator("wm.context_set_enum", text=name) + prop.data_path = data_path + prop.value = identifier + + +class WM_OT_context_menu_enum(bpy.types.Operator): + bl_idname = "wm.context_menu_enum" + bl_label = "Context Enum Menu" + bl_options = {'UNDO'} + data_path = rna_path_prop + + def execute(self, context): + data_path = self.data_path + WM_MT_context_menu_enum.data_path = data_path + bpy.ops.wm.call_menu(name="WM_MT_context_menu_enum") + return {'PASS_THROUGH'} + + class WM_OT_context_set_id(bpy.types.Operator): '''Toggle a context value.''' bl_idname = "wm.context_set_id" @@ -520,6 +552,7 @@ class WM_OT_url_open(bpy.types.Operator): def execute(self, context): import webbrowser + _webbrowser_bug_fix() webbrowser.open(self.url) return {'FINISHED'} @@ -543,7 +576,7 @@ class WM_OT_path_open(bpy.types.Operator): self.report({'ERROR'}, "File '%s' not found" % filepath) return {'CANCELLED'} - if sys.platform == 'win32': + if sys.platform[:3] == "win": subprocess.Popen(['start', filepath], shell=True) elif sys.platform == 'darwin': subprocess.Popen(['open', filepath]) @@ -563,7 +596,10 @@ class WM_OT_doc_view(bpy.types.Operator): bl_label = "View Documentation" doc_id = doc_id - _prefix = "http://www.blender.org/documentation/blender_python_api_%s" % "_".join(str(v) for v in bpy.app.version) + if bpy.app.version_cycle == "release": + _prefix = "http://www.blender.org/documentation/blender_python_api_%s%s_release" % ("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char) + else: + _prefix = "http://www.blender.org/documentation/blender_python_api_%s" % "_".join(str(v) for v in bpy.app.version) def _nested_class_string(self, class_string): ls = [] @@ -584,6 +620,15 @@ class WM_OT_doc_view(bpy.types.Operator): url = '%s/bpy.ops.%s.html#bpy.ops.%s.%s' % \ (self._prefix, class_name, class_name, class_prop) else: + + # detect if this is a inherited member and use that name instead + rna_parent = getattr(bpy.types, class_name).bl_rna + rna_prop = rna_parent.properties[class_prop] + rna_parent = rna_parent.base + while rna_parent and rna_prop == rna_parent.properties.get(class_prop): + class_name = rna_parent.identifier + rna_parent = rna_parent.base + # It so happens that epydoc nests these, not sphinx # class_name_full = self._nested_class_string(class_name) url = '%s/bpy.types.%s.html#bpy.types.%s.%s' % \ @@ -593,6 +638,7 @@ class WM_OT_doc_view(bpy.types.Operator): return {'PASS_THROUGH'} import webbrowser + _webbrowser_bug_fix() webbrowser.open(url) return {'FINISHED'} @@ -669,9 +715,6 @@ class WM_OT_doc_edit(bpy.types.Operator): return wm.invoke_props_dialog(self, width=600) -from bpy.props import * - - rna_path = StringProperty(name="Property Edit", description="Property data_path edit", maxlen=1024, default="", options={'HIDDEN'}) @@ -702,7 +745,12 @@ class WM_OT_properties_edit(bpy.types.Operator): data_path = self.data_path value = self.value prop = self.property - prop_old = self._last_prop[0] + + prop_old = getattr(self, "_last_prop", [None])[0] + + if prop_old is None: + self.report({'ERROR'}, "Direct execution not supported") + return {'CANCELLED'} try: value_eval = eval(value) @@ -734,10 +782,21 @@ class WM_OT_properties_edit(bpy.types.Operator): prop_ui['description'] = self.description + # otherwise existing buttons which reference freed + # memory may crash blender [#26510] + # context.area.tag_redraw() + for win in context.window_manager.windows: + for area in win.screen.areas: + area.tag_redraw() + return {'FINISHED'} def invoke(self, context, event): + if not self.data_path: + self.report({'ERROR'}, "Data path not set") + return {'CANCELLED'} + self._last_prop = [self.property] item = eval("context.%s" % self.data_path) @@ -779,6 +838,18 @@ class WM_OT_properties_add(bpy.types.Operator): return {'FINISHED'} +class WM_OT_properties_context_change(bpy.types.Operator): + "Change the context tab in a Properties Window" + bl_idname = "wm.properties_context_change" + bl_label = "" + + context = StringProperty(name="Context", maxlen=32) + + def execute(self, context): + context.space_data.context = (self.context) + return {'FINISHED'} + + class WM_OT_properties_remove(bpy.types.Operator): '''Internal use (edit a property data_path)''' bl_idname = "wm.properties_remove" @@ -804,6 +875,41 @@ class WM_OT_keyconfig_activate(bpy.types.Operator): return {'FINISHED'} +class WM_OT_appconfig_default(bpy.types.Operator): + bl_idname = "wm.appconfig_default" + bl_label = "Default Application Configuration" + + def execute(self, context): + import os + + context.window_manager.keyconfigs.active = context.window_manager.keyconfigs.default + + filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py") + + if os.path.exists(filepath): + bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets") + + return {'FINISHED'} + + +class WM_OT_appconfig_activate(bpy.types.Operator): + bl_idname = "wm.appconfig_activate" + bl_label = "Activate Application Configuration" + + filepath = StringProperty(name="File Path", maxlen=1024) + + def execute(self, context): + import os + bpy.utils.keyconfig_set(self.filepath) + + filepath = self.filepath.replace("keyconfig", "interaction") + + if os.path.exists(filepath): + bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets") + + return {'FINISHED'} + + class WM_OT_sysinfo(bpy.types.Operator): '''Generate System Info''' bl_idname = "wm.sysinfo" @@ -815,12 +921,92 @@ class WM_OT_sysinfo(bpy.types.Operator): return {'FINISHED'} -def register(): - bpy.utils.register_module(__name__) +class WM_OT_copy_prev_settings(bpy.types.Operator): + '''Copy settings from previous version''' + bl_idname = "wm.copy_prev_settings" + bl_label = "Copy Previous Settings" + + def execute(self, context): + import os + import shutil + ver = bpy.app.version + ver_old = ((ver[0] * 100) + ver[1]) - 1 + path_src = bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100) + path_dst = bpy.utils.resource_path('USER') + + if os.path.isdir(path_dst): + self.report({'ERROR'}, "Target path %r exists" % path_dst) + elif not os.path.isdir(path_src): + self.report({'ERROR'}, "Source path %r exists" % path_src) + else: + shutil.copytree(path_src, path_dst) + # dont loose users work if they open the splash later. + if bpy.data.is_saved is bpy.data.is_dirty is False: + bpy.ops.wm.read_homefile() + else: + self.report({'INFO'}, "Reload Start-Up file to restore settings.") + return {'FINISHED'} + + return {'CANCELLED'} + + +def _webbrowser_bug_fix(): + # test for X11 + import os + + if os.environ.get("DISPLAY"): + + # BSD licenced code copied from python, temp fix for bug + # http://bugs.python.org/issue11432, XXX == added code + def _invoke(self, args, remote, autoraise): + # XXX, added imports + import io + import subprocess + import time + raise_opt = [] + if remote and self.raise_opts: + # use autoraise argument only for remote invocation + autoraise = int(autoraise) + opt = self.raise_opts[autoraise] + if opt: + raise_opt = [opt] -def unregister(): - bpy.utils.unregister_module(__name__) + cmdline = [self.name] + raise_opt + args -if __name__ == "__main__": - register() + if remote or self.background: + inout = io.open(os.devnull, "r+") + else: + # for TTY browsers, we need stdin/out + inout = None + # if possible, put browser in separate process group, so + # keyboard interrupts don't affect browser as well as Python + setsid = getattr(os, 'setsid', None) + if not setsid: + setsid = getattr(os, 'setpgrp', None) + + p = subprocess.Popen(cmdline, close_fds=True, # XXX, stdin=inout, + stdout=(self.redirect_stdout and inout or None), + stderr=inout, preexec_fn=setsid) + if remote: + # wait five secons. If the subprocess is not finished, the + # remote invocation has (hopefully) started a new instance. + time.sleep(1) + rc = p.poll() + if rc is None: + time.sleep(4) + rc = p.poll() + if rc is None: + return True + # if remote call failed, open() will try direct invocation + return not rc + elif self.background: + if p.poll() is None: + return True + else: + return False + else: + return not p.wait() + + import webbrowser + webbrowser.UnixBrowser._invoke = _invoke diff --git a/release/scripts/startup/bl_ui/__init__.py b/release/scripts/startup/bl_ui/__init__.py new file mode 100644 index 00000000000..5c565fbd300 --- /dev/null +++ b/release/scripts/startup/bl_ui/__init__.py @@ -0,0 +1,121 @@ +# ##### BEGIN GPL LICENSE BLOCK ##### +# +# This program 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 2 +# of the License, or (at your option) any later version. +# +# This program 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 this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +# note, properties_animviz is a helper module only. + +if "bpy" in locals(): + from imp import reload as _reload + for val in _modules_loaded.values(): + _reload(val) +_modules = ( + "properties_animviz", + "properties_data_armature", + "properties_data_bone", + "properties_data_camera", + "properties_data_curve", + "properties_data_empty", + "properties_data_lamp", + "properties_data_lattice", + "properties_data_mesh", + "properties_data_metaball", + "properties_data_modifier", + "properties_game", + "properties_material", + "properties_object_constraint", + "properties_object", + "properties_particle", + "properties_physics_cloth", + "properties_physics_common", + "properties_physics_field", + "properties_physics_fluid", + "properties_physics_smoke", + "properties_physics_softbody", + "properties_render", + "properties_scene", + "properties_texture", + "properties_world", + "space_console", + "space_dopesheet", + "space_filebrowser", + "space_graph", + "space_image", + "space_info", + "space_logic", + "space_nla", + "space_node", + "space_outliner", + "space_sequencer", + "space_text", + "space_time", + "space_userpref_keymap", + "space_userpref", + "space_view3d", + "space_view3d_toolbar", +) +__import__(name=__name__, fromlist=_modules) +_namespace = globals() +_modules_loaded = {name: _namespace[name] for name in _modules} +del _namespace + + +import bpy + + +def register(): + bpy.utils.register_module(__name__) + + # space_userprefs.py + from bpy.props import StringProperty, EnumProperty + WindowManager = bpy.types.WindowManager + + WindowManager.addon_search = StringProperty(name="Search", description="Search within the selected filter") + WindowManager.addon_filter = EnumProperty( + items=[('All', "All", ""), + ('Enabled', "Enabled", ""), + ('Disabled', "Disabled", ""), + ('3D View', "3D View", ""), + ('Add Curve', "Add Curve", ""), + ('Add Mesh', "Add Mesh", ""), + ('Animation', "Animation", ""), + ('Development', "Development", ""), + ('Game Engine', "Game Engine", ""), + ('Import-Export', "Import-Export", ""), + ('Mesh', "Mesh", ""), + ('Object', "Object", ""), + ('Render', "Render", ""), + ('Rigging', "Rigging", ""), + ('Text Editor', "Text Editor", ""), + ('System', "System", "") + ], + name="Category", + description="Filter add-ons by category", + ) + + WindowManager.addon_support = EnumProperty( + items=[('OFFICIAL', "Official", ""), + ('COMMUNITY', 'Community', ""), + ], + name="Support", + description="Display support level", default={'OFFICIAL', 'COMMUNITY'}, options={'ENUM_FLAG'}) + # done... + + +def unregister(): + bpy.utils.unregister_module(__name__) diff --git a/release/scripts/ui/properties_animviz.py b/release/scripts/startup/bl_ui/properties_animviz.py index 5f5684f5c89..eb1bbfd2fb1 100644 --- a/release/scripts/ui/properties_animviz.py +++ b/release/scripts/startup/bl_ui/properties_animviz.py @@ -17,11 +17,13 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> -import bpy - # Generic Panels (Independent of DataType) +# NOTE: +# The specialised panel types are derived in their respective UI modules +# dont register these classes since they are only helpers. + class MotionPathButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -91,16 +93,5 @@ class OnionSkinButtonsPanel(): col.label(text="Display:") col.prop(arm, "show_only_ghost_selected", text="Selected Only") - -# NOTE: -# The specialised panel types are derived in their respective UI modules -# dont register these classes since they are only helpers. -def register(): - pass # bpy.utils.register_module(__name__) - - -def unregister(): - pass # bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() +if __name__ == "__main__": # only for live edit. + bpy.utils.register_module(__name__) diff --git a/release/scripts/ui/properties_data_armature.py b/release/scripts/startup/bl_ui/properties_data_armature.py index 6550e1bb1ed..9477dc866ab 100644 --- a/release/scripts/ui/properties_data_armature.py +++ b/release/scripts/startup/bl_ui/properties_data_armature.py @@ -58,24 +58,17 @@ class DATA_PT_skeleton(ArmatureButtonsPanel, bpy.types.Panel): layout.prop(arm, "pose_position", expand=True) - split = layout.split() - - col = split.column() + col = layout.column() col.label(text="Layers:") col.prop(arm, "layers", text="") col.label(text="Protected Layers:") col.prop(arm, "layers_protected", text="") - col.label(text="Deform:") - - split = layout.split() - - col = split.column() - col.prop(arm, "use_deform_vertex_groups", text="Vertex Groups") - col.prop(arm, "use_deform_envelopes", text="Envelopes") - - col = split.column() - col.prop(arm, "use_deform_preserve_volume", text="Quaternion") + layout.label(text="Deform:") + flow = layout.column_flow() + flow.prop(arm, "use_deform_vertex_groups", text="Vertex Groups") + flow.prop(arm, "use_deform_envelopes", text="Envelopes") + flow.prop(arm, "use_deform_preserve_volume", text="Quaternion") class DATA_PT_display(ArmatureButtonsPanel, bpy.types.Panel): @@ -87,7 +80,7 @@ class DATA_PT_display(ArmatureButtonsPanel, bpy.types.Panel): ob = context.object arm = context.armature - layout.row().prop(arm, "draw_type", expand=True) + layout.prop(arm, "draw_type", expand=True) split = layout.split() @@ -137,10 +130,10 @@ class DATA_PT_bone_groups(ArmatureButtonsPanel, bpy.types.Panel): col.prop(group, "color_set") if group.color_set: col = split.column() - subrow = col.row(align=True) - subrow.prop(group.colors, "normal", text="") - subrow.prop(group.colors, "select", text="") - subrow.prop(group.colors, "active", text="") + sub = col.row(align=True) + sub.prop(group.colors, "normal", text="") + sub.prop(group.colors, "select", text="") + sub.prop(group.colors, "active", text="") row = layout.row() row.active = (ob.proxy is None) @@ -154,6 +147,44 @@ class DATA_PT_bone_groups(ArmatureButtonsPanel, bpy.types.Panel): sub.operator("pose.group_deselect", text="Deselect") +class DATA_PT_pose_library(ArmatureButtonsPanel, bpy.types.Panel): + bl_label = "Pose Library" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + return (context.object and context.object.type == 'ARMATURE' and context.object.pose) + + def draw(self, context): + layout = self.layout + + ob = context.object + poselib = ob.pose_library + + layout.template_ID(ob, "pose_library", new="poselib.new", unlink="poselib.unlink") + + if poselib: + row = layout.row() + row.template_list(poselib, "pose_markers", poselib.pose_markers, "active_index", rows=5) + + col = row.column(align=True) + col.active = (poselib.library is None) + + # invoke should still be used for 'add', as it is needed to allow + # add/replace options to be used properly + col.operator("poselib.pose_add", icon='ZOOMIN', text="") + + col.operator_context = 'EXEC_DEFAULT' # exec not invoke, so that menu doesn't need showing + + pose_marker_active = poselib.pose_markers.active + + if pose_marker_active is not None: + col.operator("poselib.pose_remove", icon='ZOOMOUT', text="").pose = pose_marker_active.name + col.operator("poselib.apply_pose", icon='ZOOM_SELECTED', text="").pose_index = poselib.pose_markers.active_index + + layout.operator("poselib.action_sanitise") + + # TODO: this panel will soon be depreceated too class DATA_PT_ghost(ArmatureButtonsPanel, bpy.types.Panel): bl_label = "Ghost" @@ -167,16 +198,15 @@ class DATA_PT_ghost(ArmatureButtonsPanel, bpy.types.Panel): split = layout.split() - col = split.column() + col = split.column(align=True) - sub = col.column(align=True) if arm.ghost_type == 'RANGE': - sub.prop(arm, "ghost_frame_start", text="Start") - sub.prop(arm, "ghost_frame_end", text="End") - sub.prop(arm, "ghost_size", text="Step") + col.prop(arm, "ghost_frame_start", text="Start") + col.prop(arm, "ghost_frame_end", text="End") + col.prop(arm, "ghost_size", text="Step") elif arm.ghost_type == 'CURRENT_FRAME': - sub.prop(arm, "ghost_step", text="Range") - sub.prop(arm, "ghost_size", text="Step") + col.prop(arm, "ghost_step", text="Range") + col.prop(arm, "ghost_size", text="Step") col = split.column() col.label(text="Display:") @@ -196,11 +226,9 @@ class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, bpy.types.Panel): layout = self.layout ob = context.object - itasc = ob.pose.ik_param - row = layout.row() - row.prop(ob.pose, "ik_solver") + layout.prop(ob.pose, "ik_solver") if itasc: layout.prop(itasc, "mode", expand=True) @@ -209,13 +237,10 @@ class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, bpy.types.Panel): layout.label(text="Reiteration:") layout.prop(itasc, "reiteration_method", expand=True) - split = layout.split() - split.active = not simulation or itasc.reiteration_method != 'NEVER' - col = split.column() - col.prop(itasc, "precision") - - col = split.column() - col.prop(itasc, "iterations") + row = layout.row() + row.active = not simulation or itasc.reiteration_method != 'NEVER' + row.prop(itasc, "precision") + row.prop(itasc, "iterations") if simulation: layout.prop(itasc, "use_auto_step") @@ -235,7 +260,10 @@ class DATA_PT_iksolver_itasc(ArmatureButtonsPanel, bpy.types.Panel): row.prop(itasc, "damping_max", text="Damp", slider=True) row.prop(itasc, "damping_epsilon", text="Eps", slider=True) -from properties_animviz import MotionPathButtonsPanel, OnionSkinButtonsPanel +from bl_ui.properties_animviz import ( + MotionPathButtonsPanel, + OnionSkinButtonsPanel, + ) class DATA_PT_motion_paths(MotionPathButtonsPanel, bpy.types.Panel): @@ -257,12 +285,8 @@ class DATA_PT_motion_paths(MotionPathButtonsPanel, bpy.types.Panel): layout.separator() split = layout.split() - - col = split.column() - col.operator("pose.paths_calculate", text="Calculate Paths") - - col = split.column() - col.operator("pose.paths_clear", text="Clear Paths") + split.operator("pose.paths_calculate", text="Calculate Paths") + split.operator("pose.paths_clear", text="Clear Paths") class DATA_PT_onion_skinning(OnionSkinButtonsPanel): # , bpy.types.Panel): # inherit from panel when ready @@ -287,13 +311,5 @@ class DATA_PT_custom_props_arm(ArmatureButtonsPanel, PropertyPanel, bpy.types.Pa _context_path = "object.data" _property_type = bpy.types.Armature - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_bone.py b/release/scripts/startup/bl_ui/properties_data_bone.py index feca4fc2502..9fc055e9343 100644 --- a/release/scripts/ui/properties_data_bone.py +++ b/release/scripts/startup/bl_ui/properties_data_bone.py @@ -17,6 +17,7 @@ # ##### END GPL LICENSE BLOCK ##### # <pep8 compliant> + import bpy from rna_prop_ui import PropertyPanel @@ -123,7 +124,7 @@ class BONE_PT_transform_locks(BoneButtonsPanel, bpy.types.Panel): col.active = not (bone.parent and bone.use_connect) col = row.column() - if pchan.rotation_mode in ('QUATERNION', 'AXIS_ANGLE'): + if pchan.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}: col.prop(pchan, "lock_rotations_4d", text="Lock Rotation") if pchan.lock_rotations_4d: col.prop(pchan, "lock_rotation_w", text="W") @@ -368,13 +369,5 @@ class BONE_PT_custom_props(BoneButtonsPanel, PropertyPanel, bpy.types.Panel): else: return "active_bone" - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_camera.py b/release/scripts/startup/bl_ui/properties_data_camera.py index 0f5d4a59bf5..e5076fd20d5 100644 --- a/release/scripts/ui/properties_data_camera.py +++ b/release/scripts/startup/bl_ui/properties_data_camera.py @@ -78,7 +78,14 @@ class DATA_PT_camera(CameraButtonsPanel, bpy.types.Panel): elif cam.type == 'ORTHO': col.prop(cam, "ortho_scale") - layout.prop(cam, "use_panorama") + col = layout.column() + if cam.type == 'ORTHO': + if cam.use_panorama: + col.alert = True + else: + col.enabled = False + + col.prop(cam, "use_panorama") split = layout.split() @@ -95,9 +102,7 @@ class DATA_PT_camera(CameraButtonsPanel, bpy.types.Panel): layout.label(text="Depth of Field:") split = layout.split() - - col = split.column() - col.prop(cam, "dof_object", text="") + split.prop(cam, "dof_object", text="") col = split.column() @@ -137,13 +142,5 @@ class DATA_PT_custom_props_camera(CameraButtonsPanel, PropertyPanel, bpy.types.P _context_path = "object.data" _property_type = bpy.types.Camera - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_curve.py b/release/scripts/startup/bl_ui/properties_data_curve.py index b26b7140fd2..623daffac93 100644 --- a/release/scripts/ui/properties_data_curve.py +++ b/release/scripts/startup/bl_ui/properties_data_curve.py @@ -28,7 +28,7 @@ class CurveButtonsPanel(): @classmethod def poll(cls, context): - return (context.object and context.object.type in ('CURVE', 'SURFACE', 'FONT') and context.curve) + return (context.object and context.object.type in {'CURVE', 'SURFACE', 'FONT'} and context.curve) class CurveButtonsPanelCurve(CurveButtonsPanel): @@ -106,12 +106,12 @@ class DATA_PT_shape_curve(CurveButtonsPanel, bpy.types.Panel): sub.prop(curve, "render_resolution_v", text="Render V") if (is_curve or is_text): + col.label(text="Fill:") sub = col.column() sub.active = (curve.bevel_object is None) - sub.label(text="Fill:") sub.prop(curve, "use_fill_front") sub.prop(curve, "use_fill_back") - sub.prop(curve, "use_fill_deform", text="Fill Deformed") + col.prop(curve, "use_fill_deform", text="Fill Deformed") col.label(text="Textures:") col.prop(curve, "use_uv_as_generated") @@ -312,12 +312,9 @@ class DATA_PT_font(CurveButtonsPanel, bpy.types.Panel): col.prop(char, "use_italic") col.prop(char, "use_underline") - split = layout.split() - col = split.column() - col.prop(text, "small_caps_scale", text="Small Caps") - - col = split.column() - col.prop(char, "use_small_caps") + row = layout.row() + row.prop(text, "small_caps_scale", text="Small Caps") + row.prop(char, "use_small_caps") class DATA_PT_paragraph(CurveButtonsPanel, bpy.types.Panel): @@ -394,13 +391,5 @@ class DATA_PT_custom_props_curve(CurveButtonsPanel, PropertyPanel, bpy.types.Pan _context_path = "object.data" _property_type = bpy.types.Curve - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_empty.py b/release/scripts/startup/bl_ui/properties_data_empty.py index bb0028efec5..e46cd1270ad 100644 --- a/release/scripts/ui/properties_data_empty.py +++ b/release/scripts/startup/bl_ui/properties_data_empty.py @@ -39,16 +39,7 @@ class DATA_PT_empty(DataButtonsPanel, bpy.types.Panel): ob = context.object layout.prop(ob, "empty_draw_type", text="Display") - layout.prop(ob, "empty_draw_size", text="Size") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_lamp.py b/release/scripts/startup/bl_ui/properties_data_lamp.py index 55781434763..36010c8b511 100644 --- a/release/scripts/ui/properties_data_lamp.py +++ b/release/scripts/startup/bl_ui/properties_data_lamp.py @@ -91,7 +91,7 @@ class DATA_PT_lamp(DataButtonsPanel, bpy.types.Panel): sub.prop(lamp, "color", text="") sub.prop(lamp, "energy") - if lamp.type in ('POINT', 'SPOT'): + if lamp.type in {'POINT', 'SPOT'}: sub.label(text="Falloff:") sub.prop(lamp, "falloff_type", text="") sub.prop(lamp, "distance") @@ -195,7 +195,7 @@ class DATA_PT_shadow(DataButtonsPanel, bpy.types.Panel): def poll(cls, context): lamp = context.lamp engine = context.scene.render.engine - return (lamp and lamp.type in ('POINT', 'SUN', 'SPOT', 'AREA')) and (engine in cls.COMPAT_ENGINES) + return (lamp and lamp.type in {'POINT', 'SUN', 'SPOT', 'AREA'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout @@ -234,7 +234,7 @@ class DATA_PT_shadow(DataButtonsPanel, bpy.types.Panel): col = split.column() col.label(text="Sampling:") - if lamp.type in ('POINT', 'SUN', 'SPOT'): + if lamp.type in {'POINT', 'SUN', 'SPOT'}: sub = col.row() sub.prop(lamp, "shadow_ray_samples", text="Samples") @@ -251,26 +251,21 @@ class DATA_PT_shadow(DataButtonsPanel, bpy.types.Panel): col.row().prop(lamp, "shadow_ray_sample_method", expand=True) - split = layout.split() - col = split.column() - if lamp.shadow_ray_sample_method == 'ADAPTIVE_QMC': - col.prop(lamp, "shadow_adaptive_threshold", text="Threshold") - col = split.column() + layout.prop(lamp, "shadow_adaptive_threshold", text="Threshold") if lamp.type == 'AREA' and lamp.shadow_ray_sample_method == 'CONSTANT_JITTERED': - col = split.column() - col = split.column() - col.prop(lamp, "use_umbra") - col.prop(lamp, "use_dither") - col.prop(lamp, "use_jitter") + row = layout.row() + row.prop(lamp, "use_umbra") + row.prop(lamp, "use_dither") + row.prop(lamp, "use_jitter") elif lamp.shadow_method == 'BUFFER_SHADOW': col = layout.column() col.label(text="Buffer Type:") col.row().prop(lamp, "shadow_buffer_type", expand=True) - if lamp.shadow_buffer_type in ('REGULAR', 'HALFWAY', 'DEEP'): + if lamp.shadow_buffer_type in {'REGULAR', 'HALFWAY', 'DEEP'}: split = layout.split() col = split.column() @@ -318,13 +313,11 @@ class DATA_PT_area(DataButtonsPanel, bpy.types.Panel): return (lamp and lamp.type == 'AREA') and (engine in cls.COMPAT_ENGINES) def draw(self, context): - lamp = context.lamp - layout = self.layout - split = layout.split() - col = split.column() + lamp = context.lamp + col = layout.column() col.row().prop(lamp, "shape", expand=True) sub = col.row(align=True) @@ -379,7 +372,7 @@ class DATA_PT_falloff_curve(DataButtonsPanel, bpy.types.Panel): lamp = context.lamp engine = context.scene.render.engine - return (lamp and lamp.type in ('POINT', 'SPOT') and lamp.falloff_type == 'CUSTOM_CURVE') and (engine in cls.COMPAT_ENGINES) + return (lamp and lamp.type in {'POINT', 'SPOT'} and lamp.falloff_type == 'CUSTOM_CURVE') and (engine in cls.COMPAT_ENGINES) def draw(self, context): lamp = context.lamp @@ -392,13 +385,5 @@ class DATA_PT_custom_props_lamp(DataButtonsPanel, PropertyPanel, bpy.types.Panel _context_path = "object.data" _property_type = bpy.types.Lamp - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_lattice.py b/release/scripts/startup/bl_ui/properties_data_lattice.py index 6bdb36d3520..cd719b6fe84 100644 --- a/release/scripts/ui/properties_data_lattice.py +++ b/release/scripts/startup/bl_ui/properties_data_lattice.py @@ -59,23 +59,17 @@ class DATA_PT_lattice(DataButtonsPanel, bpy.types.Panel): lat = context.lattice - split = layout.split() - col = split.column() - col.prop(lat, "points_u") - col = split.column() - col.prop(lat, "interpolation_type_u", text="") - - split = layout.split() - col = split.column() - col.prop(lat, "points_v") - col = split.column() - col.prop(lat, "interpolation_type_v", text="") - - split = layout.split() - col = split.column() - col.prop(lat, "points_w") - col = split.column() - col.prop(lat, "interpolation_type_w", text="") + row = layout.row() + row.prop(lat, "points_u") + row.prop(lat, "interpolation_type_u", text="") + + row = layout.row() + row.prop(lat, "points_v") + row.prop(lat, "interpolation_type_v", text="") + + row = layout.row() + row.prop(lat, "points_w") + row.prop(lat, "interpolation_type_w", text="") row = layout.row() row.prop(lat, "use_outside") @@ -87,13 +81,5 @@ class DATA_PT_custom_props_lattice(DataButtonsPanel, PropertyPanel, bpy.types.Pa _context_path = "object.data" _property_type = bpy.types.Lattice - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_mesh.py b/release/scripts/startup/bl_ui/properties_data_mesh.py index 342204d0e78..b1d1789fadd 100644 --- a/release/scripts/ui/properties_data_mesh.py +++ b/release/scripts/startup/bl_ui/properties_data_mesh.py @@ -94,9 +94,7 @@ class DATA_PT_normals(MeshButtonsPanel, bpy.types.Panel): sub.active = mesh.use_auto_smooth sub.prop(mesh, "auto_smooth_angle", text="Angle") - col = split.column() - - col.prop(mesh, "show_double_sided") + split.prop(mesh, "show_double_sided") class DATA_PT_settings(MeshButtonsPanel, bpy.types.Panel): @@ -120,7 +118,7 @@ class DATA_PT_vertex_groups(MeshButtonsPanel, bpy.types.Panel): def poll(cls, context): engine = context.scene.render.engine obj = context.object - return (obj and obj.type in ('MESH', 'LATTICE') and (engine in cls.COMPAT_ENGINES)) + return (obj and obj.type in {'MESH', 'LATTICE'} and (engine in cls.COMPAT_ENGINES)) def draw(self, context): layout = self.layout @@ -169,7 +167,7 @@ class DATA_PT_shape_keys(MeshButtonsPanel, bpy.types.Panel): def poll(cls, context): engine = context.scene.render.engine obj = context.object - return (obj and obj.type in ('MESH', 'LATTICE', 'CURVE', 'SURFACE') and (engine in cls.COMPAT_ENGINES)) + return (obj and obj.type in {'MESH', 'LATTICE', 'CURVE', 'SURFACE'} and (engine in cls.COMPAT_ENGINES)) def draw(self, context): layout = self.layout @@ -190,7 +188,7 @@ class DATA_PT_shape_keys(MeshButtonsPanel, bpy.types.Panel): rows = 2 if kb: rows = 5 - row.template_list(key, "keys", ob, "active_shape_key_index", rows=rows) + row.template_list(key, "key_blocks", ob, "active_shape_key_index", rows=rows) col = row.column() @@ -245,7 +243,7 @@ class DATA_PT_shape_keys(MeshButtonsPanel, bpy.types.Panel): col.active = enable_edit_value col.label(text="Blend:") col.prop_search(kb, "vertex_group", ob, "vertex_groups", text="") - col.prop_search(kb, "relative_key", key, "keys", text="") + col.prop_search(kb, "relative_key", key, "key_blocks", text="") else: row = layout.row() @@ -283,7 +281,6 @@ class DATA_PT_texface(MeshButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): ob = context.active_object - rd = context.scene.render return (context.mode == 'EDIT_MESH') and ob and ob.type == 'MESH' @@ -353,13 +350,5 @@ class DATA_PT_custom_props_mesh(MeshButtonsPanel, PropertyPanel, bpy.types.Panel _context_path = "object.data" _property_type = bpy.types.Mesh - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_metaball.py b/release/scripts/startup/bl_ui/properties_data_metaball.py index cf7aa8c08fd..81ba15d6f40 100644 --- a/release/scripts/ui/properties_data_metaball.py +++ b/release/scripts/startup/bl_ui/properties_data_metaball.py @@ -96,7 +96,7 @@ class DATA_PT_metaball_element(DataButtonsPanel, bpy.types.Panel): col = split.column(align=True) - if metaelem.type in ('CUBE', 'ELLIPSOID'): + if metaelem.type in {'CUBE', 'ELLIPSOID'}: col.label(text="Size:") col.prop(metaelem, "size_x", text="X") col.prop(metaelem, "size_y", text="Y") @@ -117,13 +117,5 @@ class DATA_PT_custom_props_metaball(DataButtonsPanel, PropertyPanel, bpy.types.P _context_path = "object.data" _property_type = bpy.types.MetaBall - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_data_modifier.py b/release/scripts/startup/bl_ui/properties_data_modifier.py index f17cd8a4b98..95db7d216fb 100644 --- a/release/scripts/ui/properties_data_modifier.py +++ b/release/scripts/startup/bl_ui/properties_data_modifier.py @@ -59,16 +59,15 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "use_vertex_groups", text="Vertex Groups") col.prop(md, "use_bone_envelopes", text="Bone Envelopes") - split = layout.split() + layout.separator() - col = split.split() - col.prop_search(md, "vertex_group", ob, "vertex_groups", text="") - sub = col.column() + row = layout.row() + row.prop_search(md, "vertex_group", ob, "vertex_groups", text="") + sub = row.row() sub.active = bool(md.vertex_group) sub.prop(md, "invert_vertex_group") - col = layout.column() - col.prop(md, "use_multi_modifier") + layout.prop(md, "use_multi_modifier") def ARRAY(self, layout, ob, md): layout.prop(md, "fit_type") @@ -113,18 +112,14 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): layout.separator() - col = layout.column() - col.prop(md, "start_cap") - col.prop(md, "end_cap") + layout.prop(md, "start_cap") + layout.prop(md, "end_cap") def BEVEL(self, layout, ob, md): split = layout.split() - col = split.column() - col.prop(md, "width") - - col = split.column() - col.prop(md, "use_only_vertices") + split.prop(md, "width") + split.prop(md, "use_only_vertices") layout.label(text="Limit Method:") layout.row().prop(md, "limit_method", expand=True) @@ -188,10 +183,10 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "use_transform") def CLOTH(self, layout, ob, md): - layout.label(text="See Cloth panel.") + layout.label(text="Settings can be found inside the Physics context") def COLLISION(self, layout, ob, md): - layout.label(text="See Collision panel.") + layout.label(text="Settings can be found inside the Physics context") def CURVE(self, layout, ob, md): split = layout.split() @@ -214,7 +209,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col = split.column() col.label(text="Texture:") - col.prop(md, "texture", text="") + col.template_ID(md, "texture", new="texture.new") col.label(text="Vertex Group:") col.prop_search(md, "vertex_group", ob, "vertex_groups", text="") @@ -230,13 +225,9 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): layout.separator() - split = layout.split() - - col = split.column() - col.prop(md, "mid_level") - - col = split.column() - col.prop(md, "strength") + row = layout.row() + row.prop(md, "mid_level") + row.prop(md, "strength") def EDGE_SPLIT(self, layout, ob, md): split = layout.split() @@ -247,8 +238,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): sub.active = md.use_edge_angle sub.prop(md, "split_angle") - col = split.column() - col.prop(md, "use_edge_sharp", text="Sharp Edges") + split.prop(md, "use_edge_sharp", text="Sharp Edges") def EXPLODE(self, layout, ob, md): split = layout.split() @@ -259,9 +249,11 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): sub = col.column() sub.active = bool(md.vertex_group) sub.prop(md, "protect") + col.label(text="Particle UV") + col.prop_search(md, "particle_uv", ob.data, "uv_textures", text="") col = split.column() - col.prop(md, "use_edge_split") + col.prop(md, "use_edge_cut") col.prop(md, "show_unborn") col.prop(md, "show_alive") col.prop(md, "show_dead") @@ -270,7 +262,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): layout.operator("object.explode_refresh", text="Refresh") def FLUID_SIMULATION(self, layout, ob, md): - layout.label(text="See Fluid panel.") + layout.label(text="Settings can be found inside the Physics context") def HOOK(self, layout, ob, md): split = layout.split() @@ -334,6 +326,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): def MESH_DEFORM(self, layout, ob, md): split = layout.split() + col = split.column() sub = col.column() sub.label(text="Object:") @@ -354,13 +347,9 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): else: layout.operator("object.meshdeform_bind", text="Bind") - split = layout.split() - - col = split.column() - col.prop(md, "precision") - - col = split.column() - col.prop(md, "use_dynamic_bind") + row = layout.row() + row.prop(md, "precision") + row.prop(md, "use_dynamic_bind") def MIRROR(self, layout, ob, md): split = layout.split(percentage=0.25) @@ -383,7 +372,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "use_mirror_v", text="V") col = layout.column() - + if md.use_mirror_merge == True: col.prop(md, "merge_threshold") col.label(text="Mirror Object:") @@ -393,9 +382,9 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): split = layout.split() if ob.mode == 'EDIT': col = split.column() - col.operator("object.assign_navpolygon", text="Assign poly idx") - col = split.column() - col.operator("object.assign_new_navpolygon", text="Assign new poly idx") + col.operator("object.assign_navpolygon", text="Assign poly idx") + col = split.column() + col.operator("object.assign_new_navpolygon", text="Assign new poly idx") def MULTIRES(self, layout, ob, md): layout.row().prop(md, "subdivision_type", expand=True) @@ -460,7 +449,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "random_position", text="Random", slider=True) def PARTICLE_SYSTEM(self, layout, ob, md): - layout.label(text="See Particle panel.") + layout.label(text="Settings can be found inside the Particle context") def SCREW(self, layout, ob, md): split = layout.split() @@ -520,8 +509,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col = split.column() col.label(text="Cull Faces:") - col.prop(md, "use_cull_front_faces", text="Front") - col.prop(md, "use_cull_back_faces", text="Back") + col.prop(md, "cull_face", expand=True) layout.label(text="Auxiliary Target:") layout.prop(md, "auxiliary_target", text="") @@ -553,12 +541,12 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.label(text="Deform:") col.prop(md, "factor") col.prop(md, "limits", slider=True) - if md.deform_method in ('TAPER', 'STRETCH'): + if md.deform_method in {'TAPER', 'STRETCH'}: col.prop(md, "lock_x") col.prop(md, "lock_y") def SMOKE(self, layout, ob, md): - layout.label(text="See Smoke panel.") + layout.label(text="Settings can be found inside the Physics context") def SMOOTH(self, layout, ob, md): split = layout.split(percentage=0.25) @@ -576,10 +564,9 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop_search(md, "vertex_group", ob, "vertex_groups", text="") def SOFT_BODY(self, layout, ob, md): - layout.label(text="See Soft Body panel.") + layout.label(text="Settings can be found inside the Physics context") def SOLIDIFY(self, layout, ob, md): - split = layout.split() col = split.column() @@ -590,21 +577,26 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "edge_crease_inner", text="Inner") col.prop(md, "edge_crease_outer", text="Outer") col.prop(md, "edge_crease_rim", text="Rim") + col.label(text="Material Index Offset:") col = split.column() col.prop(md, "offset") - colsub = col.column() - colsub.active = bool(md.vertex_group) - colsub.prop(md, "invert_vertex_group", text="Invert") + sub = col.column() + sub.active = bool(md.vertex_group) + sub.prop(md, "invert_vertex_group", text="Invert") col.prop(md, "use_even_offset") col.prop(md, "use_quality_normals") - col.prop(md, "use_rim") - colsub = col.column() - colsub.active = md.use_rim - colsub.prop(md, "use_rim_material") + + sub = col.column() + sub.label() + row = sub.split(align=True, percentage=0.4) + row.prop(md, "material_offset", text="") + row = row.row() + row.active = md.use_rim + row.prop(md, "material_offset_rim", text="Rim") def SUBSURF(self, layout, ob, md): layout.row().prop(md, "subdivision_type", expand=True) @@ -621,7 +613,7 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "show_only_control_edges") def SURFACE(self, layout, ob, md): - layout.label(text="See Fields panel.") + layout.label(text="Settings can be found inside the Physics context") def UV_PROJECT(self, layout, ob, md): if ob.type == 'MESH': @@ -651,6 +643,48 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): sub.prop(md, "scale_x", text="Scale X") sub.prop(md, "scale_y", text="Scale Y") + def WARP(self, layout, ob, md): + use_falloff = (md.falloff_type != 'NONE') + split = layout.split() + + col = split.column() + col.label(text="From:") + col.prop(md, "object_from", text="") + + col.prop(md, "use_volume_preserve") + + col = split.column() + col.label(text="To:") + col.prop(md, "object_to", text="") + col.prop_search(md, "vertex_group", ob, "vertex_groups", text="") + + col = layout.column() + + row = col.row(align=True) + row.prop(md, "strength") + if use_falloff: + row.prop(md, "falloff_radius") + + col.prop(md, "falloff_type") + if use_falloff: + if md.falloff_type == 'CURVE': + col.template_curve_mapping(md, "falloff_curve") + + # 2 new columns + split = layout.split() + col = split.column() + col.label(text="Texture:") + col.prop(md, "texture", text="") + + col = split.column() + col.label(text="Texture Coordinates:") + col.prop(md, "texture_coords", text="") + + if md.texture_coords == 'OBJECT': + layout.prop(md, "texture_coordinate_object", text="Object") + elif md.texture_coords == 'UV' and ob.type == 'MESH': + layout.prop_search(md, "uv_layer", ob.data, "uv_textures") + def WAVE(self, layout, ob, md): split = layout.split() @@ -688,7 +722,11 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): layout.prop(md, "start_position_object") layout.prop_search(md, "vertex_group", ob, "vertex_groups") - layout.prop(md, "texture") + split = layout.split(percentage=0.33) + col = split.column() + col.label(text="Texture") + col = split.column() + col.template_ID(md, "texture", new="texture.new") layout.prop(md, "texture_coords") if md.texture_coords == 'MAP_UV' and ob.type == 'MESH': layout.prop_search(md, "uv_layer", ob.data, "uv_textures") @@ -707,13 +745,5 @@ class DATA_PT_modifiers(ModifierButtonsPanel, bpy.types.Panel): col.prop(md, "width", slider=True) col.prop(md, "narrowness", slider=True) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_game.py b/release/scripts/startup/bl_ui/properties_game.py index 60934891b7f..6952f3c31db 100644 --- a/release/scripts/ui/properties_game.py +++ b/release/scripts/startup/bl_ui/properties_game.py @@ -47,7 +47,7 @@ class PHYSICS_PT_game_physics(PhysicsButtonsPanel, bpy.types.Panel): layout.separator() #if game.physics_type == 'DYNAMIC': - if game.physics_type in ('DYNAMIC', 'RIGID_BODY'): + if game.physics_type in {'DYNAMIC', 'RIGID_BODY'}: split = layout.split() col = split.column() @@ -56,7 +56,7 @@ class PHYSICS_PT_game_physics(PhysicsButtonsPanel, bpy.types.Panel): col.prop(ob, "hide_render", text="Invisible") # out of place but useful col = split.column() - col.prop(game, "use_material_physics") + col.prop(game, "use_material_physics_fh") col.prop(game, "use_rotate_from_normal") col.prop(game, "use_sleep") @@ -163,7 +163,7 @@ class PHYSICS_PT_game_physics(PhysicsButtonsPanel, bpy.types.Panel): subsub.active = game.use_anisotropic_friction subsub.prop(game, "friction_coefficients", text="", slider=True) - elif game.physics_type in ('SENSOR', 'INVISIBLE', 'NO_COLLISION', 'OCCLUDE'): + elif game.physics_type in {'SENSOR', 'INVISIBLE', 'NO_COLLISION', 'OCCLUDE'}: layout.prop(ob, "hide_render", text="Invisible") @@ -175,7 +175,7 @@ class PHYSICS_PT_game_collision_bounds(PhysicsButtonsPanel, bpy.types.Panel): def poll(cls, context): game = context.object.game rd = context.scene.render - return (game.physics_type in ('DYNAMIC', 'RIGID_BODY', 'SENSOR', 'SOFT_BODY', 'STATIC')) and (rd.engine in cls.COMPAT_ENGINES) + return (game.physics_type in {'DYNAMIC', 'RIGID_BODY', 'SENSOR', 'SOFT_BODY', 'STATIC'}) and (rd.engine in cls.COMPAT_ENGINES) def draw_header(self, context): game = context.active_object.game @@ -190,18 +190,14 @@ class PHYSICS_PT_game_collision_bounds(PhysicsButtonsPanel, bpy.types.Panel): layout.active = game.use_collision_bounds layout.prop(game, "collision_bounds_type", text="Bounds") - split = layout.split() - - col = split.column() - col.prop(game, "collision_margin", text="Margin", slider=True) - - col = split.column() - col.prop(game, "use_collision_compound", text="Compound") + row = layout.row() + row.prop(game, "collision_margin", text="Margin", slider=True) + row.prop(game, "use_collision_compound", text="Compound") class PHYSICS_PT_game_obstacles(PhysicsButtonsPanel, bpy.types.Panel): bl_label = "Create obstacle" COMPAT_ENGINES = {'BLENDER_GAME'} - + @classmethod def poll(self, context): game = context.object.game @@ -222,7 +218,7 @@ class PHYSICS_PT_game_obstacles(PhysicsButtonsPanel, bpy.types.Panel): split = layout.split() col = split.column() - col.prop(game, "obstacle_radius", text="Radius") + col.prop(game, "obstacle_radius", text="Radius") class RenderButtonsPanel(): bl_space_type = 'PROPERTIES' @@ -352,6 +348,7 @@ class RENDER_PT_game_shading(RenderButtonsPanel, bpy.types.Panel): col.prop(gs, "use_glsl_lights", text="Lights") col.prop(gs, "use_glsl_shaders", text="Shaders") col.prop(gs, "use_glsl_shadows", text="Shadows") + col.prop(gs, "use_glsl_color_management", text="Color Management") col = split.column() col.prop(gs, "use_glsl_ramps", text="Ramps") @@ -385,7 +382,7 @@ class RENDER_PT_game_display(RenderButtonsPanel, bpy.types.Panel): flow.prop(gs, "show_framerate_profile", text="Framerate and Profile") flow.prop(gs, "show_physics_visualization", text="Physics Visualization") flow.prop(gs, "use_deprecation_warnings") - flow.prop(gs, "show_mouse") + flow.prop(gs, "show_mouse", text="Mouse Cursor") class RENDER_PT_game_sound(RenderButtonsPanel, bpy.types.Panel): @@ -447,13 +444,9 @@ class WORLD_PT_game_world(WorldButtonsPanel, bpy.types.Panel): world = context.world - split = layout.split() - - col = split.column() - col.prop(world, "horizon_color") - - col = split.column() - col.prop(world, "ambient_color") + row = layout.row() + row.column().prop(world, "horizon_color") + row.column().prop(world, "ambient_color") class WORLD_PT_game_mist(WorldButtonsPanel, bpy.types.Panel): @@ -476,13 +469,10 @@ class WORLD_PT_game_mist(WorldButtonsPanel, bpy.types.Panel): world = context.world layout.active = world.mist_settings.use_mist - split = layout.split() - - col = split.column() - col.prop(world.mist_settings, "start") - col = split.column() - col.prop(world.mist_settings, "depth") + row = layout.row() + row.prop(world.mist_settings, "start") + row.prop(world.mist_settings, "depth") class WORLD_PT_game_physics(WorldButtonsPanel, bpy.types.Panel): @@ -547,12 +537,5 @@ class WORLD_PT_game_physics_obstacles(WorldButtonsPanel, bpy.types.Panel): layout.prop(gs, "level_height") layout.prop(gs, "show_obstacle_simulation") -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_material.py b/release/scripts/startup/bl_ui/properties_material.py index 82c3e840420..52d6b5f1376 100644 --- a/release/scripts/ui/properties_material.py +++ b/release/scripts/startup/bl_ui/properties_material.py @@ -34,6 +34,22 @@ def active_node_mat(mat): return None +def check_material(mat): + if mat is not None: + if mat.use_nodes: + if mat.active_node_material is not None: + return True + return False + return True + return False + + +def simple_material(mat): + if (mat is not None) and (not mat.use_nodes): + return True + return False + + class MATERIAL_MT_sss_presets(bpy.types.Menu): bl_label = "SSS Presets" preset_subdir = "sss" @@ -119,6 +135,13 @@ class MATERIAL_PT_context_material(MaterialButtonsPanel, bpy.types.Panel): if mat: layout.prop(mat, "type", expand=True) + if mat.use_nodes: + row = layout.row() + row.label(text="", icon='NODETREE') + if mat.active_node_material: + row.prop(mat.active_node_material, "name", text="") + else: + row.label(text="No material node selected") class MATERIAL_PT_preview(MaterialButtonsPanel, bpy.types.Panel): @@ -129,15 +152,64 @@ class MATERIAL_PT_preview(MaterialButtonsPanel, bpy.types.Panel): self.layout.template_preview(context.material) +class MATERIAL_PT_pipeline(MaterialButtonsPanel, bpy.types.Panel): + bl_label = "Render Pipeline Options" + bl_options = {'DEFAULT_CLOSED'} + COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} + + @classmethod + def poll(cls, context): + mat = context.material + engine = context.scene.render.engine + return mat and (not simple_material(mat)) and (mat.type in {'SURFACE', 'WIRE', 'VOLUME'}) and (engine in cls.COMPAT_ENGINES) + + def draw(self, context): + layout = self. layout + + mat = context.material + mat_type = mat.type in {'SURFACE', 'WIRE'} + + row = layout.row() + row.active = mat_type + row.prop(mat, "use_transparency") + sub = row.column() + sub.prop(mat, "offset_z") + sub.active = mat_type and mat.use_transparency and mat.transparency_method == 'Z_TRANSPARENCY' + + row = layout.row() + row.active = mat.use_transparency or not mat_type + row.prop(mat, "transparency_method", expand=True) + + layout.separator() + + split = layout.split() + col = split.column() + + col.prop(mat, "use_raytrace") + col.prop(mat, "use_full_oversampling") + sub = col.column() + sub.active = mat_type + sub.prop(mat, "use_sky") + sub.prop(mat, "invert_z") + + col = split.column() + col.active = mat_type + + col.prop(mat, "use_cast_shadows_only", text="Cast Only") + col.prop(mat, "shadow_cast_alpha", text="Casting Alpha") + col.prop(mat, "use_cast_buffer_shadows") + col.prop(mat, "use_cast_approximate") + + class MATERIAL_PT_diffuse(MaterialButtonsPanel, bpy.types.Panel): bl_label = "Diffuse" COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout @@ -164,36 +236,24 @@ class MATERIAL_PT_diffuse(MaterialButtonsPanel, bpy.types.Panel): elif mat.diffuse_shader == 'MINNAERT': col.prop(mat, "darkness") elif mat.diffuse_shader == 'TOON': - split = col.split() - - col = split.column() - col.prop(mat, "diffuse_toon_size", text="Size") - - col = split.column() - col.prop(mat, "diffuse_toon_smooth", text="Smooth") + row = col.row() + row.prop(mat, "diffuse_toon_size", text="Size") + row.prop(mat, "diffuse_toon_smooth", text="Smooth") elif mat.diffuse_shader == 'FRESNEL': - split = col.split() - - col = split.column() - col.prop(mat, "diffuse_fresnel", text="Fresnel") - - col = split.column() - col.prop(mat, "diffuse_fresnel_factor", text="Factor") + row = col.row() + row.prop(mat, "diffuse_fresnel", text="Fresnel") + row.prop(mat, "diffuse_fresnel_factor", text="Factor") if mat.use_diffuse_ramp: layout.separator() layout.template_color_ramp(mat, "diffuse_ramp", expand=True) layout.separator() - split = layout.split() - - col = split.column() - col.prop(mat, "diffuse_ramp_input", text="Input") - - col = split.column() - col.prop(mat, "diffuse_ramp_blend", text="Blend") row = layout.row() - row.prop(mat, "diffuse_ramp_factor", text="Factor") + row.prop(mat, "diffuse_ramp_input", text="Input") + row.prop(mat, "diffuse_ramp_blend", text="Blend") + + layout.prop(mat, "diffuse_ramp_factor", text="Factor") class MATERIAL_PT_specular(MaterialButtonsPanel, bpy.types.Panel): @@ -202,9 +262,9 @@ class MATERIAL_PT_specular(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout @@ -224,41 +284,29 @@ class MATERIAL_PT_specular(MaterialButtonsPanel, bpy.types.Panel): col.prop(mat, "use_specular_ramp", text="Ramp") col = layout.column() - if mat.specular_shader in ('COOKTORR', 'PHONG'): + if mat.specular_shader in {'COOKTORR', 'PHONG'}: col.prop(mat, "specular_hardness", text="Hardness") elif mat.specular_shader == 'BLINN': - split = layout.split() - - col = split.column() - col.prop(mat, "specular_hardness", text="Hardness") - - col = split.column() - col.prop(mat, "specular_ior", text="IOR") + row = col.row() + row.prop(mat, "specular_hardness", text="Hardness") + row.prop(mat, "specular_ior", text="IOR") elif mat.specular_shader == 'WARDISO': col.prop(mat, "specular_slope", text="Slope") elif mat.specular_shader == 'TOON': - split = layout.split() - - col = split.column() - col.prop(mat, "specular_toon_size", text="Size") - - col = split.column() - col.prop(mat, "specular_toon_smooth", text="Smooth") + row = col.row() + row.prop(mat, "specular_toon_size", text="Size") + row.prop(mat, "specular_toon_smooth", text="Smooth") if mat.use_specular_ramp: layout.separator() layout.template_color_ramp(mat, "specular_ramp", expand=True) layout.separator() - split = layout.split() - - col = split.column() - col.prop(mat, "specular_ramp_input", text="Input") - - col = split.column() - col.prop(mat, "specular_ramp_blend", text="Blend") row = layout.row() - row.prop(mat, "specular_ramp_factor", text="Factor") + row.prop(mat, "specular_ramp_input", text="Input") + row.prop(mat, "specular_ramp_blend", text="Blend") + + layout.prop(mat, "specular_ramp_factor", text="Factor") class MATERIAL_PT_shading(MaterialButtonsPanel, bpy.types.Panel): @@ -267,16 +315,16 @@ class MATERIAL_PT_shading(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout mat = active_node_mat(context.material) - if mat.type in ('SURFACE', 'WIRE'): + if mat.type in {'SURFACE', 'WIRE'}: split = layout.split() col = split.column() @@ -297,36 +345,40 @@ class MATERIAL_PT_shading(MaterialButtonsPanel, bpy.types.Panel): class MATERIAL_PT_transp(MaterialButtonsPanel, bpy.types.Panel): bl_label = "Transparency" - bl_options = {'DEFAULT_CLOSED'} + # bl_options = {'DEFAULT_CLOSED'} COMPAT_ENGINES = {'BLENDER_RENDER'} @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw_header(self, context): - mat = active_node_mat(context.material) + mat = context.material - self.layout.prop(mat, "use_transparency", text="") + if simple_material(mat): + self.layout.prop(mat, "use_transparency", text="") def draw(self, context): layout = self.layout + base_mat = context.material mat = active_node_mat(context.material) rayt = mat.raytrace_transparency - row = layout.row() - row.active = mat.use_transparency and (not mat.use_shadeless) - row.prop(mat, "transparency_method", expand=True) + if simple_material(base_mat): + row = layout.row() + row.active = mat.use_transparency + row.prop(mat, "transparency_method", expand=True) split = layout.split() + split.active = base_mat.use_transparency col = split.column() col.prop(mat, "alpha") row = col.row() - row.active = mat.use_transparency and (not mat.use_shadeless) + row.active = (base_mat.transparency_method != 'MASK') and (not mat.use_shadeless) row.prop(mat, "specular_alpha", text="Specular") col = split.column() @@ -336,10 +388,10 @@ class MATERIAL_PT_transp(MaterialButtonsPanel, bpy.types.Panel): sub.active = rayt.fresnel > 0 sub.prop(rayt, "fresnel_factor", text="Blend") - if mat.transparency_method == 'RAYTRACE': + if base_mat.transparency_method == 'RAYTRACE': layout.separator() split = layout.split() - split.active = mat.use_transparency + split.active = base_mat.use_transparency col = split.column() col.prop(rayt, "ior") @@ -364,9 +416,9 @@ class MATERIAL_PT_mirror(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw_header(self, context): raym = active_node_mat(context.material).raytrace_mirror @@ -422,9 +474,9 @@ class MATERIAL_PT_sss(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw_header(self, context): mat = active_node_mat(context.material) @@ -497,6 +549,7 @@ class MATERIAL_PT_halo(MaterialButtonsPanel, bpy.types.Panel): col = split.column() col.prop(mat, "alpha") col.prop(mat, "diffuse_color", text="") + col.prop(halo, "seed") col = split.column() col.prop(halo, "size") @@ -568,16 +621,20 @@ class MATERIAL_PT_physics(MaterialButtonsPanel, bpy.types.Panel): phys = context.material.physics # dont use node material split = layout.split() + row = split.row() + row.prop(phys, "friction") + row.prop(phys, "elasticity", slider=True) - col = split.column() - col.prop(phys, "distance") - col.prop(phys, "friction") - col.prop(phys, "use_normal_align") + row = layout.row() + row.label(text="Force Field:") - col = split.column() - col.prop(phys, "force", slider=True) - col.prop(phys, "elasticity", slider=True) - col.prop(phys, "damping", slider=True) + row = layout.row() + row.prop(phys, "fh_force") + row.prop(phys, "fh_damping", slider=True) + + row = layout.row() + row.prop(phys, "fh_distance") + row.prop(phys, "use_fh_normal") class MATERIAL_PT_strand(MaterialButtonsPanel, bpy.types.Panel): @@ -589,7 +646,7 @@ class MATERIAL_PT_strand(MaterialButtonsPanel, bpy.types.Panel): def poll(cls, context): mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE', 'HALO')) and (engine in cls.COMPAT_ENGINES) + return mat and (mat.type in {'SURFACE', 'WIRE', 'HALO'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout @@ -633,26 +690,29 @@ class MATERIAL_PT_options(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout - mat = active_node_mat(context.material) + base_mat = context.material + mat = active_node_mat(base_mat) split = layout.split() col = split.column() - col.prop(mat, "use_raytrace") - col.prop(mat, "use_full_oversampling") - col.prop(mat, "use_sky") + if simple_material(base_mat): + col.prop(mat, "use_raytrace") + col.prop(mat, "use_full_oversampling") + col.prop(mat, "use_sky") col.prop(mat, "use_mist") - col.prop(mat, "invert_z") - sub = col.row() - sub.prop(mat, "offset_z") - sub.active = mat.use_transparency and mat.transparency_method == 'Z_TRANSPARENCY' + if simple_material(base_mat): + col.prop(mat, "invert_z") + sub = col.row() + sub.prop(mat, "offset_z") + sub.active = mat.use_transparency and mat.transparency_method == 'Z_TRANSPARENCY' sub = col.column(align=True) sub.label(text="Light Group:") sub.prop(mat, "light_group", text="") @@ -678,26 +738,32 @@ class MATERIAL_PT_shadow(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (mat.type in ('SURFACE', 'WIRE')) and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (mat.type in {'SURFACE', 'WIRE'}) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout - mat = active_node_mat(context.material) + base_mat = context.material + mat = active_node_mat(base_mat) split = layout.split() col = split.column() col.prop(mat, "use_shadows", text="Receive") col.prop(mat, "use_transparent_shadows", text="Receive Transparent") + if simple_material(base_mat): + col.prop(mat, "use_cast_shadows_only", text="Cast Only") + col.prop(mat, "shadow_cast_alpha", text="Casting Alpha") col.prop(mat, "use_only_shadow", text="Shadows Only") - col.prop(mat, "use_cast_shadows_only", text="Cast Only") - col.prop(mat, "shadow_cast_alpha", text="Casting Alpha") + sub = col.column() + sub.active = mat.use_only_shadow + sub.prop(mat, "shadow_only_type", text="") col = split.column() - col.prop(mat, "use_cast_buffer_shadows") + if simple_material(base_mat): + col.prop(mat, "use_cast_buffer_shadows") sub = col.column() sub.active = mat.use_cast_buffer_shadows sub.prop(mat, "shadow_buffer_bias", text="Buffer Bias") @@ -705,7 +771,8 @@ class MATERIAL_PT_shadow(MaterialButtonsPanel, bpy.types.Panel): sub = col.column() sub.active = (not mat.use_ray_shadow_bias) sub.prop(mat, "shadow_ray_bias", text="Ray Bias") - col.prop(mat, "use_cast_approximate") + if simple_material(base_mat): + col.prop(mat, "use_cast_approximate") class MATERIAL_PT_transp_game(MaterialButtonsPanel, bpy.types.Panel): @@ -715,29 +782,27 @@ class MATERIAL_PT_transp_game(MaterialButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - mat = active_node_mat(context.material) + mat = context.material engine = context.scene.render.engine - return mat and (engine in cls.COMPAT_ENGINES) + return check_material(mat) and (engine in cls.COMPAT_ENGINES) def draw_header(self, context): - mat = active_node_mat(context.material) + mat = context.material - self.layout.prop(mat, "use_transparency", text="") + if simple_material(mat): + self.layout.prop(mat, "use_transparency", text="") def draw(self, context): layout = self.layout + base_mat = context.material + mat = active_node_mat(base_mat) - mat = active_node_mat(context.material) - rayt = mat.raytrace_transparency - - row = layout.row() - row.active = mat.use_transparency and (not mat.use_shadeless) - row.prop(mat, "transparency_method", expand=True) - - split = layout.split() + if simple_material(base_mat): + row = layout.row() + row.active = mat.use_transparency + row.prop(mat, "transparency_method", expand=True) - col = split.column() - col.prop(mat, "alpha") + layout.prop(mat, "alpha") class VolumeButtonsPanel(): @@ -762,12 +827,9 @@ class MATERIAL_PT_volume_density(VolumeButtonsPanel, bpy.types.Panel): vol = context.material.volume # dont use node material - split = layout.split() - col = split.column() - col.prop(vol, "density") - - col = split.column() - col.prop(vol, "density_scale") + row = layout.row() + row.prop(vol, "density") + row.prop(vol, "density_scale") class MATERIAL_PT_volume_shading(VolumeButtonsPanel, bpy.types.Panel): @@ -817,7 +879,7 @@ class MATERIAL_PT_volume_lighting(VolumeButtonsPanel, bpy.types.Panel): sub = col.column() sub.active = vol.use_light_cache sub.prop(vol, "cache_resolution") - elif vol.light_method in ('MULTIPLE_SCATTERING', 'SHADED_PLUS_MULTIPLE_SCATTERING'): + elif vol.light_method in {'MULTIPLE_SCATTERING', 'SHADED_PLUS_MULTIPLE_SCATTERING'}: sub = col.column() sub.enabled = True sub.active = False @@ -834,6 +896,12 @@ class MATERIAL_PT_volume_transp(VolumeButtonsPanel, bpy.types.Panel): bl_label = "Transparency" COMPAT_ENGINES = {'BLENDER_RENDER'} + @classmethod + def poll(cls, context): + mat = context.material + engine = context.scene.render.engine + return mat and simple_material(mat) and (mat.type == 'VOLUME') and (engine in cls.COMPAT_ENGINES) + def draw(self, context): layout = self.layout @@ -869,6 +937,12 @@ class MATERIAL_PT_volume_options(VolumeButtonsPanel, bpy.types.Panel): COMPAT_ENGINES = {'BLENDER_RENDER', 'BLENDER_GAME'} bl_options = {'DEFAULT_CLOSED'} + @classmethod + def poll(cls, context): + mat = context.material + engine = context.scene.render.engine + return check_material(mat) and (mat.type == 'VOLUME') and (engine in cls.COMPAT_ENGINES) + def draw(self, context): layout = self.layout @@ -877,8 +951,9 @@ class MATERIAL_PT_volume_options(VolumeButtonsPanel, bpy.types.Panel): split = layout.split() col = split.column() - col.prop(mat, "use_raytrace") - col.prop(mat, "use_full_oversampling") + if simple_material(context.material): + col.prop(mat, "use_raytrace") + col.prop(mat, "use_full_oversampling") col.prop(mat, "use_mist") col = split.column() @@ -894,13 +969,5 @@ class MATERIAL_PT_custom_props(MaterialButtonsPanel, PropertyPanel, bpy.types.Pa _context_path = "material" _property_type = bpy.types.Material - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_object.py b/release/scripts/startup/bl_ui/properties_object.py index 30380e2d338..ae66642e903 100644 --- a/release/scripts/ui/properties_object.py +++ b/release/scripts/startup/bl_ui/properties_object.py @@ -40,8 +40,7 @@ class OBJECT_PT_context_object(ObjectButtonsPanel, bpy.types.Panel): layout.template_ID(space, "pin_id") else: row = layout.row() - row.label(text="", icon='OBJECT_DATA') - row.prop(ob, "name", text="") + row.template_ID(context.scene.objects, "active") class OBJECT_PT_transform(ObjectButtonsPanel, bpy.types.Panel): @@ -111,7 +110,7 @@ class OBJECT_PT_transform_locks(ObjectButtonsPanel, bpy.types.Panel): col.prop(ob, "lock_location", text="Location") col = row.column() - if ob.rotation_mode in ('QUATERNION', 'AXIS_ANGLE'): + if ob.rotation_mode in {'QUATERNION', 'AXIS_ANGLE'}: col.prop(ob, "lock_rotations_4d", text="Rotation") if ob.lock_rotations_4d: col.prop(ob, "lock_rotation_w", text="W") @@ -249,13 +248,10 @@ class OBJECT_PT_duplication(ObjectButtonsPanel, bpy.types.Panel): layout.prop(ob, "use_dupli_vertices_rotation", text="Rotation") elif ob.dupli_type == 'FACES': - split = layout.split() - - col = split.column() - col.prop(ob, "use_dupli_faces_scale", text="Scale") - col = split.column() - col.prop(ob, "dupli_faces_scale", text="Inherit Scale") + row = layout.row() + row.prop(ob, "use_dupli_faces_scale", text="Scale") + row.prop(ob, "dupli_faces_scale", text="Inherit Scale") elif ob.dupli_type == 'GROUP': layout.prop(ob, "dupli_group", text="Group") @@ -291,7 +287,11 @@ class OBJECT_PT_animation(ObjectButtonsPanel, bpy.types.Panel): col.prop(ob, "track_axis", text="Axis") col.prop(ob, "up_axis", text="Up Axis") -from properties_animviz import MotionPathButtonsPanel, OnionSkinButtonsPanel + +from bl_ui.properties_animviz import ( + MotionPathButtonsPanel, + OnionSkinButtonsPanel, + ) class OBJECT_PT_motion_paths(MotionPathButtonsPanel, bpy.types.Panel): @@ -311,13 +311,9 @@ class OBJECT_PT_motion_paths(MotionPathButtonsPanel, bpy.types.Panel): layout.separator() - split = layout.split() - - col = split.column() - col.operator("object.paths_calculate", text="Calculate Paths") - - col = split.column() - col.operator("object.paths_clear", text="Clear Paths") + row = layout.row() + row.operator("object.paths_calculate", text="Calculate Paths") + row.operator("object.paths_clear", text="Clear Paths") class OBJECT_PT_onion_skinning(OnionSkinButtonsPanel): # , bpy.types.Panel): # inherit from panel when ready @@ -329,8 +325,6 @@ class OBJECT_PT_onion_skinning(OnionSkinButtonsPanel): # , bpy.types.Panel): # return (context.object) def draw(self, context): - layout = self.layout - ob = context.object self.draw_settings(context, ob.animation_visualisation) @@ -341,13 +335,5 @@ class OBJECT_PT_custom_props(ObjectButtonsPanel, PropertyPanel, bpy.types.Panel) _context_path = "object" _property_type = bpy.types.Object - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_object_constraint.py b/release/scripts/startup/bl_ui/properties_object_constraint.py index 4b9c2885277..59f9ca16d1a 100644 --- a/release/scripts/ui/properties_object_constraint.py +++ b/release/scripts/startup/bl_ui/properties_object_constraint.py @@ -34,7 +34,7 @@ class ConstraintButtonsPanel(): # match enum type to our functions, avoids a lookup table. getattr(self, con.type)(context, box, con) - if con.type not in ('RIGID_BODY_JOINT', 'NULL'): + if con.type not in {'RIGID_BODY_JOINT', 'NULL'}: box.prop(con, "influence") def space_template(self, layout, con, target=True, owner=True): @@ -65,7 +65,7 @@ class ConstraintButtonsPanel(): row = layout.row() row.label(text="Head/Tail:") row.prop(con, "head_tail", text="") - elif con.target.type in ('MESH', 'LATTICE'): + elif con.target.type in {'MESH', 'LATTICE'}: layout.prop_search(con, "subtarget", con.target, "vertex_groups", text="Vertex Group") def ik_template(self, layout, con): @@ -112,13 +112,9 @@ class ConstraintButtonsPanel(): col.prop(con, "use_scale_y", text="Y") col.prop(con, "use_scale_z", text="Z") - split = layout.split() - - col = split.column() - col.operator("constraint.childof_set_inverse") - - col = split.column() - col.operator("constraint.childof_clear_inverse") + row = layout.row() + row.operator("constraint.childof_set_inverse") + row.operator("constraint.childof_clear_inverse") def TRACK_TO(self, context, layout, con): self.target_template(layout, con) @@ -127,13 +123,9 @@ class ConstraintButtonsPanel(): row.label(text="To:") row.prop(con, "track_axis", expand=True) - split = layout.split() - - col = split.column() - col.prop(con, "up_axis", text="Up") - - col = split.column() - col.prop(con, "use_target_z") + row = layout.row() + row.prop(con, "up_axis", text="Up") + row.prop(con, "use_target_z") self.space_template(layout, con) @@ -212,6 +204,7 @@ class ConstraintButtonsPanel(): self.ik_template(layout, con) layout.prop(con, "limit_mode") + row = layout.row() row.prop(con, "weight", text="Weight", slider=True) row.prop(con, "distance", text="Distance", slider=True) @@ -486,16 +479,11 @@ class ConstraintButtonsPanel(): def STRETCH_TO(self, context, layout, con): self.target_template(layout, con) - split = layout.split() - - col = split.column() - col.prop(con, "rest_length", text="Rest Length") - - col = split.column() - col.operator("constraint.stretchto_reset", text="Reset") + row = layout.row() + row.prop(con, "rest_length", text="Rest Length") + row.operator("constraint.stretchto_reset", text="Reset") - col = layout.column() - col.prop(con, "bulge", text="Volume Variation") + layout.prop(con, "bulge", text="Volume Variation") row = layout.row() row.label(text="Volume:") @@ -507,13 +495,9 @@ class ConstraintButtonsPanel(): def FLOOR(self, context, layout, con): self.target_template(layout, con) - split = layout.split() - - col = split.column() - col.prop(con, "use_sticky") - - col = split.column() - col.prop(con, "use_rotation") + row = layout.row() + row.prop(con, "use_sticky") + row.prop(con, "use_rotation") layout.prop(con, "offset") @@ -529,13 +513,9 @@ class ConstraintButtonsPanel(): layout.prop(con, "pivot_type") layout.prop(con, "child") - split = layout.split() - - col = split.column() - col.prop(con, "use_linked_collision", text="Linked Collision") - - col = split.column() - col.prop(con, "show_pivot", text="Display Pivot") + row = layout.row() + row.prop(con, "use_linked_collision", text="Linked Collision") + row.prop(con, "show_pivot", text="Display Pivot") split = layout.split() @@ -555,15 +535,23 @@ class ConstraintButtonsPanel(): layout.label(text="Limits:") split = layout.split() - col = split.column(align=True) - col.prop(con, "use_angular_limit_x", text="Angular X") - col.prop(con, "use_angular_limit_y", text="Angular Y") - col.prop(con, "use_angular_limit_z", text="Angular Z") + col = split.column() + col.prop(con, "use_angular_limit_x", text="Angle X") + sub = col.column() + sub.active = con.use_angular_limit_x + sub.prop(con, "limit_angle_max_x", text="") col = split.column() - col.prop(con, "limit_cone_min", text="") + col.prop(con, "use_angular_limit_y", text="Angle Y") + sub = col.column() + sub.active = con.use_angular_limit_y + sub.prop(con, "limit_angle_max_y", text="") + col = split.column() - col.prop(con, "limit_cone_max", text="") + col.prop(con, "use_angular_limit_z", text="Angle Z") + sub = col.column() + sub.active = con.use_angular_limit_z + sub.prop(con, "limit_angle_max_z", text="") elif con.pivot_type == 'GENERIC_6_DOF': layout.label(text="Limits:") @@ -571,16 +559,62 @@ class ConstraintButtonsPanel(): col = split.column(align=True) col.prop(con, "use_limit_x", text="X") + sub = col.column() + sub.active = con.use_limit_x + sub.prop(con, "limit_min_x", text="Min") + sub.prop(con, "limit_max_x", text="Max") + + col = split.column(align=True) col.prop(con, "use_limit_y", text="Y") + sub = col.column() + sub.active = con.use_limit_y + sub.prop(con, "limit_min_y", text="Min") + sub.prop(con, "limit_max_y", text="Max") + + col = split.column(align=True) col.prop(con, "use_limit_z", text="Z") - col.prop(con, "use_angular_limit_x", text="Angular X") - col.prop(con, "use_angular_limit_y", text="Angular Y") - col.prop(con, "use_angular_limit_z", text="Angular Z") + sub = col.column() + sub.active = con.use_limit_z + sub.prop(con, "limit_min_z", text="Min") + sub.prop(con, "limit_max_z", text="Max") - col = split.column() - col.prop(con, "limit_generic_min", text="") - col = split.column() - col.prop(con, "limit_generic_max", text="") + split = layout.split() + + col = split.column(align=True) + col.prop(con, "use_angular_limit_x", text="Angle X") + sub = col.column() + sub.active = con.use_angular_limit_x + sub.prop(con, "limit_angle_min_x", text="Min") + sub.prop(con, "limit_angle_max_x", text="Max") + + col = split.column(align=True) + col.prop(con, "use_angular_limit_y", text="Angle Y") + sub = col.column() + sub.active = con.use_angular_limit_y + sub.prop(con, "limit_angle_min_y", text="Min") + sub.prop(con, "limit_angle_max_y", text="Max") + + col = split.column(align=True) + col.prop(con, "use_angular_limit_z", text="Angle Z") + sub = col.column() + sub.active = con.use_angular_limit_z + sub.prop(con, "limit_angle_min_z", text="Min") + sub.prop(con, "limit_angle_max_z", text="Max") + + elif con.pivot_type == 'HINGE': + layout.label(text="Limits:") + split = layout.split() + + row = split.row(align=True) + col = row.column() + col.prop(con, "use_angular_limit_x", text="Angle X") + + col = row.column() + col.active = con.use_angular_limit_x + col.prop(con, "limit_angle_min_x", text="Min") + col = row.column() + col.active = con.use_angular_limit_x + col.prop(con, "limit_angle_max_x", text="Max") def CLAMP_TO(self, context, layout, con): self.target_template(layout, con) @@ -589,8 +623,7 @@ class ConstraintButtonsPanel(): row.label(text="Main Axis:") row.prop(con, "main_axis", expand=True) - row = layout.row() - row.prop(con, "use_cyclic") + layout.prop(con, "use_cyclic") def TRANSFORM(self, context, layout, con): self.target_template(layout, con) @@ -747,13 +780,5 @@ class BONE_PT_constraints(ConstraintButtonsPanel, bpy.types.Panel): for con in context.pose_bone.constraints: self.draw_constraint(context, con) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_particle.py b/release/scripts/startup/bl_ui/properties_particle.py index 4bed2e81629..5037308c6fd 100644 --- a/release/scripts/ui/properties_particle.py +++ b/release/scripts/startup/bl_ui/properties_particle.py @@ -20,15 +20,19 @@ import bpy from rna_prop_ui import PropertyPanel -from properties_physics_common import point_cache_ui -from properties_physics_common import effector_weights_ui -from properties_physics_common import basic_force_field_settings_ui -from properties_physics_common import basic_force_field_falloff_ui +from bl_ui.properties_physics_common import ( + point_cache_ui, + effector_weights_ui, + basic_force_field_settings_ui, + basic_force_field_falloff_ui, + ) def particle_panel_enabled(context, psys): + if psys is None: + return True phystype = psys.settings.physics_type - if psys.settings.type in ('EMITTER', 'REACTOR') and phystype in ('NO', 'KEYED'): + if psys.settings.type in {'EMITTER', 'REACTOR'} and phystype in {'NO', 'KEYED'}: return True else: return (psys.point_cache.is_baked is False) and (not psys.is_edited) and (not context.particle_system_editable) @@ -37,11 +41,25 @@ def particle_panel_enabled(context, psys): def particle_panel_poll(cls, context): psys = context.particle_system engine = context.scene.render.engine - if psys is None: - return False - if psys.settings is None: + settings = 0 + + if psys: + settings = psys.settings + elif isinstance(context.space_data.pin_id, bpy.types.ParticleSettings): + settings = context.space_data.pin_id + + if not settings: return False - return psys.settings.is_fluid == False and (engine in cls.COMPAT_ENGINES) + + return settings.is_fluid == False and (engine in cls.COMPAT_ENGINES) + + +def particle_get_settings(context): + if context.particle_system: + return context.particle_system.settings + elif isinstance(context.space_data.pin_id, bpy.types.ParticleSettings): + return context.space_data.pin_id + return None class ParticleButtonsPanel(): @@ -62,13 +80,14 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): engine = context.scene.render.engine - return (context.particle_system or context.object) and (engine in cls.COMPAT_ENGINES) + return (context.particle_system or context.object or context.space_data.pin_id) and (engine in cls.COMPAT_ENGINES) def draw(self, context): layout = self.layout ob = context.object psys = context.particle_system + part = 0 if ob: row = layout.row() @@ -79,7 +98,21 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, bpy.types.Panel): col.operator("object.particle_system_add", icon='ZOOMIN', text="") col.operator("object.particle_system_remove", icon='ZOOMOUT', text="") - if psys and not psys.settings: + if psys is None: + part = particle_get_settings(context) + + if part is None: + return + + layout.template_ID(context.space_data, "pin_id") + + if part.is_fluid: + layout.label(text="Settings used for fluid.") + return + + layout.prop(part, "type", text="Type") + + elif not psys.settings: split = layout.split(percentage=0.32) col = split.column() @@ -89,7 +122,7 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, bpy.types.Panel): col = split.column() col.prop(psys, "name", text="") col.template_ID(psys, "settings", new="particle.new") - elif psys: + else: part = psys.settings split = layout.split(percentage=0.32) @@ -110,39 +143,39 @@ class PARTICLE_PT_context_particles(ParticleButtonsPanel, bpy.types.Panel): #row.label(text="Viewport") #row.label(text="Render") - if part: - if part.is_fluid: - layout.label(text=str(part.count) + " fluid particles for this frame.") - return + if part.is_fluid: + layout.label(text=str(part.count) + " fluid particles for this frame.") + return - row = col.row() - row.enabled = particle_panel_enabled(context, psys) - row.prop(part, "type", text="") - row.prop(psys, "seed") + row = col.row() + row.enabled = particle_panel_enabled(context, psys) + row.prop(part, "type", text="") + row.prop(psys, "seed") - split = layout.split(percentage=0.65) - if part.type == 'HAIR': - if psys.is_edited: - split.operator("particle.edited_clear", text="Free Edit") - else: - row = split.row() - row.enabled = particle_panel_enabled(context, psys) - row.prop(part, "regrow_hair") - row.prop(part, "use_advanced_hair") + if part: + split = layout.split(percentage=0.65) + if part.type == 'HAIR': + if psys != None and psys.is_edited: + split.operator("particle.edited_clear", text="Free Edit") + else: row = split.row() row.enabled = particle_panel_enabled(context, psys) - row.prop(part, "hair_step") - if psys.is_edited: - if psys.is_global_hair: - layout.operator("particle.connect_hair") - layout.label(text="Hair is disconnected.") - else: - layout.operator("particle.disconnect_hair") - layout.label(text="") - elif part.type == 'REACTOR': - split.enabled = particle_panel_enabled(context, psys) - split.prop(psys, "reactor_target_object") - split.prop(psys, "reactor_target_particle_system", text="Particle System") + row.prop(part, "regrow_hair") + row.prop(part, "use_advanced_hair") + row = split.row() + row.enabled = particle_panel_enabled(context, psys) + row.prop(part, "hair_step") + if psys != None and psys.is_edited: + if psys.is_global_hair: + layout.operator("particle.connect_hair") + layout.label(text="Hair is disconnected.") + else: + layout.operator("particle.disconnect_hair") + layout.label(text="") + elif psys != None and part.type == 'REACTOR': + split.enabled = particle_panel_enabled(context, psys) + split.prop(psys, "reactor_target_object") + split.prop(psys, "reactor_target_particle_system", text="Particle System") class PARTICLE_PT_emission(ParticleButtonsPanel, bpy.types.Panel): @@ -152,23 +185,23 @@ class PARTICLE_PT_emission(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): psys = context.particle_system - if psys is None: - return False - if psys.settings is None: + settings = particle_get_settings(context) + + if settings is None: return False - if psys.settings.is_fluid: + if settings.is_fluid: return False if particle_panel_poll(PARTICLE_PT_emission, context): - return not context.particle_system.point_cache.use_external + return psys is None or not context.particle_system.point_cache.use_external return False def draw(self, context): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) - layout.enabled = particle_panel_enabled(context, psys) and not psys.has_multiple_caches + layout.enabled = particle_panel_enabled(context, psys) and (psys is None or not psys.has_multiple_caches) row = layout.row() row.active = part.distribution != 'GRID' @@ -189,25 +222,23 @@ class PARTICLE_PT_emission(ParticleButtonsPanel, bpy.types.Panel): col.prop(part, "lifetime") col.prop(part, "lifetime_random", slider=True) - layout.row().label(text="Emit From:") - - row = layout.row() - row.prop(part, "emit_from", expand=True) + layout.label(text="Emit From:") + layout.prop(part, "emit_from", expand=True) row = layout.row() - if part.distribution == 'GRID': + if part.emit_from == 'VERT': + row.prop(part, "use_emit_random") + elif part.distribution == 'GRID': row.prop(part, "invert_grid") + row.prop(part, "hexagonal_grid") else: row.prop(part, "use_emit_random") row.prop(part, "use_even_distribution") if part.emit_from == 'FACE' or part.emit_from == 'VOLUME': - row = layout.row() - - row.prop(part, "distribution", expand=True) + layout.prop(part, "distribution", expand=True) row = layout.row() - if part.distribution == 'JIT': row.prop(part, "userjit", text="Particles/Face") row.prop(part, "jitter_factor", text="Jittering Amount", slider=True) @@ -247,7 +278,6 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, bpy.types.Panel): if not psys.cloth: return - #part = psys.settings cloth = psys.cloth.settings layout.enabled = psys.use_hair_dynamics and psys.point_cache.is_baked == False @@ -264,7 +294,6 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel, bpy.types.Panel): sub.prop(cloth, "collider_friction", slider=True) col = split.column() - col.label(text="Damping:") sub = col.column(align=True) sub.prop(cloth, "spring_damping", text="Spring") @@ -292,7 +321,7 @@ class PARTICLE_PT_cache(ParticleButtonsPanel, bpy.types.Panel): phystype = psys.settings.physics_type if phystype == 'NO' or phystype == 'KEYED': return False - return (psys.settings.type in ('EMITTER', 'REACTOR') or (psys.settings.type == 'HAIR' and (psys.use_hair_dynamics or psys.point_cache.is_baked))) and engine in cls.COMPAT_ENGINES + return (psys.settings.type in {'EMITTER', 'REACTOR'} or (psys.settings.type == 'HAIR' and (psys.use_hair_dynamics or psys.point_cache.is_baked))) and engine in cls.COMPAT_ENGINES def draw(self, context): psys = context.particle_system @@ -308,9 +337,11 @@ class PARTICLE_PT_velocity(ParticleButtonsPanel, bpy.types.Panel): def poll(cls, context): if particle_panel_poll(PARTICLE_PT_velocity, context): psys = context.particle_system - if psys.settings.type == 'HAIR' and not psys.settings.use_advanced_hair: + settings = particle_get_settings(context) + + if settings.type == 'HAIR' and not settings.use_advanced_hair: return False - return psys.settings.physics_type != 'BOIDS' and not psys.point_cache.use_external + return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external) else: return False @@ -318,32 +349,30 @@ class PARTICLE_PT_velocity(ParticleButtonsPanel, bpy.types.Panel): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) layout.enabled = particle_panel_enabled(context, psys) split = layout.split() - sub = split.column() - sub.label(text="Emitter Geometry:") - sub.prop(part, "normal_factor") - subsub = sub.column(align=True) - subsub.prop(part, "tangent_factor") - subsub.prop(part, "tangent_phase", slider=True) + col = split.column() + col.label(text="Emitter Geometry:") + col.prop(part, "normal_factor") + sub = col.column(align=True) + sub.prop(part, "tangent_factor") + sub.prop(part, "tangent_phase", slider=True) - sub = split.column() - sub.label(text="Emitter Object") - sub.prop(part, "object_align_factor", text="") + col = split.column() + col.label(text="Emitter Object:") + col.prop(part, "object_align_factor", text="") - layout.row().label(text="Other:") - split = layout.split() - sub = split.column() + layout.label(text="Other:") + row = layout.row() if part.emit_from == 'PARTICLE': - sub.prop(part, "particle_factor") + row.prop(part, "particle_factor") else: - sub.prop(part, "object_factor", slider=True) - sub = split.column() - sub.prop(part, "factor_random") + row.prop(part, "object_factor", slider=True) + row.prop(part, "factor_random") #if part.type=='REACTOR': # sub.prop(part, "reactor_factor") @@ -358,9 +387,11 @@ class PARTICLE_PT_rotation(ParticleButtonsPanel, bpy.types.Panel): def poll(cls, context): if particle_panel_poll(PARTICLE_PT_rotation, context): psys = context.particle_system - if psys.settings.type == 'HAIR' and not psys.settings.use_advanced_hair: + settings = particle_get_settings(context) + + if settings.type == 'HAIR' and not settings.use_advanced_hair: return False - return psys.settings.physics_type != 'BOIDS' and not psys.point_cache.use_external + return settings.physics_type != 'BOIDS' and (psys is None or not psys.point_cache.use_external) else: return False @@ -368,31 +399,33 @@ class PARTICLE_PT_rotation(ParticleButtonsPanel, bpy.types.Panel): layout = self.layout psys = context.particle_system - part = psys.settings + if psys: + part = psys.settings + else: + part = context.space_data.pin_id layout.enabled = particle_panel_enabled(context, psys) - split = layout.split() - split.label(text="Initial Rotation:") - split.prop(part, "use_dynamic_rotation") - split = layout.split() + row = layout.row() + row.label(text="Initial Rotation:") + row.prop(part, "use_dynamic_rotation") - sub = split.column(align=True) - sub.prop(part, "rotation_mode", text="") - sub.prop(part, "rotation_factor_random", slider=True, text="Random") + split = layout.split() - sub = split.column(align=True) - sub.prop(part, "phase_factor", slider=True) - sub.prop(part, "phase_factor_random", text="Random", slider=True) + col = split.column(align=True) + col.prop(part, "rotation_mode", text="") + col.prop(part, "rotation_factor_random", slider=True, text="Random") - layout.row().label(text="Angular Velocity:") - layout.row().prop(part, "angular_velocity_mode", expand=True) - split = layout.split() + col = split.column(align=True) + col.prop(part, "phase_factor", slider=True) + col.prop(part, "phase_factor_random", text="Random", slider=True) - sub = split.column() + col = layout.column() + col.label(text="Angular Velocity:") + col.row().prop(part, "angular_velocity_mode", expand=True) if part.angular_velocity_mode != 'NONE': - sub.prop(part, "angular_velocity_factor", text="") + col.prop(part, "angular_velocity_factor", text="") class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): @@ -403,9 +436,11 @@ class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): def poll(cls, context): if particle_panel_poll(PARTICLE_PT_physics, context): psys = context.particle_system - if psys.settings.type == 'HAIR' and not psys.settings.use_advanced_hair: + settings = particle_get_settings(context) + + if settings.type == 'HAIR' and not settings.use_advanced_hair: return False - return not psys.point_cache.use_external + return psys is None or not psys.point_cache.use_external else: return False @@ -413,12 +448,11 @@ class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) layout.enabled = particle_panel_enabled(context, psys) - row = layout.row() - row.prop(part, "physics_type", expand=True) + layout.prop(part, "physics_type", expand=True) row = layout.row() col = row.column(align=True) @@ -430,70 +464,76 @@ class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): col.prop(part, "mass") col.prop(part, "use_multiply_size_mass", text="Multiply mass with size") - if part.physics_type == 'NEWTON': + if part.physics_type in ('NEWTON', 'FLUID'): split = layout.split() - sub = split.column() - sub.label(text="Forces:") - sub.prop(part, "brownian_factor") - sub.prop(part, "drag_factor", slider=True) - sub.prop(part, "damping", slider=True) - sub = split.column() - sub.label(text="Integration:") - sub.prop(part, "integrator", text="") - sub.prop(part, "time_tweak") - sub.prop(part, "subframes") - sub = layout.row() - sub.prop(part, "use_size_deflect") - sub.prop(part, "use_die_on_collision") - - elif part.physics_type == 'FLUID': - fluid = part.fluid - split = layout.split() - sub = split.column() + col = split.column() + col.label(text="Forces:") + col.prop(part, "brownian_factor") + col.prop(part, "drag_factor", slider=True) + col.prop(part, "damping", slider=True) - sub.label(text="Forces:") - sub.prop(part, "brownian_factor") - sub.prop(part, "drag_factor", slider=True) - sub.prop(part, "damping", slider=True) - sub = split.column() - sub.label(text="Integration:") - sub.prop(part, "integrator", text="") - sub.prop(part, "time_tweak") - sub.prop(part, "subframes") - sub = layout.row() - sub.prop(part, "use_size_deflect") - sub.prop(part, "use_die_on_collision") + col = split.column() + col.label(text="Integration:") + col.prop(part, "integrator", text="") + col.prop(part, "timestep") + col.prop(part, "subframes") - split = layout.split() - sub = split.column() - sub.label(text="Fluid Interaction:") - sub.prop(fluid, "fluid_radius") - sub.prop(fluid, "repulsion_force") - subsub = sub.column(align=True) - subsub.prop(fluid, "rest_density") - subsub.prop(fluid, "density_force", text="Force") + row = layout.row() + row.prop(part, "use_size_deflect") + row.prop(part, "use_die_on_collision") - sub.label(text="Viscosity:") - subsub = sub.column(align=True) - subsub.prop(fluid, "linear_viscosity", text="Linear") - subsub.prop(fluid, "square_viscosity", text="Square") + if part.physics_type == 'FLUID': + fluid = part.fluid - sub = split.column() + split = layout.split() - sub.label(text="Springs:") - sub.prop(fluid, "spring_force", text="Force") - #Hidden to make ui a bit lighter, can be unhidden for a bit more control - #sub.prop(fluid, "rest_length", slider=True) - sub.prop(fluid, "use_viscoelastic_springs") - subsub = sub.column(align=True) - subsub.active = fluid.use_viscoelastic_springs - subsub.prop(fluid, "yield_ratio", slider=True) - subsub.prop(fluid, "plasticity", slider=True) - subsub.prop(fluid, "use_initial_rest_length") + col = split.column() + col.label(text="Fluid properties:") + col.prop(fluid, "stiffness", text="Stiffness") + col.prop(fluid, "linear_viscosity", text="Viscosity") + col.prop(fluid, "buoyancy", text="Buoancy", slider=True) - sub.label(text="Buoyancy:") - sub.prop(fluid, "buoyancy", text="Strength", slider=True) + col = split.column() + col.label(text="Advanced:") + + sub = col.row() + sub.prop(fluid, "repulsion", slider=fluid.factor_repulsion) + sub.prop(fluid, "factor_repulsion", text="") + + sub = col.row() + sub.prop(fluid, "stiff_viscosity", slider=fluid.factor_stiff_viscosity) + sub.prop(fluid, "factor_stiff_viscosity", text="") + + sub = col.row() + sub.prop(fluid, "fluid_radius", slider=fluid.factor_radius) + sub.prop(fluid, "factor_radius", text="") + + sub = col.row() + sub.prop(fluid, "rest_density", slider=fluid.factor_density) + sub.prop(fluid, "factor_density", text="") + + split = layout.split() + + col = split.column() + col.label(text="Springs:") + col.prop(fluid, "spring_force", text="Force") + col.prop(fluid, "use_viscoelastic_springs") + sub = col.column(align=True) + sub.active = fluid.use_viscoelastic_springs + sub.prop(fluid, "yield_ratio", slider=True) + sub.prop(fluid, "plasticity", slider=True) + + col = split.column() + col.label(text="Advanced:") + sub = col.row() + sub.prop(fluid, "rest_length", slider=fluid.factor_rest_length) + sub.prop(fluid, "factor_rest_length", text="") + col.label(text="") + sub = col.column() + sub.active = fluid.use_viscoelastic_springs + sub.prop(fluid, "use_initial_rest_length") + sub.prop(fluid, "spring_frames", text="Frames") elif part.physics_type == 'KEYED': split = layout.split() @@ -503,7 +543,8 @@ class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): col = row.column() col.active = not psys.use_keyed_timing col.prop(part, "keyed_loops", text="Loops") - row.prop(psys, "use_keyed_timing", text="Use Timing") + if psys: + row.prop(psys, "use_keyed_timing", text="Use Timing") layout.label(text="Keys:") elif part.physics_type == 'BOIDS': @@ -554,7 +595,7 @@ class PARTICLE_PT_physics(ParticleButtonsPanel, bpy.types.Panel): col.prop(boids, "pitch", slider=True) col.prop(boids, "height", slider=True) - if part.physics_type == 'KEYED' or part.physics_type == 'BOIDS' or part.physics_type == 'FLUID': + if psys and part.physics_type in {'KEYED', 'BOIDS', 'FLUID'}: if part.physics_type == 'BOIDS': layout.label(text="Relations:") elif part.physics_type == 'FLUID': @@ -609,19 +650,19 @@ class PARTICLE_PT_boidbrain(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): psys = context.particle_system + settings = particle_get_settings(context) engine = context.scene.render.engine - if psys is None: - return False - if psys.settings is None: + + if settings is None: return False - if psys.point_cache.use_external: + if psys != None and psys.point_cache.use_external: return False - return psys.settings.physics_type == 'BOIDS' and engine in cls.COMPAT_ENGINES + return settings.physics_type == 'BOIDS' and engine in cls.COMPAT_ENGINES def draw(self, context): layout = self.layout - boids = context.particle_system.settings.boids + boids = particle_get_settings(context).boids layout.enabled = particle_panel_enabled(context, context.particle_system) @@ -709,65 +750,65 @@ class PARTICLE_PT_render(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - psys = context.particle_system + settings = particle_get_settings(context) engine = context.scene.render.engine - if psys is None: - return False - if psys.settings is None: + if settings is None: return False + return engine in cls.COMPAT_ENGINES def draw(self, context): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) row = layout.row() row.prop(part, "material") - row.prop(psys, "parent") + if psys: + row.prop(psys, "parent") split = layout.split() - sub = split.column() - sub.prop(part, "use_render_emitter") - sub.prop(part, "use_parent_particles") - sub = split.column() - sub.prop(part, "show_unborn") - sub.prop(part, "use_dead") + col = split.column() + col.prop(part, "use_render_emitter") + col.prop(part, "use_parent_particles") - row = layout.row() - row.prop(part, "render_type", expand=True) + col = split.column() + col.prop(part, "show_unborn") + col.prop(part, "use_dead") + + layout.prop(part, "render_type", expand=True) split = layout.split() - sub = split.column() + col = split.column() if part.render_type == 'LINE': - sub.prop(part, "line_length_tail") - sub.prop(part, "line_length_head") - sub = split.column() - sub.prop(part, "use_velocity_length") + col.prop(part, "line_length_tail") + col.prop(part, "line_length_head") + + split.prop(part, "use_velocity_length") elif part.render_type == 'PATH': - sub.prop(part, "use_strand_primitive") - subsub = sub.column() - subsub.active = (part.use_strand_primitive is False) - subsub.prop(part, "use_render_adaptive") - subsub = sub.column() - subsub.active = part.use_render_adaptive or part.use_strand_primitive == True - subsub.prop(part, "adaptive_angle") - subsub = sub.column() - subsub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False) - subsub.prop(part, "adaptive_pixel") - sub.prop(part, "use_hair_bspline") - sub.prop(part, "render_step", text="Steps") + col.prop(part, "use_strand_primitive") + sub = col.column() + sub.active = (part.use_strand_primitive is False) + sub.prop(part, "use_render_adaptive") + sub = col.column() + sub.active = part.use_render_adaptive or part.use_strand_primitive == True + sub.prop(part, "adaptive_angle") + sub = col.column() + sub.active = (part.use_render_adaptive is True and part.use_strand_primitive is False) + sub.prop(part, "adaptive_pixel") + col.prop(part, "use_hair_bspline") + col.prop(part, "render_step", text="Steps") - sub = split.column() - sub.label(text="Timing:") - sub.prop(part, "use_absolute_path_time") - sub.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time) - sub.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time) - sub.prop(part, "length_random", text="Random", slider=True) + col = split.column() + col.label(text="Timing:") + col.prop(part, "use_absolute_path_time") + col.prop(part, "path_start", text="Start", slider=not part.use_absolute_path_time) + col.prop(part, "path_end", text="End", slider=not part.use_absolute_path_time) + col.prop(part, "length_random", text="Random", slider=True) row = layout.row() col = row.column() @@ -786,22 +827,23 @@ class PARTICLE_PT_render(ParticleButtonsPanel, bpy.types.Panel): sub.prop(part, "simplify_viewport") elif part.render_type == 'OBJECT': - sub.prop(part, "dupli_object") - sub.prop(part, "use_global_dupli") + col.prop(part, "dupli_object") + col.prop(part, "use_global_dupli") elif part.render_type == 'GROUP': - sub.prop(part, "dupli_group") + col.prop(part, "dupli_group") split = layout.split() - sub = split.column() - sub.prop(part, "use_whole_group") - subsub = sub.column() - subsub.active = (part.use_whole_group is False) - subsub.prop(part, "use_group_count") - sub = split.column() - subsub = sub.column() - subsub.active = (part.use_whole_group is False) - subsub.prop(part, "use_global_dupli") - subsub.prop(part, "use_group_pick_random") + col = split.column() + col.prop(part, "use_whole_group") + sub = col.column() + sub.active = (part.use_whole_group is False) + sub.prop(part, "use_group_count") + + col = split.column() + sub = col.column() + sub.active = (part.use_whole_group is False) + sub.prop(part, "use_global_dupli") + sub.prop(part, "use_group_pick_random") if part.use_group_count and not part.use_whole_group: row = layout.row() @@ -821,7 +863,9 @@ class PARTICLE_PT_render(ParticleButtonsPanel, bpy.types.Panel): row.prop(weight, "count") elif part.render_type == 'BILLBOARD': - sub.label(text="Align:") + ob = context.object + + col.label(text="Align:") row = layout.row() row.prop(part, "billboard_align", expand=True) @@ -833,21 +877,25 @@ class PARTICLE_PT_render(ParticleButtonsPanel, bpy.types.Panel): col = row.column(align=True) col.label(text="Tilt:") col.prop(part, "billboard_tilt", text="Angle", slider=True) - col.prop(part, "billboard_tilt_random", slider=True) + col.prop(part, "billboard_tilt_random", text="Random", slider=True) col = row.column() col.prop(part, "billboard_offset") - row = layout.row() - row.prop(psys, "billboard_normal_uv") - row = layout.row() - row.prop(psys, "billboard_time_index_uv") + if psys: + col = layout.column() + col.prop_search(psys, "billboard_normal_uv", ob.data, "uv_textures") + col.prop_search(psys, "billboard_time_index_uv", ob.data, "uv_textures") - row = layout.row() - row.label(text="Split uv's:") - row.prop(part, "billboard_uv_split", text="Number of splits") - row = layout.row() - row.prop(psys, "billboard_split_uv") - row = layout.row() + split = layout.split(percentage=0.33) + split.label(text="Split uv's:") + split.prop(part, "billboard_uv_split", text="Number of splits") + + if psys: + col = layout.column() + col.active = part.billboard_uv_split > 1 + col.prop_search(psys, "billboard_split_uv", ob.data, "uv_textures") + + row = col.row() row.label(text="Animate:") row.prop(part, "billboard_animation", text="") row.label(text="Offset:") @@ -866,6 +914,11 @@ class PARTICLE_PT_render(ParticleButtonsPanel, bpy.types.Panel): col = row.column() col.label(text="") + if part.render_type in ('OBJECT', 'GROUP') and not part.use_advanced_hair: + row = layout.row(align=True) + row.prop(part, "particle_size") + row.prop(part, "size_random", slider=True) + class PARTICLE_PT_draw(ParticleButtonsPanel, bpy.types.Panel): bl_label = "Display" @@ -874,11 +927,9 @@ class PARTICLE_PT_draw(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): - psys = context.particle_system + settings = particle_get_settings(context) engine = context.scene.render.engine - if psys is None: - return False - if psys.settings is None: + if settings is None: return False return engine in cls.COMPAT_ENGINES @@ -886,7 +937,7 @@ class PARTICLE_PT_draw(ParticleButtonsPanel, bpy.types.Panel): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) row = layout.row() row.prop(part, "draw_method", expand=True) @@ -903,7 +954,7 @@ class PARTICLE_PT_draw(ParticleButtonsPanel, bpy.types.Panel): else: row.label(text="") - if part.draw_percentage != 100: + if part.draw_percentage != 100 and psys is not None: if part.type == 'HAIR': if psys.use_hair_dynamics and psys.point_cache.is_baked == False: layout.row().label(text="Display percentage makes dynamics inaccurate without baking!") @@ -920,16 +971,15 @@ class PARTICLE_PT_draw(ParticleButtonsPanel, bpy.types.Panel): if part.physics_type == 'BOIDS': col.prop(part, "show_health") - col = row.column() - col.prop(part, "show_material_color", text="Use material color") + col = row.column(align=True) + col.label(text="Color:") + col.prop(part, "draw_color", text="") + sub = col.row() + sub.active = part.draw_color in ('VELOCITY', 'ACCELERATION') + sub.prop(part, "color_maximum", text="Max") if (path): col.prop(part, "draw_step") - else: - sub = col.column() - sub.active = (part.show_material_color is False) - #sub.label(text="color") - #sub.label(text="Override material color") class PARTICLE_PT_children(ParticleButtonsPanel, bpy.types.Panel): @@ -945,7 +995,7 @@ class PARTICLE_PT_children(ParticleButtonsPanel, bpy.types.Panel): layout = self.layout psys = context.particle_system - part = psys.settings + part = particle_get_settings(context) layout.row().prop(part, "child_type", expand=True) @@ -960,7 +1010,8 @@ class PARTICLE_PT_children(ParticleButtonsPanel, bpy.types.Panel): if part.child_type == 'INTERPOLATED': col = row.column() - col.prop(psys, "child_seed", text="Seed") + if psys: + col.prop(psys, "child_seed", text="Seed") col.prop(part, "virtual_parents", slider=True) col.prop(part, "create_long_hair_children") else: @@ -985,7 +1036,8 @@ class PARTICLE_PT_children(ParticleButtonsPanel, bpy.types.Panel): sub = col.column(align=True) sub.prop(part, "child_radius", text="Radius") sub.prop(part, "child_roundness", text="Roundness", slider=True) - sub.prop(psys, "child_seed", text="Seed") + if psys: + sub.prop(psys, "child_seed", text="Seed") elif part.virtual_parents > 0.0: sub = col.column(align=True) sub.label(text="Parting not") @@ -1040,7 +1092,7 @@ class PARTICLE_PT_field_weights(ParticleButtonsPanel, bpy.types.Panel): return particle_panel_poll(cls, context) def draw(self, context): - part = context.particle_system.settings + part = particle_get_settings(context) effector_weights_ui(self, context, part.effector_weights) if part.type == 'HAIR': @@ -1059,14 +1111,18 @@ class PARTICLE_PT_force_fields(ParticleButtonsPanel, bpy.types.Panel): def draw(self, context): layout = self.layout - part = context.particle_system.settings + part = particle_get_settings(context) - layout.prop(part, "use_self_effect") + row = layout.row() + row.prop(part, "use_self_effect") + row.prop(part, "effector_amount", text="Amount") split = layout.split(percentage=0.2) split.label(text="Type 1:") split.prop(part.force_field_1, "type", text="") basic_force_field_settings_ui(self, context, part.force_field_1) + if part.force_field_1.type != 'NONE': + layout.label(text="Falloff:") basic_force_field_falloff_ui(self, context, part.force_field_1) if part.force_field_1.type != 'NONE': @@ -1076,6 +1132,8 @@ class PARTICLE_PT_force_fields(ParticleButtonsPanel, bpy.types.Panel): split.label(text="Type 2:") split.prop(part.force_field_2, "type", text="") basic_force_field_settings_ui(self, context, part.force_field_2) + if part.force_field_2.type != 'NONE': + layout.label(text="Falloff:") basic_force_field_falloff_ui(self, context, part.force_field_2) @@ -1086,6 +1144,8 @@ class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): + if context.particle_system is None: + return False return particle_panel_poll(cls, context) def draw(self, context): @@ -1093,9 +1153,6 @@ class PARTICLE_PT_vertexgroups(ParticleButtonsPanel, bpy.types.Panel): ob = context.object psys = context.particle_system - # part = psys.settings - - # layout.label(text="Nothing here yet.") row = layout.row() row.label(text="Vertex Group") @@ -1156,13 +1213,5 @@ class PARTICLE_PT_custom_props(ParticleButtonsPanel, PropertyPanel, bpy.types.Pa _context_path = "particle_system.settings" _property_type = bpy.types.ParticleSettings - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_cloth.py b/release/scripts/startup/bl_ui/properties_physics_cloth.py index 0ac4429c71a..bce6ab993a7 100644 --- a/release/scripts/ui/properties_physics_cloth.py +++ b/release/scripts/startup/bl_ui/properties_physics_cloth.py @@ -20,8 +20,10 @@ import bpy -from properties_physics_common import point_cache_ui -from properties_physics_common import effector_weights_ui +from bl_ui.properties_physics_common import ( + point_cache_ui, + effector_weights_ui, + ) def cloth_panel_enabled(md): @@ -112,7 +114,7 @@ class PHYSICS_PT_cloth(PhysicButtonsPanel, bpy.types.Panel): if key: col.label(text="Rest Shape Key:") - col.prop_search(cloth, "rest_shape_key", key, "keys", text="") + col.prop_search(cloth, "rest_shape_key", key, "key_blocks", text="") class PHYSICS_PT_cloth_cache(PhysicButtonsPanel, bpy.types.Panel): @@ -155,6 +157,8 @@ class PHYSICS_PT_cloth_collision(PhysicButtonsPanel, bpy.types.Panel): col = split.column() col.prop(cloth, "collision_quality", slider=True, text="Quality") col.prop(cloth, "distance_min", slider=True, text="Distance") + col.prop(cloth, "repel_force", slider=True, text="Repel") + col.prop(cloth, "distance_repel", slider=True, text="Repel Distance") col.prop(cloth, "friction") col = split.column() @@ -215,13 +219,5 @@ class PHYSICS_PT_cloth_field_weights(PhysicButtonsPanel, bpy.types.Panel): cloth = context.cloth.settings effector_weights_ui(self, context, cloth.effector_weights) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_common.py b/release/scripts/startup/bl_ui/properties_physics_common.py index 3d854533506..f7cf8da1840 100644 --- a/release/scripts/ui/properties_physics_common.py +++ b/release/scripts/startup/bl_ui/properties_physics_common.py @@ -89,7 +89,7 @@ def point_cache_ui(self, context, cache, enabled, cachetype): col.operator("ptcache.remove", icon='ZOOMOUT', text="") row = layout.row() - if cachetype in ('PSYS', 'HAIR', 'SMOKE'): + if cachetype in {'PSYS', 'HAIR', 'SMOKE'}: row.prop(cache, "use_external") if cache.use_external: @@ -106,7 +106,7 @@ def point_cache_ui(self, context, cache, enabled, cachetype): layout.label(text=cache.info) else: if cachetype == 'SMOKE': - if bpy.data.is_dirty: + if not bpy.data.is_saved: layout.label(text="Cache is disabled until the file is saved") layout.enabled = False @@ -121,7 +121,7 @@ def point_cache_ui(self, context, cache, enabled, cachetype): row.enabled = enabled row.prop(cache, "frame_start") row.prop(cache, "frame_end") - if cachetype not in ('SMOKE', 'CLOTH'): + if cachetype not in {'SMOKE', 'CLOTH'}: row.prop(cache, "frame_step") row.prop(cache, "use_quick_cache") if cachetype != 'SMOKE': @@ -129,7 +129,7 @@ def point_cache_ui(self, context, cache, enabled, cachetype): if cachetype != 'SMOKE': split = layout.split() - split.enabled = enabled and (not bpy.data.is_dirty) + split.enabled = enabled and bpy.data.is_saved col = split.column() col.prop(cache, "use_disk_cache") @@ -139,7 +139,7 @@ def point_cache_ui(self, context, cache, enabled, cachetype): col.prop(cache, "use_library_path", "Use Lib Path") row = layout.row() - row.enabled = enabled and (not bpy.data.is_dirty) + row.enabled = enabled and bpy.data.is_saved row.active = cache.use_disk_cache row.label(text="Compression:") row.prop(cache, "compression", expand=True) @@ -232,8 +232,9 @@ def basic_force_field_settings_ui(self, context, field): col.prop(field, "flow") col = split.column() - col.prop(field, "noise") - col.prop(field, "seed") + sub = col.column(align=True) + sub.prop(field, "noise") + sub.prop(field, "seed") if field.type == 'TURBULENCE': col.prop(field, "use_global_coords", text="Global") elif field.type == 'HARMONIC': @@ -254,8 +255,6 @@ def basic_force_field_settings_ui(self, context, field): def basic_force_field_falloff_ui(self, context, field): layout = self.layout - # XXX: This doesn't update for some reason. - #split = layout.split() split = layout.split(percentage=0.35) if not field or field.type == 'NONE': @@ -263,27 +262,24 @@ def basic_force_field_falloff_ui(self, context, field): col = split.column() col.prop(field, "z_direction", text="") - col.prop(field, "use_min_distance", text="Use Minimum") - col.prop(field, "use_max_distance", text="Use Maximum") col = split.column() col.prop(field, "falloff_power", text="Power") - sub = col.column() + split = layout.split() + col = split.column() + row = col.row(align=True) + row.prop(field, "use_min_distance", text="") + sub = row.row() sub.active = field.use_min_distance - sub.prop(field, "distance_min", text="Distance") + sub.prop(field, "distance_min", text="Minimum") - sub = col.column() + col = split.column() + row = col.row(align=True) + row.prop(field, "use_max_distance", text="") + sub = row.row() sub.active = field.use_max_distance - sub.prop(field, "distance_max", text="Distance") - + sub.prop(field, "distance_max", text="Maximum") -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_field.py b/release/scripts/startup/bl_ui/properties_physics_field.py index 8a697806aa1..9f96f0a5b9f 100644 --- a/release/scripts/ui/properties_physics_field.py +++ b/release/scripts/startup/bl_ui/properties_physics_field.py @@ -20,8 +20,10 @@ import bpy -from properties_physics_common import basic_force_field_settings_ui -from properties_physics_common import basic_force_field_falloff_ui +from bl_ui.properties_physics_common import ( + basic_force_field_settings_ui, + basic_force_field_falloff_ui, + ) class PhysicButtonsPanel(): @@ -55,7 +57,7 @@ class PHYSICS_PT_field(PhysicButtonsPanel, bpy.types.Panel): split.prop(field, "type", text="") - if field.type not in ('NONE', 'GUIDE', 'TEXTURE'): + if field.type not in {'NONE', 'GUIDE', 'TEXTURE'}: split = layout.split(percentage=0.2) split.label(text="Shape:") split.prop(field, "shape", text="") @@ -112,7 +114,7 @@ class PHYSICS_PT_field(PhysicButtonsPanel, bpy.types.Panel): else: basic_force_field_settings_ui(self, context, field) - if field.type not in ('NONE', 'GUIDE'): + if field.type not in {'NONE', 'GUIDE'}: layout.label(text="Falloff:") layout.prop(field, "falloff_type", expand=True) @@ -215,13 +217,5 @@ class PHYSICS_PT_collision(PhysicButtonsPanel, bpy.types.Panel): col.label(text="Force Fields:") col.prop(settings, "absorption", text="Absorption") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_fluid.py b/release/scripts/startup/bl_ui/properties_physics_fluid.py index 6385a535a44..5da89d0090a 100644 --- a/release/scripts/ui/properties_physics_fluid.py +++ b/release/scripts/startup/bl_ui/properties_physics_fluid.py @@ -49,11 +49,11 @@ class PHYSICS_PT_fluid(PhysicButtonsPanel, bpy.types.Panel): return row.prop(fluid, "type") - if fluid.type not in ('NONE', 'DOMAIN', 'PARTICLE', 'FLUID'): + if fluid.type not in {'NONE', 'DOMAIN', 'PARTICLE', 'FLUID'}: row.prop(fluid, "use", text="") layout = layout.column() - if fluid.type not in ('NONE', 'DOMAIN', 'PARTICLE', 'FLUID'): + if fluid.type not in {'NONE', 'DOMAIN', 'PARTICLE', 'FLUID'}: layout.active = fluid.use if fluid.type == 'DOMAIN': @@ -123,7 +123,9 @@ class PHYSICS_PT_fluid(PhysicButtonsPanel, bpy.types.Panel): col.label(text="Volume Initialization:") col.prop(fluid, "volume_initialization", text="") col.prop(fluid, "use_animated_mesh") - col.prop(fluid, "use_local_coords") + row = col.row() + row.active = not fluid.use_animated_mesh + row.prop(fluid, "use_local_coords") col = split.column() col.label(text="Inflow Velocity:") @@ -280,13 +282,5 @@ class PHYSICS_PT_domain_particles(PhysicButtonsPanel, bpy.types.Panel): col.prop(fluid, "tracer_particles") col.prop(fluid, "generate_particles") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_smoke.py b/release/scripts/startup/bl_ui/properties_physics_smoke.py index 56ddff292d9..61d8d2e3825 100644 --- a/release/scripts/ui/properties_physics_smoke.py +++ b/release/scripts/startup/bl_ui/properties_physics_smoke.py @@ -20,8 +20,10 @@ import bpy -from properties_physics_common import point_cache_ui -from properties_physics_common import effector_weights_ui +from bl_ui.properties_physics_common import ( + point_cache_ui, + effector_weights_ui, + ) class PhysicButtonsPanel(): @@ -100,9 +102,6 @@ class PHYSICS_PT_smoke(PhysicButtonsPanel, bpy.types.Panel): sub.prop(flow, "density") sub.prop(flow, "temperature") - #elif md.smoke_type == 'COLLISION': - # layout.separator() - class PHYSICS_PT_smoke_groups(PhysicButtonsPanel, bpy.types.Panel): bl_label = "Smoke Groups" @@ -203,13 +202,5 @@ class PHYSICS_PT_smoke_field_weights(PhysicButtonsPanel, bpy.types.Panel): domain = context.smoke.domain_settings effector_weights_ui(self, context, domain.effector_weights) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_physics_softbody.py b/release/scripts/startup/bl_ui/properties_physics_softbody.py index 7d3fa66ed02..61115a0590e 100644 --- a/release/scripts/ui/properties_physics_softbody.py +++ b/release/scripts/startup/bl_ui/properties_physics_softbody.py @@ -20,8 +20,10 @@ import bpy -from properties_physics_common import point_cache_ui -from properties_physics_common import effector_weights_ui +from bl_ui.properties_physics_common import ( + point_cache_ui, + effector_weights_ui, + ) def softbody_panel_enabled(md): @@ -260,13 +262,5 @@ class PHYSICS_PT_softbody_field_weights(PhysicButtonsPanel, bpy.types.Panel): effector_weights_ui(self, context, softbody.effector_weights) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_render.py b/release/scripts/startup/bl_ui/properties_render.py index 5dfed9ccb20..a3b10702fa7 100644 --- a/release/scripts/ui/properties_render.py +++ b/release/scripts/startup/bl_ui/properties_render.py @@ -62,13 +62,9 @@ class RENDER_PT_render(RenderButtonsPanel, bpy.types.Panel): rd = context.scene.render - split = layout.split() - - col = split.column() - col.operator("render.render", text="Image", icon='RENDER_STILL') - - col = split.column() - col.operator("render.render", text="Animation", icon='RENDER_ANIMATION').animation = True + row = layout.row() + row.operator("render.render", text="Image", icon='RENDER_STILL') + row.operator("render.render", text="Animation", icon='RENDER_ANIMATION').animation = True layout.prop(rd, "display_mode", text="Display") @@ -293,6 +289,7 @@ class RENDER_PT_output(RenderButtonsPanel, bpy.types.Panel): layout.prop(rd, "filepath", text="") split = layout.split() + col = split.column() col.prop(rd, "file_format", text="") col.row().prop(rd, "color_mode", text="Color", expand=True) @@ -302,36 +299,21 @@ class RENDER_PT_output(RenderButtonsPanel, bpy.types.Panel): col.prop(rd, "use_overwrite") col.prop(rd, "use_placeholder") - if file_format in ('AVI_JPEG', 'JPEG'): - split = layout.split() - split.prop(rd, "file_quality", slider=True) + if file_format in {'AVI_JPEG', 'JPEG'}: + layout.prop(rd, "file_quality", slider=True) if file_format == 'PNG': - split = layout.split() - split.prop(rd, "file_quality", slider=True, text="Compression") - - elif file_format == 'MULTILAYER': - split = layout.split() + layout.prop(rd, "file_quality", slider=True, text="Compression") - col = split.column() - col.label(text="Codec:") - col.prop(rd, "exr_codec", text="") - col = split.column() - - elif file_format == 'OPEN_EXR': - split = layout.split() - - col = split.column() - col.label(text="Codec:") - col.prop(rd, "exr_codec", text="") + if file_format in {'OPEN_EXR', 'MULTILAYER'}: + row = layout.row() + row.prop(rd, "exr_codec", text="Codec") - subsplit = split.split() - col = subsplit.column() - col.prop(rd, "use_exr_half") - col.prop(rd, "exr_zbuf") - - col = subsplit.column() - col.prop(rd, "exr_preview") + if file_format == 'OPEN_EXR': + row = layout.row() + row.prop(rd, "use_exr_half") + row.prop(rd, "exr_zbuf") + row.prop(rd, "exr_preview") elif file_format == 'JPEG2000': split = layout.split() @@ -343,7 +325,7 @@ class RENDER_PT_output(RenderButtonsPanel, bpy.types.Panel): col.prop(rd, "jpeg2k_preset", text="") col.prop(rd, "jpeg2k_ycc") - elif file_format in ('CINEON', 'DPX'): + elif file_format in {'CINEON', 'DPX'}: split = layout.split() split.label("FIXME: hard coded Non-Linear, Gamma:1.0") @@ -359,12 +341,10 @@ class RENDER_PT_output(RenderButtonsPanel, bpy.types.Panel): ''' elif file_format == 'TIFF': - split = layout.split() - split.prop(rd, "use_tiff_16bit") + layout.prop(rd, "use_tiff_16bit") elif file_format == 'QUICKTIME_CARBON': - split = layout.split() - split.operator("scene.render_data_set_quicktime_codec") + layout.operator("scene.render_data_set_quicktime_codec") elif file_format == 'QUICKTIME_QTKIT': split = layout.split() @@ -376,12 +356,10 @@ class RENDER_PT_output(RenderButtonsPanel, bpy.types.Panel): col.prop(rd, "quicktime_audiocodec_type", text="Audio Codec") if rd.quicktime_audiocodec_type != 'No audio': split = layout.split() - col = split.column() if rd.quicktime_audiocodec_type == 'LPCM': - col.prop(rd, "quicktime_audio_bitdepth", text="") + split.prop(rd, "quicktime_audio_bitdepth", text="") - col = split.column() - col.prop(rd, "quicktime_audio_samplerate", text="") + split.prop(rd, "quicktime_audio_samplerate", text="") split = layout.split() col = split.column() @@ -406,7 +384,7 @@ class RENDER_PT_encoding(RenderButtonsPanel, bpy.types.Panel): @classmethod def poll(cls, context): rd = context.scene.render - return rd.file_format in ('FFMPEG', 'XVID', 'H264', 'THEORA') + return rd.file_format in {'FFMPEG', 'XVID', 'H264', 'THEORA'} def draw(self, context): layout = self.layout @@ -416,22 +394,15 @@ class RENDER_PT_encoding(RenderButtonsPanel, bpy.types.Panel): layout.menu("RENDER_MT_ffmpeg_presets", text="Presets") split = layout.split() - - col = split.column() - col.prop(rd, "ffmpeg_format") - if rd.ffmpeg_format in ('AVI', 'QUICKTIME', 'MKV', 'OGG'): - col = split.column() - col.prop(rd, "ffmpeg_codec") + split.prop(rd, "ffmpeg_format") + if rd.ffmpeg_format in {'AVI', 'QUICKTIME', 'MKV', 'OGG'}: + split.prop(rd, "ffmpeg_codec") else: split.label() - split = layout.split() - - col = split.column() - col.prop(rd, "ffmpeg_video_bitrate") - - col = split.column() - col.prop(rd, "ffmpeg_gopsize") + row = layout.row() + row.prop(rd, "ffmpeg_video_bitrate") + row.prop(rd, "ffmpeg_gopsize") split = layout.split() @@ -442,28 +413,24 @@ class RENDER_PT_encoding(RenderButtonsPanel, bpy.types.Panel): col.prop(rd, "ffmpeg_buffersize", text="Buffer") col = split.column() - col.prop(rd, "ffmpeg_autosplit") col.label(text="Mux:") col.prop(rd, "ffmpeg_muxrate", text="Rate") col.prop(rd, "ffmpeg_packetsize", text="Packet Size") - # Audio: - sub = layout.column() - - if rd.ffmpeg_format not in ('MP3', ): - sub.prop(rd, "ffmpeg_audio_codec", text="Audio Codec") + layout.separator() - sub.separator() + # Audio: + if rd.ffmpeg_format not in {'MP3'}: + layout.prop(rd, "ffmpeg_audio_codec", text="Audio Codec") - split = sub.split() + split = layout.split() col = split.column() col.prop(rd, "ffmpeg_audio_bitrate") col.prop(rd, "ffmpeg_audio_mixrate") - col = split.column() - col.prop(rd, "ffmpeg_audio_volume", slider=True) + split.prop(rd, "ffmpeg_audio_volume", slider=True) class RENDER_PT_antialiasing(RenderButtonsPanel, bpy.types.Panel): @@ -568,10 +535,7 @@ class RENDER_PT_dimensions(RenderButtonsPanel, bpy.types.Panel): fps_rate = round(rd.fps / rd.fps_base, 2) # TODO: Change the following to iterate over existing presets - if (fps_rate in (23.98, 24, 25, 29.97, 30, 50, 59.94, 60)): - custom_framerate = False - else: - custom_framerate = True + custom_framerate = (fps_rate not in {23.98, 24, 25, 29.97, 30, 50, 59.94, 60}) if custom_framerate == True: fps_label_text = "Custom (" + str(fps_rate) + " fps)" @@ -580,7 +544,7 @@ class RENDER_PT_dimensions(RenderButtonsPanel, bpy.types.Panel): sub.menu("RENDER_MT_framerate_presets", text=fps_label_text) - if (bpy.types.RENDER_MT_framerate_presets.bl_label == "Custom") or (custom_framerate == True): + if custom_framerate or (bpy.types.RENDER_MT_framerate_presets.bl_label == "Custom"): sub.prop(rd, "fps") sub.prop(rd, "fps_base", text="/") subrow = sub.row(align=True) @@ -651,7 +615,7 @@ class RENDER_PT_bake(RenderButtonsPanel, bpy.types.Panel): if rd.bake_type == 'NORMALS': layout.prop(rd, "bake_normal_space") - elif rd.bake_type in ('DISPLACEMENT', 'AO'): + elif rd.bake_type in {'DISPLACEMENT', 'AO'}: layout.prop(rd, "use_bake_normalize") # col.prop(rd, "bake_aa_mode") @@ -673,13 +637,5 @@ class RENDER_PT_bake(RenderButtonsPanel, bpy.types.Panel): sub.prop(rd, "bake_distance") sub.prop(rd, "bake_bias") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_scene.py b/release/scripts/startup/bl_ui/properties_scene.py index 51fe7c9e132..ce5ac0c62d3 100644 --- a/release/scripts/ui/properties_scene.py +++ b/release/scripts/startup/bl_ui/properties_scene.py @@ -55,14 +55,10 @@ class SCENE_PT_unit(SceneButtonsPanel, bpy.types.Panel): col.row().prop(unit, "system", expand=True) col.row().prop(unit, "system_rotation", expand=True) - split = layout.split() - split.active = (unit.system != 'NONE') - - col = split.column() - col.prop(unit, "scale_length", text="Scale") - - col = split.column() - col.prop(unit, "use_separate") + row = layout.row() + row.active = (unit.system != 'NONE') + row.prop(unit, "scale_length", text="Scale") + row.prop(unit, "use_separate") class SCENE_PT_keying_sets(SceneButtonsPanel, bpy.types.Panel): @@ -199,9 +195,6 @@ class SCENE_PT_custom_props(SceneButtonsPanel, PropertyPanel, bpy.types.Panel): _context_path = "scene" _property_type = bpy.types.Scene - -from bpy.props import * - # XXX, move operator to op/ dir @@ -310,83 +303,75 @@ class ANIM_OT_keying_set_export(bpy.types.Operator): return {'RUNNING_MODAL'} class SCENE_PT_navmesh(SceneButtonsPanel, bpy.types.Panel): - bl_label = "Navmesh" - bl_default_closed = True - COMPAT_ENGINES = {'BLENDER_GAME'} - - def draw(self, context): - layout = self.layout - - rd = context.scene.game_settings.recast_data - - layout.operator("object.create_navmesh", text='Build navigation mesh') - - layout.label(text="Rasterization:") - split = layout.split() - - col = split.column() - col.prop(rd, "cell_size") - col = split.column() - col.prop(rd, "cell_height") - - layout.separator() - - layout.label(text="Agent:") - split = layout.split() - - col = split.column() - row = col.row() - row.prop(rd, "agent_height") - row = col.row() - row.prop(rd, "agent_radius") - - col = split.column() - row = col.row() - row.prop(rd, "max_slope") - row = col.row() - row.prop(rd, "max_climb") - - layout.separator() - - layout.label(text="Region:") - split = layout.split() - col = split.column() - col.prop(rd, "region_min_size") - - col = split.column() - col.prop(rd, "region_merge_size") - - layout.separator() - - layout.label(text="Polygonization:") - split = layout.split() - col = split.column() - row = col.row() - row.prop(rd, "edge_max_len") - row = col.row() - row.prop(rd, "edge_max_error") - - col = split.column() - row = col.row() - row.prop(rd, "verts_per_poly") - - layout.separator() - - layout.label(text="Detail Mesh:") - split = layout.split() - col = split.column() - col.prop(rd, "sample_dist") - - col = split.column() - col.prop(rd, "sample_max_error") - - -def register(): + bl_label = "Navmesh" + bl_default_closed = True + COMPAT_ENGINES = {'BLENDER_GAME'} + + def draw(self, context): + layout = self.layout + + rd = context.scene.game_settings.recast_data + + layout.operator("object.create_navmesh", text='Build navigation mesh') + + layout.label(text="Rasterization:") + split = layout.split() + + col = split.column() + col.prop(rd, "cell_size") + col = split.column() + col.prop(rd, "cell_height") + + layout.separator() + + layout.label(text="Agent:") + split = layout.split() + + col = split.column() + row = col.row() + row.prop(rd, "agent_height") + row = col.row() + row.prop(rd, "agent_radius") + + col = split.column() + row = col.row() + row.prop(rd, "max_slope") + row = col.row() + row.prop(rd, "max_climb") + + layout.separator() + + layout.label(text="Region:") + split = layout.split() + col = split.column() + col.prop(rd, "region_min_size") + + col = split.column() + col.prop(rd, "region_merge_size") + + layout.separator() + + layout.label(text="Polygonization:") + split = layout.split() + col = split.column() + row = col.row() + row.prop(rd, "edge_max_len") + row = col.row() + row.prop(rd, "edge_max_error") + + col = split.column() + row = col.row() + row.prop(rd, "verts_per_poly") + + layout.separator() + + layout.label(text="Detail Mesh:") + split = layout.split() + col = split.column() + col.prop(rd, "sample_dist") + + col = split.column() + col.prop(rd, "sample_max_error") + +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_texture.py b/release/scripts/startup/bl_ui/properties_texture.py index 05e130fac2d..01890bc3c99 100644 --- a/release/scripts/ui/properties_texture.py +++ b/release/scripts/startup/bl_ui/properties_texture.py @@ -43,7 +43,7 @@ class TEXTURE_MT_envmap_specials(bpy.types.Menu): layout.operator("texture.envmap_clear", icon='FILE_REFRESH') layout.operator("texture.envmap_clear_all", icon='FILE_REFRESH') -from properties_material import active_node_mat +from bl_ui.properties_material import active_node_mat def context_tex_datablock(context): @@ -60,6 +60,12 @@ def context_tex_datablock(context): return idblock idblock = context.brush + if idblock: + return idblock + + if context.particle_system: + idblock = context.particle_system.settings + return idblock @@ -84,7 +90,7 @@ class TEXTURE_PT_context_texture(TextureButtonsPanel, bpy.types.Panel): engine = context.scene.render.engine if not hasattr(context, "texture_slot"): return False - return ((context.material or context.world or context.lamp or context.brush or context.texture) + return ((context.material or context.world or context.lamp or context.brush or context.texture or context.particle_system or isinstance(context.space_data.pin_id, bpy.types.ParticleSettings)) and (engine in cls.COMPAT_ENGINES)) def draw(self, context): @@ -96,13 +102,14 @@ class TEXTURE_PT_context_texture(TextureButtonsPanel, bpy.types.Panel): idblock = context_tex_datablock(context) pin_id = space.pin_id - if not isinstance(pin_id, bpy.types.Material): + if space.use_pin_id and not isinstance(pin_id, bpy.types.Texture): + idblock = pin_id pin_id = None if not space.use_pin_id: layout.prop(space, "texture_context", expand=True) - tex_collection = (not space.use_pin_id) and (node is None) and (not isinstance(idblock, bpy.types.Brush)) + tex_collection = (pin_id is None) and (node is None) and (not isinstance(idblock, bpy.types.Brush)) if tex_collection: row = layout.row() @@ -237,8 +244,7 @@ class TEXTURE_PT_clouds(TextureTypePanel, bpy.types.Panel): col.prop(tex, "noise_scale", text="Size") col.prop(tex, "noise_depth", text="Depth") - col = split.column() - col.prop(tex, "nabla", text="Nabla") + split.prop(tex, "nabla", text="Nabla") class TEXTURE_PT_wood(TextureTypePanel, bpy.types.Panel): @@ -251,24 +257,23 @@ class TEXTURE_PT_wood(TextureTypePanel, bpy.types.Panel): tex = context.texture - layout.prop(tex, "noisebasis_2", expand=True) + layout.prop(tex, "noise_basis_2", expand=True) layout.prop(tex, "wood_type", expand=True) col = layout.column() - col.active = tex.wood_type in ('RINGNOISE', 'BANDNOISE') + col.active = tex.wood_type in {'RINGNOISE', 'BANDNOISE'} col.label(text="Noise:") col.row().prop(tex, "noise_type", text="Type", expand=True) layout.prop(tex, "noise_basis", text="Basis") split = layout.split() - split.active = tex.wood_type in ('RINGNOISE', 'BANDNOISE') + split.active = tex.wood_type in {'RINGNOISE', 'BANDNOISE'} col = split.column() col.prop(tex, "noise_scale", text="Size") col.prop(tex, "turbulence") - col = split.column() - col.prop(tex, "nabla") + split.prop(tex, "nabla") class TEXTURE_PT_marble(TextureTypePanel, bpy.types.Panel): @@ -282,7 +287,7 @@ class TEXTURE_PT_marble(TextureTypePanel, bpy.types.Panel): tex = context.texture layout.prop(tex, "marble_type", expand=True) - layout.prop(tex, "noisebasis_2", expand=True) + layout.prop(tex, "noise_basis_2", expand=True) layout.label(text="Noise:") layout.prop(tex, "noise_type", text="Type", expand=True) layout.prop(tex, "noise_basis", text="Basis") @@ -308,13 +313,9 @@ class TEXTURE_PT_magic(TextureTypePanel, bpy.types.Panel): tex = context.texture - split = layout.split() - - col = split.column() - col.prop(tex, "noise_depth", text="Depth") - - col = split.column() - col.prop(tex, "turbulence") + row = layout.row() + row.prop(tex, "noise_depth", text="Depth") + row.prop(tex, "turbulence") class TEXTURE_PT_blend(TextureTypePanel, bpy.types.Panel): @@ -331,7 +332,7 @@ class TEXTURE_PT_blend(TextureTypePanel, bpy.types.Panel): sub = layout.row() - sub.active = (tex.progression in ('LINEAR', 'QUADRATIC', 'EASING', 'RADIAL')) + sub.active = (tex.progression in {'LINEAR', 'QUADRATIC', 'EASING', 'RADIAL'}) sub.prop(tex, "use_flip_axis", expand=True) @@ -350,13 +351,9 @@ class TEXTURE_PT_stucci(TextureTypePanel, bpy.types.Panel): layout.prop(tex, "noise_type", text="Type", expand=True) layout.prop(tex, "noise_basis", text="Basis") - split = layout.split() - - col = split.column() - col.prop(tex, "noise_scale", text="Size") - - col = split.column() - col.prop(tex, "turbulence") + row = layout.row() + row.prop(tex, "noise_scale", text="Size") + row.prop(tex, "turbulence") class TEXTURE_PT_image(TextureTypePanel, bpy.types.Panel): @@ -375,7 +372,7 @@ class TEXTURE_PT_image(TextureTypePanel, bpy.types.Panel): def texture_filter_common(tex, layout): layout.label(text="Filter:") layout.prop(tex, "filter_type", text="") - if tex.use_mipmap and tex.filter_type in ('AREA', 'EWA', 'FELINE'): + if tex.use_mipmap and tex.filter_type in {'AREA', 'EWA', 'FELINE'}: if tex.filter_type == 'FELINE': layout.prop(tex, "filter_probes", text="Probes") else: @@ -555,24 +552,20 @@ class TEXTURE_PT_musgrave(TextureTypePanel, bpy.types.Panel): musgrave_type = tex.musgrave_type col = split.column() - if musgrave_type in ('HETERO_TERRAIN', 'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'): + if musgrave_type in {'HETERO_TERRAIN', 'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'}: col.prop(tex, "offset") - if musgrave_type in ('MULTIFRACTAL', 'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'): + if musgrave_type in {'MULTIFRACTAL', 'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'}: col.prop(tex, "noise_intensity", text="Intensity") - if musgrave_type in ('RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'): + if musgrave_type in {'RIDGED_MULTIFRACTAL', 'HYBRID_MULTIFRACTAL'}: col.prop(tex, "gain") layout.label(text="Noise:") layout.prop(tex, "noise_basis", text="Basis") - split = layout.split() - - col = split.column() - col.prop(tex, "noise_scale", text="Size") - - col = split.column() - col.prop(tex, "nabla") + row = layout.row() + row.prop(tex, "noise_scale", text="Size") + row.prop(tex, "nabla") class TEXTURE_PT_voronoi(TextureTypePanel, bpy.types.Panel): @@ -606,14 +599,9 @@ class TEXTURE_PT_voronoi(TextureTypePanel, bpy.types.Panel): sub.prop(tex, "weight_4", text="4", slider=True) layout.label(text="Noise:") - - split = layout.split() - - col = split.column() - col.prop(tex, "noise_scale", text="Size") - - col = split.column() - col.prop(tex, "nabla") + row = layout.row() + row.prop(tex, "noise_scale", text="Size") + row.prop(tex, "nabla") class TEXTURE_PT_distortednoise(TextureTypePanel, bpy.types.Panel): @@ -635,8 +623,7 @@ class TEXTURE_PT_distortednoise(TextureTypePanel, bpy.types.Panel): col.prop(tex, "distortion", text="Distortion") col.prop(tex, "noise_scale", text="Size") - col = split.column() - col.prop(tex, "nabla") + split.prop(tex, "nabla") class TEXTURE_PT_voxeldata(TextureButtonsPanel, bpy.types.Panel): @@ -656,7 +643,7 @@ class TEXTURE_PT_voxeldata(TextureButtonsPanel, bpy.types.Panel): vd = tex.voxel_data layout.prop(vd, "file_format") - if vd.file_format in ('BLENDER_VOXEL', 'RAW_8BIT'): + if vd.file_format in {'BLENDER_VOXEL', 'RAW_8BIT'}: layout.prop(vd, "filepath") if vd.file_format == 'RAW_8BIT': layout.prop(vd, "resolution") @@ -668,7 +655,7 @@ class TEXTURE_PT_voxeldata(TextureButtonsPanel, bpy.types.Panel): layout.template_image(tex, "image", tex.image_user, compact=True) #layout.prop(vd, "frame_duration") - if vd.file_format in ('BLENDER_VOXEL', 'RAW_8BIT'): + if vd.file_format in {'BLENDER_VOXEL', 'RAW_8BIT'}: layout.prop(vd, "use_still_frame") row = layout.row() row.active = vd.use_still_frame @@ -719,12 +706,13 @@ class TEXTURE_PT_pointdensity(TextureButtonsPanel, bpy.types.Panel): col.separator() - col.label(text="Color Source:") - col.prop(pd, "color_source", text="") - if pd.color_source in ('PARTICLE_SPEED', 'PARTICLE_VELOCITY'): - col.prop(pd, "speed_scale") - if pd.color_source in ('PARTICLE_SPEED', 'PARTICLE_AGE'): - layout.template_color_ramp(pd, "color_ramp", expand=True) + if pd.point_source == 'PARTICLE_SYSTEM': + col.label(text="Color Source:") + col.prop(pd, "color_source", text="") + if pd.color_source in {'PARTICLE_SPEED', 'PARTICLE_VELOCITY'}: + col.prop(pd, "speed_scale") + if pd.color_source in {'PARTICLE_SPEED', 'PARTICLE_AGE'}: + layout.template_color_ramp(pd, "color_ramp", expand=True) col = split.column() col.label() @@ -733,6 +721,15 @@ class TEXTURE_PT_pointdensity(TextureButtonsPanel, bpy.types.Panel): col.prop(pd, "falloff", text="") if pd.falloff == 'SOFT': col.prop(pd, "falloff_soft") + if pd.falloff == "PARTICLE_VELOCITY": + col.prop(pd, "falloff_speed_scale") + + col.prop(pd, "use_falloff_curve") + + if pd.use_falloff_curve: + col = layout.column() + col.label(text="Falloff Curve") + col.template_curve_mapping(pd, "falloff_curve", brush=False) class TEXTURE_PT_pointdensity_turbulence(TextureButtonsPanel, bpy.types.Panel): @@ -746,12 +743,9 @@ class TEXTURE_PT_pointdensity_turbulence(TextureButtonsPanel, bpy.types.Panel): return tex and (tex.type == 'POINT_DENSITY' and (engine in cls.COMPAT_ENGINES)) def draw_header(self, context): - layout = self.layout + pd = context.texture.point_density - tex = context.texture - pd = tex.point_density - - layout.prop(pd, "use_turbulence", text="") + self.layout.prop(pd, "use_turbulence", text="") def draw(self, context): layout = self.layout @@ -834,7 +828,7 @@ class TEXTURE_PT_mapping(TextureSlotPanel, bpy.types.Panel): layout.prop(tex, "map_mode", expand=True) row = layout.row() - row.active = tex.map_mode in ('FIXED', 'TILED') + row.active = tex.map_mode in {'FIXED', 'TILED'} row.prop(tex, "angle") else: if isinstance(idblock, bpy.types.Material): @@ -845,7 +839,7 @@ class TEXTURE_PT_mapping(TextureSlotPanel, bpy.types.Panel): split = layout.split() col = split.column() - if tex.texture_coords in ('ORCO', 'UV'): + if tex.texture_coords in {'ORCO', 'UV'}: col.prop(tex, "use_from_dupli") elif tex.texture_coords == 'OBJECT': col.prop(tex, "use_from_original") @@ -858,14 +852,9 @@ class TEXTURE_PT_mapping(TextureSlotPanel, bpy.types.Panel): row.prop(tex, "mapping_y", text="") row.prop(tex, "mapping_z", text="") - split = layout.split() - - col = split.column() - col.prop(tex, "offset") - - col = split.column() - - col.prop(tex, "scale") + row = layout.row() + row.column().prop(tex, "offset") + row.column().prop(tex, "scale") class TEXTURE_PT_influence(TextureSlotPanel, bpy.types.Panel): @@ -902,7 +891,7 @@ class TEXTURE_PT_influence(TextureSlotPanel, bpy.types.Panel): return sub # XXX, temp. use_map_normal needs to override. if isinstance(idblock, bpy.types.Material): - if idblock.type in ('SURFACE', 'WIRE'): + if idblock.type in {'SURFACE', 'WIRE'}: split = layout.split() col = split.column() @@ -983,38 +972,65 @@ class TEXTURE_PT_influence(TextureSlotPanel, bpy.types.Panel): col = split.column() factor_but(col, "use_map_zenith_up", "zenith_up_factor", "Zenith Up") factor_but(col, "use_map_zenith_down", "zenith_down_factor", "Zenith Down") + elif isinstance(idblock, bpy.types.ParticleSettings): + split = layout.split() + + col = split.column() + col.label(text="General:") + factor_but(col, "use_map_time", "time_factor", "Time") + factor_but(col, "use_map_life", "life_factor", "Lifetime") + factor_but(col, "use_map_density", "density_factor", "Density") + factor_but(col, "use_map_size", "size_factor", "Size") + + col = split.column() + col.label(text="Physics:") + factor_but(col, "use_map_velocity", "velocity_factor", "Velocity") + factor_but(col, "use_map_damp", "damp_factor", "Damp") + factor_but(col, "use_map_gravity", "gravity_factor", "Gravity") + factor_but(col, "use_map_field", "field_factor", "Force Fields") + + layout.label(text="Hair:") + + split = layout.split() + + col = split.column() + factor_but(col, "use_map_length", "length_factor", "Length") + factor_but(col, "use_map_clump", "clump_factor", "Clump") + + col = split.column() + factor_but(col, "use_map_kink", "kink_factor", "Kink") + factor_but(col, "use_map_rough", "rough_factor", "Rough") layout.separator() - split = layout.split() + if not isinstance(idblock, bpy.types.ParticleSettings): + split = layout.split() - col = split.column() - col.prop(tex, "blend_type", text="Blend") - col.prop(tex, "use_rgb_to_intensity") - # color is used on grayscale textures even when use_rgb_to_intensity is disabled. - col.prop(tex, "color", text="") + col = split.column() + col.prop(tex, "blend_type", text="Blend") + col.prop(tex, "use_rgb_to_intensity") + # color is used on grayscale textures even when use_rgb_to_intensity is disabled. + col.prop(tex, "color", text="") - col = split.column() - col.prop(tex, "invert", text="Negative") - col.prop(tex, "use_stencil") + col = split.column() + col.prop(tex, "invert", text="Negative") + col.prop(tex, "use_stencil") if isinstance(idblock, bpy.types.Material) or isinstance(idblock, bpy.types.World): col.prop(tex, "default_value", text="DVar", slider=True) if isinstance(idblock, bpy.types.Material): - row = layout.row() - row.label(text="Bump Mapping:") + layout.label(text="Bump Mapping:") - row = layout.row() # only show bump settings if activated but not for normalmap images - row.active = tex.use_map_normal and not (tex.texture.type == 'IMAGE' and tex.texture.use_normal_map) + row = layout.row() + row.active = (tex.use_map_normal or tex.use_map_warp) and not (tex.texture.type == 'IMAGE' and tex.texture.use_normal_map) - col = row.column() - col.prop(tex, "bump_method", text="Method") + row.prop(tex, "bump_method", text="Method") - col = row.column() - col.prop(tex, "bump_objectspace", text="Space") - col.active = tex.bump_method in ('BUMP_DEFAULT', 'BUMP_BEST_QUALITY') + sub = row.row() + sub.active = tex.bump_method in {'BUMP_DEFAULT', 'BUMP_BEST_QUALITY'} + sub.prop(tex, "bump_objectspace", text="Space") class TEXTURE_PT_custom_props(TextureButtonsPanel, PropertyPanel, bpy.types.Panel): @@ -1022,13 +1038,5 @@ class TEXTURE_PT_custom_props(TextureButtonsPanel, PropertyPanel, bpy.types.Pane _context_path = "texture" _property_type = bpy.types.Texture - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/properties_world.py b/release/scripts/startup/bl_ui/properties_world.py index fd11f299f34..4f398c9fbd9 100644 --- a/release/scripts/ui/properties_world.py +++ b/release/scripts/startup/bl_ui/properties_world.py @@ -266,13 +266,5 @@ class WORLD_PT_custom_props(WorldButtonsPanel, PropertyPanel, bpy.types.Panel): _context_path = "world" _property_type = bpy.types.World - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_console.py b/release/scripts/startup/bl_ui/space_console.py index b3bd886d8c5..da6c102100b 100644 --- a/release/scripts/ui/space_console.py +++ b/release/scripts/startup/bl_ui/space_console.py @@ -18,15 +18,13 @@ # <pep8 compliant> import bpy -from bpy.props import * +from bpy.props import StringProperty class CONSOLE_HT_header(bpy.types.Header): bl_space_type = 'CONSOLE' def draw(self, context): - sc = context.space_data - # text = sc.text layout = self.layout row = layout.row(align=True) @@ -161,13 +159,5 @@ class ConsoleLanguage(bpy.types.Operator): return {'FINISHED'} - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_dopesheet.py b/release/scripts/startup/bl_ui/space_dopesheet.py index 9a1acfd76b8..930a2029d32 100644 --- a/release/scripts/ui/space_dopesheet.py +++ b/release/scripts/startup/bl_ui/space_dopesheet.py @@ -33,48 +33,52 @@ def dopesheet_filter(layout, context, genericFiltersOnly=False): row.prop(dopesheet, "show_only_selected", text="") row.prop(dopesheet, "show_hidden", text="") - if genericFiltersOnly: - return - - row = layout.row(align=True) - row.prop(dopesheet, "show_transforms", text="") + if not genericFiltersOnly: + row = layout.row(align=True) + row.prop(dopesheet, "show_transforms", text="") - if is_nla: - row.prop(dopesheet, "show_missing_nla", text="") + if is_nla: + row.prop(dopesheet, "show_missing_nla", text="") - row = layout.row(align=True) - row.prop(dopesheet, "show_scenes", text="") - row.prop(dopesheet, "show_worlds", text="") - row.prop(dopesheet, "show_nodes", text="") - - if bpy.data.meshes: - row.prop(dopesheet, "show_meshes", text="") - if bpy.data.shape_keys: - row.prop(dopesheet, "show_shapekeys", text="") - if bpy.data.materials: - row.prop(dopesheet, "show_materials", text="") - if bpy.data.lamps: - row.prop(dopesheet, "show_lamps", text="") - if bpy.data.textures: - row.prop(dopesheet, "show_textures", text="") - if bpy.data.cameras: - row.prop(dopesheet, "show_cameras", text="") - if bpy.data.curves: - row.prop(dopesheet, "show_curves", text="") - if bpy.data.metaballs: - row.prop(dopesheet, "show_metaballs", text="") - if bpy.data.lattices: - row.prop(dopesheet, "show_lattices", text="") - if bpy.data.armatures: - row.prop(dopesheet, "show_armatures", text="") - if bpy.data.particles: - row.prop(dopesheet, "show_particles", text="") - - if bpy.data.groups: row = layout.row(align=True) - row.prop(dopesheet, "show_only_group_objects", text="") - if dopesheet.show_only_group_objects: - row.prop(dopesheet, "filter_group", text="") + row.prop(dopesheet, "show_scenes", text="") + row.prop(dopesheet, "show_worlds", text="") + row.prop(dopesheet, "show_nodes", text="") + + if bpy.data.meshes: + row.prop(dopesheet, "show_meshes", text="") + if bpy.data.shape_keys: + row.prop(dopesheet, "show_shapekeys", text="") + if bpy.data.materials: + row.prop(dopesheet, "show_materials", text="") + if bpy.data.lamps: + row.prop(dopesheet, "show_lamps", text="") + if bpy.data.textures: + row.prop(dopesheet, "show_textures", text="") + if bpy.data.cameras: + row.prop(dopesheet, "show_cameras", text="") + if bpy.data.curves: + row.prop(dopesheet, "show_curves", text="") + if bpy.data.metaballs: + row.prop(dopesheet, "show_metaballs", text="") + if bpy.data.lattices: + row.prop(dopesheet, "show_lattices", text="") + if bpy.data.armatures: + row.prop(dopesheet, "show_armatures", text="") + if bpy.data.particles: + row.prop(dopesheet, "show_particles", text="") + + if bpy.data.groups: + row = layout.row(align=True) + row.prop(dopesheet, "show_only_group_objects", text="") + if dopesheet.show_only_group_objects: + row.prop(dopesheet, "filter_group", text="") + + if not is_nla: + row = layout.row(align=True) + row.prop(dopesheet, "show_only_matching_fcurves", text="") + if dopesheet.show_only_matching_fcurves: + row.prop(dopesheet, "filter_fcurve_name", text="") ####################################### @@ -118,7 +122,7 @@ class DOPESHEET_HT_header(bpy.types.Header): # filters which will work here and are useful (especially for character animation) dopesheet_filter(layout, context, genericFiltersOnly=True) - if st.mode in ('ACTION', 'SHAPEKEY'): + if st.mode in {'ACTION', 'SHAPEKEY'}: layout.template_ID(st, "action", new="action.new") # Grease Pencil mode doesn't need snapping, as it's frame-aligned only @@ -159,6 +163,7 @@ class DOPESHEET_MT_view(bpy.types.Menu): layout.separator() layout.operator("action.frame_jump") layout.operator("action.view_all") + layout.operator("action.view_selected") layout.separator() layout.operator("screen.area_dupli") @@ -187,6 +192,10 @@ class DOPESHEET_MT_select(bpy.types.Menu): layout.operator("action.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN' layout.operator("action.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN' + layout.separator() + layout.operator("action.select_leftright", text="Before Current Frame").mode = 'LEFT' + layout.operator("action.select_leftright", text="After Current Frame").mode = 'RIGHT' + # FIXME: grease pencil mode isn't supported for these yet, so skip for that mode only if context.space_data.mode != 'GPENCIL': layout.separator() @@ -217,10 +226,13 @@ class DOPESHEET_MT_marker(bpy.types.Menu): layout.operator("marker.rename", text="Rename Marker") layout.operator("marker.move", text="Grab/Move Marker") - if st.mode in ('ACTION', 'SHAPEKEY') and st.action: + if st.mode in {'ACTION', 'SHAPEKEY'} and st.action: layout.separator() layout.prop(st, "show_pose_markers") + if st.show_pose_markers is False: + layout.operator("action.markers_make_local") + ####################################### # Keyframe Editing @@ -353,13 +365,5 @@ class DOPESHEET_MT_gpencil_frame(bpy.types.Menu): #layout.operator("action.copy") #layout.operator("action.paste") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_filebrowser.py b/release/scripts/startup/bl_ui/space_filebrowser.py index 2d66bb38637..73fe1a97252 100644 --- a/release/scripts/ui/space_filebrowser.py +++ b/release/scripts/startup/bl_ui/space_filebrowser.py @@ -43,7 +43,7 @@ class FILEBROWSER_HT_header(bpy.types.Header): row.separator() row = layout.row(align=True) - row.operator("file.directory_new", text="", icon='NEWFOLDER') + row.operator("file.directory_new", icon='NEWFOLDER') params = st.params @@ -73,13 +73,5 @@ class FILEBROWSER_HT_header(bpy.types.Header): row.prop(params, "use_filter_sound", text="") row.prop(params, "use_filter_text", text="") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_graph.py b/release/scripts/startup/bl_ui/space_graph.py index 55c4d4cfd94..bfc1a0e3a23 100644 --- a/release/scripts/ui/space_graph.py +++ b/release/scripts/startup/bl_ui/space_graph.py @@ -25,7 +25,7 @@ class GRAPH_HT_header(bpy.types.Header): bl_space_type = 'GRAPH_EDITOR' def draw(self, context): - from space_dopesheet import dopesheet_filter + from bl_ui.space_dopesheet import dopesheet_filter layout = self.layout @@ -100,6 +100,7 @@ class GRAPH_MT_view(bpy.types.Menu): layout.separator() layout.operator("graph.frame_jump") layout.operator("graph.view_all") + layout.operator("graph.view_selected") layout.separator() layout.operator("screen.area_dupli") @@ -130,6 +131,10 @@ class GRAPH_MT_select(bpy.types.Menu): layout.operator("graph.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN' layout.separator() + layout.operator("graph.select_leftright", text="Before Current Frame").mode = 'LEFT' + layout.operator("graph.select_leftright", text="After Current Frame").mode = 'RIGHT' + + layout.separator() layout.operator("graph.select_more") layout.operator("graph.select_less") @@ -205,6 +210,7 @@ class GRAPH_MT_key(bpy.types.Menu): layout.separator() layout.operator("graph.keyframe_insert") layout.operator("graph.fmodifier_add") + layout.operator("graph.sound_bake") layout.separator() layout.operator("graph.duplicate") @@ -240,13 +246,5 @@ class GRAPH_MT_key_transform(bpy.types.Menu): layout.operator("transform.rotate", text="Rotate") layout.operator("transform.resize", text="Scale") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_image.py b/release/scripts/startup/bl_ui/space_image.py index 433c12b0620..77583b80824 100644 --- a/release/scripts/ui/space_image.py +++ b/release/scripts/startup/bl_ui/space_image.py @@ -91,8 +91,8 @@ class IMAGE_MT_select(bpy.types.Menu): layout.separator() layout.operator("uv.select_all") - layout.operator("uv.select_inverse") - layout.operator("uv.unlink_selection") + layout.operator("uv.select_all", text="Inverse").action = 'INVERT' + layout.operator("uv.unlink_selected") layout.separator() @@ -128,6 +128,10 @@ class IMAGE_MT_image(bpy.types.Menu): layout.operator("image.external_edit", "Edit Externally") + layout.separator() + + layout.menu("IMAGE_MT_image_invert") + if not show_render: layout.separator() @@ -139,7 +143,7 @@ class IMAGE_MT_image(bpy.types.Menu): # only for dirty && specific image types, perhaps # this could be done in operator poll too if ima.is_dirty: - if ima.source in ('FILE', 'GENERATED') and ima.type != 'MULTILAYER': + if ima.source in {'FILE', 'GENERATED'} and ima.type != 'MULTILAYER': layout.operator("image.pack", text="Pack As PNG").as_png = True layout.separator() @@ -147,6 +151,32 @@ class IMAGE_MT_image(bpy.types.Menu): layout.prop(sima, "use_image_paint") +class IMAGE_MT_image_invert(bpy.types.Menu): + bl_label = "Invert" + + def draw(self, context): + layout = self.layout + + op = layout.operator("image.invert", text="Invert Image Colors") + op.invert_r = True + op.invert_g = True + op.invert_b = True + + layout.separator() + + op = layout.operator("image.invert", text="Invert Red Channel") + op.invert_r = True + + op = layout.operator("image.invert", text="Invert Green Channel") + op.invert_g = True + + op = layout.operator("image.invert", text="Invert Blue Channel") + op.invert_b = True + + op = layout.operator("image.invert", text="Invert Alpha Channel") + op.invert_a = True + + class IMAGE_MT_uvs_showhide(bpy.types.Menu): bl_label = "Show/Hide Faces" @@ -176,14 +206,14 @@ class IMAGE_MT_uvs_snap(bpy.types.Menu): layout = self.layout layout.operator_context = 'EXEC_REGION_WIN' - layout.operator("uv.snap_selection", text="Selected to Pixels").target = 'PIXELS' - layout.operator("uv.snap_selection", text="Selected to Cursor").target = 'CURSOR' - layout.operator("uv.snap_selection", text="Selected to Adjacent Unselected").target = 'ADJACENT_UNSELECTED' + layout.operator("uv.snap_selected", text="Selected to Pixels").target = 'PIXELS' + layout.operator("uv.snap_selected", text="Selected to Cursor").target = 'CURSOR' + layout.operator("uv.snap_selected", text="Selected to Adjacent Unselected").target = 'ADJACENT_UNSELECTED' layout.separator() layout.operator("uv.snap_cursor", text="Cursor to Pixels").target = 'PIXELS' - layout.operator("uv.snap_cursor", text="Cursor to Selection").target = 'SELECTION' + layout.operator("uv.snap_cursor", text="Cursor to Selected").target = 'SELECTED' class IMAGE_MT_uvs_mirror(bpy.types.Menu): @@ -204,7 +234,7 @@ class IMAGE_MT_uvs_weldalign(bpy.types.Menu): layout = self.layout layout.operator("uv.weld") # W, 1 - layout.operator_enums("uv.align", "axis") # W, 2/3/4 + layout.operator_enum("uv.align", "axis") # W, 2/3/4 class IMAGE_MT_uvs(bpy.types.Menu): @@ -233,7 +263,7 @@ class IMAGE_MT_uvs(bpy.types.Menu): layout.operator("uv.average_islands_scale") layout.operator("uv.minimize_stretch") layout.operator("uv.stitch") - layout.operator("mesh.faces_miror_uv") + layout.operator("mesh.faces_mirror_uv") layout.separator() @@ -340,10 +370,7 @@ class IMAGE_HT_header(bpy.types.Header): layout.prop(toolsettings, "use_uv_select_sync", text="") if toolsettings.use_uv_select_sync: - row = layout.row(align=True) - row.prop(toolsettings, "mesh_select_mode", text="", index=0, icon='VERTEXSEL') - row.prop(toolsettings, "mesh_select_mode", text="", index=1, icon='EDGESEL') - row.prop(toolsettings, "mesh_select_mode", text="", index=2, icon='FACESEL') + layout.template_edit_mode_selection() else: layout.prop(toolsettings, "uv_select_mode", text="", expand=True) layout.prop(uvedit, "sticky_select_mode", text="", icon_only=True) @@ -357,8 +384,8 @@ class IMAGE_HT_header(bpy.types.Header): row.prop(toolsettings, "use_snap", text="") row.prop(toolsettings, "snap_element", text="", icon_only=True) - # mesh = context.edit_object.data - # row.prop_search(mesh.uv_textures, "active", mesh, "uv_textures") + mesh = context.edit_object.data + layout.prop_search(mesh.uv_textures, "active", mesh, "uv_textures", text="") if ima: # layers @@ -374,7 +401,7 @@ class IMAGE_HT_header(bpy.types.Header): row = layout.row(align=True) if ima.type == 'COMPOSITE': row.operator("image.record_composite", icon='REC') - if ima.type == 'COMPOSITE' and ima.source in ('MOVIE', 'SEQUENCE'): + if ima.type == 'COMPOSITE' and ima.source in {'MOVIE', 'SEQUENCE'}: row.operator("image.play_composite", icon='PLAY') if show_uvedit or sima.use_image_paint: @@ -395,7 +422,6 @@ class IMAGE_PT_image_properties(bpy.types.Panel): layout = self.layout sima = context.space_data - # ima = sima.image iuser = sima.image_user layout.template_image(sima, "image", iuser) @@ -578,7 +604,9 @@ class IMAGE_PT_view_properties(bpy.types.Panel): if show_uvedit: col = layout.column() - col.prop(uvedit, "cursor_location") + col.label("Cursor Location") + row = col.row() + row.prop(uvedit, "cursor_location", text="") col = layout.column() col.label(text="UVs:") @@ -638,7 +666,7 @@ class IMAGE_PT_paint(bpy.types.Panel): col.prop(brush, "blend", text="Blend") - if brush.imagepaint_tool == 'CLONE': + if brush.image_tool == 'CLONE': col.separator() col.prop(brush, "clone_image", text="Image") col.prop(brush, "clone_alpha", text="Alpha") @@ -654,11 +682,9 @@ class IMAGE_PT_tools_brush_texture(BrushButtonsPanel, bpy.types.Panel): toolsettings = context.tool_settings.image_paint brush = toolsettings.brush -# tex_slot = brush.texture_slot - col = layout.column() - col.template_ID_preview(brush, "texture", new="texture.new", rows=3, cols=8) + col.prop(brush, "use_fixed_texture") class IMAGE_PT_tools_brush_tool(BrushButtonsPanel, bpy.types.Panel): @@ -672,13 +698,13 @@ class IMAGE_PT_tools_brush_tool(BrushButtonsPanel, bpy.types.Panel): col = layout.column(align=True) - col.prop(brush, "imagepaint_tool", expand=False, text="") + col.prop(brush, "image_tool", expand=False, text="") row = layout.row(align=True) row.prop(brush, "use_paint_sculpt", text="", icon='SCULPTMODE_HLT') row.prop(brush, "use_paint_vertex", text="", icon='VPAINT_HLT') row.prop(brush, "use_paint_weight", text="", icon='WPAINT_HLT') - row.prop(brush, "use_paint_texture", text="", icon='TPAINT_HLT') + row.prop(brush, "use_paint_image", text="", icon='TPAINT_HLT') class IMAGE_PT_paint_stroke(BrushButtonsPanel, bpy.types.Panel): @@ -725,13 +751,5 @@ class IMAGE_PT_paint_curve(BrushButtonsPanel, bpy.types.Panel): row.operator("brush.curve_preset", icon="LINCURVE", text="").shape = 'LINE' row.operator("brush.curve_preset", icon="NOCURVE", text="").shape = 'MAX' - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_info.py b/release/scripts/startup/bl_ui/space_info.py index 6c392cb9a3d..1fb2e5b735e 100644 --- a/release/scripts/ui/space_info.py +++ b/release/scripts/startup/bl_ui/space_info.py @@ -26,9 +26,7 @@ class INFO_HT_header(bpy.types.Header): def draw(self, context): layout = self.layout - wm = context.window_manager window = context.window - sinfo = context.space_data scene = context.scene rd = scene.render @@ -70,6 +68,7 @@ class INFO_HT_header(bpy.types.Header): # XXX: BEFORE RELEASE, MOVE FILE MENU OUT OF INFO!!! """ + sinfo = context.space_data row = layout.row(align=True) row.prop(sinfo, "show_report_debug", text="Debug") row.prop(sinfo, "show_report_info", text="Info") @@ -203,6 +202,7 @@ class INFO_MT_mesh_add(bpy.types.Menu): layout.separator() layout.operator("mesh.primitive_grid_add", icon='MESH_GRID', text="Grid") layout.operator("mesh.primitive_monkey_add", icon='MESH_MONKEY', text="Monkey") + layout.operator("mesh.primitive_torus_add", text="Torus", icon='MESH_TORUS') class INFO_MT_curve_add(bpy.types.Menu): @@ -327,8 +327,6 @@ class INFO_MT_render(bpy.types.Menu): def draw(self, context): layout = self.layout - # rd = context.scene.render - layout.operator("render.render", text="Render Image", icon='RENDER_STILL') layout.operator("render.render", text="Render Animation", icon='RENDER_ANIMATION').animation = True @@ -352,7 +350,7 @@ class INFO_MT_help(bpy.types.Menu): layout = self.layout layout.operator("wm.url_open", text="Manual", icon='HELP').url = 'http://wiki.blender.org/index.php/Doc:Manual' - layout.operator("wm.url_open", text="Release Log", icon='URL').url = 'http://www.blender.org/development/release-logs/blender-256-beta/' + layout.operator("wm.url_open", text="Release Log", icon='URL').url = 'http://www.blender.org/development/release-logs/blender-257/' layout.separator() @@ -363,16 +361,17 @@ class INFO_MT_help(bpy.types.Menu): layout.separator() layout.operator("wm.url_open", text="Report a Bug", icon='URL').url = 'http://projects.blender.org/tracker/?atid=498&group_id=9&func=browse' layout.separator() - layout.operator("wm.url_open", text="Python API Reference", icon='URL').url = "http://www.blender.org/documentation/blender_python_api_%s/contents.html" % "_".join(str(v) for v in bpy.app.version) + + layout.operator("wm.url_open", text="Python API Reference", icon='URL').url = bpy.types.WM_OT_doc_view._prefix layout.operator("help.operator_cheat_sheet", icon='TEXT') layout.operator("wm.sysinfo", icon='TEXT') layout.separator() - if sys.platform == "win32": - layout.operator("wm.toggle_console", icon='CONSOLE') + if sys.platform[:3] == "win": + layout.operator("wm.console_toggle", icon='CONSOLE') layout.separator() - layout.operator("anim.update_data_paths", text="FCurve/Driver 2.54 fix", icon='HELP') + layout.operator("anim.update_data_paths", text="FCurve/Driver Version fix", icon='HELP') layout.separator() - layout.operator("wm.splash") + layout.operator("wm.splash", icon='BLENDER') # Help operators @@ -402,13 +401,5 @@ class HELP_OT_operator_cheat_sheet(bpy.types.Operator): self.report({'INFO'}, "See OperatorList.txt textblock") return {'FINISHED'} - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_logic.py b/release/scripts/startup/bl_ui/space_logic.py index 54dde61e90a..7f7aba71a46 100644 --- a/release/scripts/ui/space_logic.py +++ b/release/scripts/startup/bl_ui/space_logic.py @@ -66,8 +66,6 @@ class LOGIC_HT_header(bpy.types.Header): def draw(self, context): layout = self.layout - st = context.space_data - row = layout.row(align=True) row.template_header() @@ -88,13 +86,5 @@ class LOGIC_MT_view(bpy.types.Menu): layout.operator("logic.properties", icon='MENU_PANEL') - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_nla.py b/release/scripts/startup/bl_ui/space_nla.py index 13afc97f913..717adb3baa8 100644 --- a/release/scripts/ui/space_nla.py +++ b/release/scripts/startup/bl_ui/space_nla.py @@ -25,7 +25,7 @@ class NLA_HT_header(bpy.types.Header): bl_space_type = 'NLA_EDITOR' def draw(self, context): - from space_dopesheet import dopesheet_filter + from bl_ui.space_dopesheet import dopesheet_filter layout = self.layout @@ -93,6 +93,10 @@ class NLA_MT_select(bpy.types.Menu): layout.operator("nla.select_border") layout.operator("nla.select_border", text="Border Axis Range").axis_range = True + layout.separator() + layout.operator("nla.select_leftright", text="Before Current Frame").mode = 'LEFT' + layout.operator("nla.select_leftright", text="After Current Frame").mode = 'RIGHT' + class NLA_MT_marker(bpy.types.Menu): bl_label = "Marker" @@ -184,15 +188,7 @@ class NLA_MT_edit_transform(bpy.types.Menu): layout.column() layout.operator("transform.translate", text="Grab/Move") layout.operator("transform.transform", text="Extend").mode = 'TIME_EXTEND' - layout.operator("transform.resize", text="Scale") - + layout.operator("transform.transform", text="Scale").mode = 'TIME_SCALE' -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_node.py b/release/scripts/startup/bl_ui/space_node.py index aaf14813ee5..fed1cc49c4c 100644 --- a/release/scripts/ui/space_node.py +++ b/release/scripts/startup/bl_ui/space_node.py @@ -156,9 +156,6 @@ class NODE_MT_node(bpy.types.Menu): layout.operator("node.preview_toggle") layout.operator("node.hide_socket_toggle") - # XXX - # layout.operator("node.rename") - layout.separator() layout.operator("node.show_cyclic_dependencies") @@ -195,13 +192,5 @@ class NODE_PT_properties(bpy.types.Panel): col.prop(snode, "backdrop_y", text="Y") col.operator("node.backimage_move", text="Move") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_outliner.py b/release/scripts/startup/bl_ui/space_outliner.py index 219dd1de117..1f196cbd191 100644 --- a/release/scripts/ui/space_outliner.py +++ b/release/scripts/startup/bl_ui/space_outliner.py @@ -72,7 +72,7 @@ class OUTLINER_MT_view(bpy.types.Menu): space = context.space_data col = layout.column() - if space.display_mode not in ('DATABLOCKS', 'USER_PREFERENCES', 'KEYMAPS'): + if space.display_mode not in {'DATABLOCKS', 'USER_PREFERENCES', 'KEYMAPS'}: col.prop(space, "show_restrict_columns") col.separator() col.operator("outliner.show_active") @@ -116,13 +116,5 @@ class OUTLINER_MT_edit_datablocks(bpy.types.Menu): col.operator("outliner.drivers_add_selected") col.operator("outliner.drivers_delete_selected") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_sequencer.py b/release/scripts/startup/bl_ui/space_sequencer.py index 3ae1e35aaf8..2079aef6402 100644 --- a/release/scripts/ui/space_sequencer.py +++ b/release/scripts/startup/bl_ui/space_sequencer.py @@ -101,34 +101,6 @@ class SEQUENCER_MT_view(bpy.types.Menu): layout.separator() - """ - uiBlock *block= uiBeginBlock(C, ar, "seq_viewmenu", UI_EMBOSSP); - short yco= 0, menuwidth=120; - - if (sseq->mainb == SEQ_DRAW_SEQUENCE) { - uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, - "Play Back Animation " - "in all Sequence Areas|Alt A", 0, yco-=20, - menuwidth, 19, NULL, 0.0, 0.0, 1, 1, ""); - } - else { - uiDefIconTextBut(block, BUTM, 1, ICON_MENU_PANEL, - "Grease Pencil...", 0, yco-=20, - menuwidth, 19, NULL, 0.0, 0.0, 1, 7, ""); - uiDefMenuSep(block); - - uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, - "Play Back Animation " - "in this window|Alt A", 0, yco-=20, - menuwidth, 19, NULL, 0.0, 0.0, 1, 1, ""); - } - uiDefIconTextBut(block, BUTM, 1, ICON_BLANK1, - "Play Back Animation in all " - "3D Views and Sequence Areas|Alt Shift A", - 0, yco-=20, - menuwidth, 19, NULL, 0.0, 0.0, 1, 2, ""); - - """ if (st.view_type == 'SEQUENCER') or (st.view_type == 'SEQUENCER_PREVIEW'): layout.operator("sequencer.view_all", text='View all Sequences') if (st.view_type == 'PREVIEW') or (st.view_type == 'SEQUENCER_PREVIEW'): @@ -353,7 +325,6 @@ class SEQUENCER_PT_edit(SequencerButtonsPanel, bpy.types.Panel): def draw(self, context): layout = self.layout scene = context.scene - render = context.scene.render frame_current = scene.frame_current strip = act_strip(context) @@ -416,11 +387,11 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel, bpy.types.Panel): if not strip: return False - return strip.type in ('ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER', + return strip.type in {'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP', 'PLUGIN', 'WIPE', 'GLOW', 'TRANSFORM', 'COLOR', 'SPEED', - 'MULTICAM') + 'MULTICAM'} def draw(self, context): layout = self.layout @@ -446,7 +417,7 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel, bpy.types.Panel): col = layout.column() col.prop(strip, "blur_width", slider=True) - if strip.transition_type in ('SINGLE', 'DOUBLE'): + if strip.transition_type in {'SINGLE', 'DOUBLE'}: col.prop(strip, "angle") elif strip.type == 'GLOW': @@ -492,7 +463,7 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel, bpy.types.Panel): col = layout.column(align=True) if strip.type == 'SPEED': col.prop(strip, "multiply_speed") - elif strip.type in ('CROSS', 'GAMMA_CROSS', 'PLUGIN', 'WIPE'): + elif strip.type in {'CROSS', 'GAMMA_CROSS', 'PLUGIN', 'WIPE'}: col.prop(strip, "use_default_fade", "Default fade") if not strip.use_default_fade: col.prop(strip, "effect_fader", text="Effect fader") @@ -554,12 +525,12 @@ class SEQUENCER_PT_input(SequencerButtonsPanel, bpy.types.Panel): if not strip: return False - return strip.type in ('MOVIE', 'IMAGE', 'SCENE', 'META', + return strip.type in {'MOVIE', 'IMAGE', 'SCENE', 'META', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP', 'PLUGIN', 'WIPE', 'GLOW', 'TRANSFORM', 'COLOR', - 'MULTICAM', 'SPEED') + 'MULTICAM', 'SPEED'} def draw(self, context): layout = self.layout @@ -704,12 +675,12 @@ class SEQUENCER_PT_filter(SequencerButtonsPanel, bpy.types.Panel): if not strip: return False - return strip.type in ('MOVIE', 'IMAGE', 'SCENE', 'META', + return strip.type in {'MOVIE', 'IMAGE', 'SCENE', 'META', 'ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER', 'CROSS', 'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP', 'PLUGIN', 'WIPE', 'GLOW', 'TRANSFORM', 'COLOR', - 'MULTICAM', 'SPEED') + 'MULTICAM', 'SPEED'} def draw(self, context): layout = self.layout @@ -766,7 +737,7 @@ class SEQUENCER_PT_proxy(SequencerButtonsPanel, bpy.types.Panel): if not strip: return False - return strip.type in ('MOVIE', 'IMAGE', 'SCENE', 'META', 'MULTICAM') + return strip.type in {'MOVIE', 'IMAGE', 'SCENE', 'META', 'MULTICAM'} def draw_header(self, context): strip = act_strip(context) @@ -829,13 +800,5 @@ class SEQUENCER_PT_view(SequencerButtonsPanel_Output, bpy.types.Panel): col.prop(st, "show_separate_color") col.prop(st, "proxy_render_size") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_text.py b/release/scripts/startup/bl_ui/space_text.py index df5967c9128..5b07e8dc37f 100644 --- a/release/scripts/ui/space_text.py +++ b/release/scripts/startup/bl_ui/space_text.py @@ -95,6 +95,11 @@ class TEXT_PT_properties(bpy.types.Panel): if text: flow.prop(text, "use_tabs_as_spaces") + flow.prop(st, "show_margin") + col = flow.column() + col.active = st.show_margin + col.prop(st, "margin_column") + class TEXT_PT_find(bpy.types.Panel): bl_space_type = 'TEXT_EDITOR' @@ -124,6 +129,7 @@ class TEXT_PT_find(bpy.types.Panel): layout.operator("text.mark_all") # settings + layout.prop(st, "use_match_case") row = layout.row() row.prop(st, "use_find_wrap", text="Wrap") row.prop(st, "use_find_all", text="All") @@ -293,13 +299,5 @@ class TEXT_MT_toolbox(bpy.types.Menu): layout.operator("text.run_script") - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_time.py b/release/scripts/startup/bl_ui/space_time.py index 046274abc89..7e62465d1ee 100644 --- a/release/scripts/ui/space_time.py +++ b/release/scripts/startup/bl_ui/space_time.py @@ -141,7 +141,6 @@ class TIME_MT_frame(bpy.types.Menu): def draw(self, context): layout = self.layout - # tools = context.tool_settings layout.operator("marker.add", text="Add Marker") layout.operator("marker.duplicate", text="Duplicate Marker") @@ -198,13 +197,5 @@ class TIME_MT_autokey(bpy.types.Menu): layout.prop_enum(tools, "auto_keying_mode", 'ADD_REPLACE_KEYS') layout.prop_enum(tools, "auto_keying_mode", 'REPLACE_KEYS') - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_userpref.py b/release/scripts/startup/bl_ui/space_userpref.py index 79bdff8ad93..bf396e98c79 100644 --- a/release/scripts/ui/space_userpref.py +++ b/release/scripts/startup/bl_ui/space_userpref.py @@ -19,7 +19,9 @@ # <pep8 compliant> import bpy import os -import shutil +import addon_utils + +from bpy.props import StringProperty, BoolProperty, EnumProperty def ui_items_general(col, context): @@ -118,6 +120,18 @@ class USERPREF_MT_interaction_presets(bpy.types.Menu): draw = bpy.types.Menu.draw_preset +class USERPREF_MT_appconfigs(bpy.types.Menu): + bl_label = "AppPresets" + preset_subdir = "keyconfig" + preset_operator = "wm.appconfig_activate" + + def draw(self, context): + props = self.layout.operator("wm.appconfig_default", text="Blender (default)") + + # now draw the presets + bpy.types.Menu.draw_preset(self, context) + + class USERPREF_MT_splash(bpy.types.Menu): bl_label = "Splash" @@ -132,7 +146,7 @@ class USERPREF_MT_splash(bpy.types.Menu): # text = bpy.path.display_name(context.window_manager.keyconfigs.active.name) # if not text: # text = "Blender (default)" - row.menu("USERPREF_MT_keyconfigs", text="Preset") + row.menu("USERPREF_MT_appconfigs", text="Preset") class USERPREF_PT_interface(bpy.types.Panel): @@ -635,7 +649,29 @@ class USERPREF_PT_theme(bpy.types.Panel): layout.separator() layout.separator() + elif theme.theme_area == 'BONE_COLOR_SETS': + col = split.column() + + for i, ui in enumerate(theme.bone_color_sets): + col.label(text="Color Set %d:" % (i + 1)) # i starts from 0 + + row = col.row() + + subsplit = row.split(percentage=0.95) + + padding = subsplit.split(percentage=0.15) + colsub = padding.column() + colsub = padding.column() + colsub.row().prop(ui, "normal") + colsub.row().prop(ui, "select") + colsub.row().prop(ui, "active") + + subsplit = row.split(percentage=0.85) + padding = subsplit.split(percentage=0.15) + colsub = padding.column() + colsub = padding.column() + colsub.row().prop(ui, "show_colored_constraints") else: self._theme_generic(split, getattr(theme, theme.theme_area.lower())) @@ -713,7 +749,7 @@ class USERPREF_PT_file(bpy.types.Panel): sub.active = paths.use_auto_save_temporary_files sub.prop(paths, "auto_save_time", text="Timer (mins)") -from space_userpref_keymap import InputKeyMapPanel +from bl_ui.space_userpref_keymap import InputKeyMapPanel class USERPREF_PT_input(InputKeyMapPanel): @@ -764,9 +800,9 @@ class USERPREF_PT_input(InputKeyMapPanel): sub.label(text="Zoom Style:") sub.row().prop(inputs, "view_zoom_method", text="") - if inputs.view_zoom_method == 'DOLLY': + if inputs.view_zoom_method in {'DOLLY', 'CONTINUE'}: sub.row().prop(inputs, "view_zoom_axis", expand=True) - sub.prop(inputs, "invert_mouse_wheel_zoom") + sub.prop(inputs, "invert_mouse_zoom") #sub.prop(inputs, "use_mouse_mmb_paste") @@ -795,7 +831,6 @@ class USERPREF_PT_input(InputKeyMapPanel): #start = time.time() userpref = context.user_preferences - wm = context.window_manager inputs = userpref.inputs @@ -830,8 +865,6 @@ class USERPREF_PT_addons(bpy.types.Panel): bl_region_type = 'WINDOW' bl_options = {'HIDE_HEADER'} - _addons_cats = None - _addons_sups = None _addons_fake_modules = {} @classmethod @@ -843,107 +876,6 @@ class USERPREF_PT_addons(bpy.types.Panel): def module_get(mod_name): return USERPREF_PT_addons._addons_fake_modules[mod_name] - @staticmethod - def _addon_list(): - import os - import sys - import time - - modules = [] - loaded_modules = set() - - # RELEASE SCRIPTS: official scripts distributed in Blender releases - paths = bpy.utils.script_paths("addons") - - # CONTRIB SCRIPTS: good for testing but not official scripts yet - # if folder addons_contrib/ exists, scripts in there will be loaded too - paths += bpy.utils.script_paths("addons_contrib") - - # EXTERN SCRIPTS: external projects scripts - # if folder addons_extern/ exists, scripts in there will be loaded too - paths += bpy.utils.script_paths("addons_extern") - - if bpy.app.debug: - t_main = time.time() - - # fake module importing - def fake_module(mod_name, mod_path, speedy=True): - if bpy.app.debug: - print("fake_module", mod_name, mod_path) - import ast - ModuleType = type(ast) - file_mod = open(mod_path, "r", encoding='UTF-8') - if speedy: - lines = [] - line_iter = iter(file_mod) - l = "" - while not l.startswith("bl_info"): - l = line_iter.readline() - if len(l) == 0: - break - while l.rstrip(): - lines.append(l) - l = line_iter.readline() - data = "".join(lines) - - else: - data = file_mod.read() - - file_mod.close() - - try: - ast_data = ast.parse(data, filename=mod_path) - except: - print("Syntax error 'ast.parse' can't read %r" % mod_path) - import traceback - traceback.print_exc() - ast_data = None - - body_info = None - - if ast_data: - for body in ast_data.body: - if body.__class__ == ast.Assign: - if len(body.targets) == 1: - if getattr(body.targets[0], "id", "") == "bl_info": - body_info = body - break - - if body_info: - mod = ModuleType(mod_name) - mod.bl_info = ast.literal_eval(body.value) - mod.__file__ = mod_path - mod.__time__ = os.path.getmtime(mod_path) - return mod - else: - return None - - modules_stale = set(USERPREF_PT_addons._addons_fake_modules.keys()) - - for path in paths: - for mod_name, mod_path in bpy.path.module_names(path): - modules_stale -= {mod_name} - mod = USERPREF_PT_addons._addons_fake_modules.get(mod_name) - if mod: - if mod.__time__ != os.path.getmtime(mod_path): - print("reloading addon:", mod_name, mod.__time__, os.path.getmtime(mod_path), mod_path) - del USERPREF_PT_addons._addons_fake_modules[mod_name] - mod = None - - if mod is None: - mod = fake_module(mod_name, mod_path) - if mod: - USERPREF_PT_addons._addons_fake_modules[mod_name] = mod - - # just incase we get stale modules, not likely - for mod_stale in modules_stale: - del USERPREF_PT_addons._addons_fake_modules[mod_stale] - del modules_stale - - mod_list = list(USERPREF_PT_addons._addons_fake_modules.values()) - mod_list.sort(key=lambda mod: (mod.bl_info['category'], mod.bl_info['name'])) - return mod_list - def draw(self, context): layout = self.layout @@ -951,22 +883,7 @@ class USERPREF_PT_addons(bpy.types.Panel): used_ext = {ext.module for ext in userpref.addons} # collect the categories that can be filtered on - addons = [(mod, addon_info_get(mod)) for mod in self._addon_list()] - - cats = {info["category"] for mod, info in addons} - cats.discard("") - - if USERPREF_PT_addons._addons_cats != cats: - bpy.types.WindowManager.addon_filter = bpy.props.EnumProperty(items=[(cat, cat, "") for cat in ["All", "Enabled", "Disabled"] + sorted(cats)], name="Category", description="Filter add-ons by category") - bpy.types.WindowManager.addon_search = bpy.props.StringProperty(name="Search", description="Search within the selected filter") - USERPREF_PT_addons._addons_cats = cats - - sups = {info["support"] for mod, info in addons} - sups.discard("") - - if USERPREF_PT_addons._addons_sups != sups: - bpy.types.WindowManager.addon_support = bpy.props.EnumProperty(items=[(sup, sup.title(), "") for sup in reversed(sorted(sups))], name="Support", description="Display support level", default={'OFFICIAL', 'COMMUNITY'}, options={'ENUM_FLAG'}) - USERPREF_PT_addons._addons_sups = sups + addons = [(mod, addon_utils.module_bl_info(mod)) for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)] split = layout.split(percentage=0.2) col = split.column() @@ -1070,7 +987,7 @@ class USERPREF_PT_addons(bpy.types.Panel): module_names = {mod.__name__ for mod, info in addons} missing_modules = {ext for ext in used_ext if ext not in module_names} - if missing_modules and filter in ("All", "Enabled"): + if missing_modules and filter in {"All", "Enabled"}: col.column().separator() col.column().label(text="Missing script files") @@ -1088,29 +1005,6 @@ class USERPREF_PT_addons(bpy.types.Panel): row.operator("wm.addon_disable", icon='CHECKBOX_HLT', text="", emboss=False).module = module_name -from bpy.props import * - - -def addon_info_get(mod, info_basis={"name": "", "author": "", "version": (), "blender": (), "api": 0, "location": "", "description": "", "wiki_url": "", "tracker_url": "", "support": 'COMMUNITY', "category": "", "warning": "", "show_expanded": False}): - addon_info = getattr(mod, "bl_info", {}) - - # avoid re-initializing - if "_init" in addon_info: - return addon_info - - if not addon_info: - mod.bl_info = addon_info - - for key, value in info_basis.items(): - addon_info.setdefault(key, value) - - if not addon_info["name"]: - addon_info["name"] = mod.__name__ - - addon_info["_init"] = None - return addon_info - - class WM_OT_addon_enable(bpy.types.Operator): "Enable an addon" bl_idname = "wm.addon_enable" @@ -1119,11 +1013,11 @@ class WM_OT_addon_enable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to enable") def execute(self, context): - mod = bpy.utils.addon_enable(self.module) + mod = addon_utils.enable(self.module) if mod: # check if add-on is written for current blender version, or raise a warning - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) if info.get("blender", (0, 0, 0)) > bpy.app.version: self.report("WARNING','This script was written for a newer version of Blender and might not function (correctly).\nThe script is enabled though.") @@ -1140,7 +1034,7 @@ class WM_OT_addon_disable(bpy.types.Operator): module = StringProperty(name="Module", description="Module name of the addon to disable") def execute(self, context): - bpy.utils.addon_disable(self.module) + addon_utils.disable(self.module) return {'FINISHED'} @@ -1150,6 +1044,10 @@ class WM_OT_addon_install(bpy.types.Operator): bl_label = "Install Add-On..." overwrite = BoolProperty(name="Overwrite", description="Remove existing addons with the same ID", default=True) + target = EnumProperty( + name="Target Path", + items=(('DEFAULT', "Default", ""), + ('PREFS', "User Prefs", ""))) filepath = StringProperty(name="File Path", description="File path to write file to") filter_folder = BoolProperty(name="Filter folders", description="", default=True, options={'HIDDEN'}) @@ -1172,16 +1070,41 @@ class WM_OT_addon_install(bpy.types.Operator): def execute(self, context): import traceback import zipfile + import shutil + pyfile = self.filepath - # dont use bpy.utils.script_paths("addons") because we may not be able to write to it. - path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) + if self.target == 'DEFAULT': + # dont use bpy.utils.script_paths("addons") because we may not be able to write to it. + path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True) + else: + path_addons = bpy.context.user_preferences.filepaths.script_directory + if path_addons: + path_addons = os.path.join(path_addons, "addons") if not path_addons: - self.report({'WARNING'}, "Failed to get addons path\n") + self.report({'ERROR'}, "Failed to get addons path") return {'CANCELLED'} - contents = set(os.listdir(path_addons)) + # create dir is if missing. + if not os.path.exists(path_addons): + os.makedirs(path_addons) + + # Check if we are installing from a target path, + # doing so causes 2+ addons of same name or when the same from/to + # location is used, removal of the file! + addon_path = "" + pyfile_dir = os.path.dirname(pyfile) + for addon_path in addon_utils.paths(): + if os.path.samefile(pyfile_dir, addon_path): + self.report({'ERROR'}, "Source file is in the addon search path: %r" % addon_path) + return {'CANCELLED'} + del addon_path + del pyfile_dir + # done checking for exceptional case + + addon_files_old = set(os.listdir(path_addons)) + addons_old = {mod.__name__ for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)} #check to see if the file is in compressed format (.zip) if zipfile.is_zipfile(pyfile): @@ -1203,6 +1126,12 @@ class WM_OT_addon_install(bpy.types.Operator): try: # extract the file to "addons" file_to_extract.extractall(path_addons) + + # zip files can create this dir with metadata, don't need it + macosx_dir = os.path.join(path_addons, '__MACOSX') + if os.path.isdir(macosx_dir): + shutil.rmtree(macosx_dir) + except: traceback.print_exc() return {'CANCELLED'} @@ -1224,23 +1153,28 @@ class WM_OT_addon_install(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} + addons_new = {mod.__name__ for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules)} - addons_old + addons_new.discard("modules") + # disable any addons we may have enabled previously and removed. # this is unlikely but do just incase. bug [#23978] - addons_new = set(os.listdir(path_addons)) - contents for new_addon in addons_new: - bpy.utils.addon_disable(os.path.splitext(new_addon)[0]) + addon_utils.disable(new_addon) # possible the zip contains multiple addons, we could disallow this # but for now just use the first - for mod in USERPREF_PT_addons._addon_list(): + for mod in addon_utils.modules(USERPREF_PT_addons._addons_fake_modules): if mod.__name__ in addons_new: - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) # show the newly installed addon. context.window_manager.addon_filter = 'All' context.window_manager.addon_search = info["name"] break + # incase a new module path was created to install this addon. + bpy.utils.refresh_script_paths() + # TODO, should not be a warning. # self.report({'WARNING'}, "File installed to '%s'\n" % path_dest) return {'FINISHED'} @@ -1270,17 +1204,9 @@ class WM_OT_addon_expand(bpy.types.Operator): traceback.print_exc() return {'CANCELLED'} - info = addon_info_get(mod) + info = addon_utils.module_bl_info(mod) info["show_expanded"] = not info["show_expanded"] return {'FINISHED'} - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_userpref_keymap.py b/release/scripts/startup/bl_ui/space_userpref_keymap.py index a78ef1be36f..378fe231091 100644 --- a/release/scripts/ui/space_userpref_keymap.py +++ b/release/scripts/startup/bl_ui/space_userpref_keymap.py @@ -206,7 +206,7 @@ class InputKeyMapPanel(bpy.types.Panel): # Key Map items if km.show_expanded_items: - for kmi in km.items: + for kmi in km.keymap_items: self.draw_kmi(display_keymaps, kc, km, kmi, col, level + 1) # "Add New" at end of keymap item list @@ -292,7 +292,7 @@ class InputKeyMapPanel(bpy.types.Panel): box.enabled = km.is_user_defined - if map_type not in ('TEXTINPUT', 'TIMER'): + if map_type not in {'TEXTINPUT', 'TIMER'}: split = box.split(percentage=0.4) sub = split.row() @@ -339,7 +339,7 @@ class InputKeyMapPanel(bpy.types.Panel): km = km.active() layout.context_pointer_set("keymap", km) - filtered_items = [kmi for kmi in km.items if filter_text in kmi.name.lower()] + filtered_items = [kmi for kmi in km.keymap_items if filter_text in kmi.name.lower()] if len(filtered_items) != 0: col = layout.column() @@ -404,7 +404,7 @@ class InputKeyMapPanel(bpy.types.Panel): self.draw_hierarchy(display_keymaps, col) -from bpy.props import * +from bpy.props import StringProperty, BoolProperty, IntProperty def export_properties(prefix, properties, lines=None): @@ -433,9 +433,9 @@ class WM_OT_keyconfig_test(bpy.types.Operator): def kmistr(kmi): if km.is_modal: - s = ["kmi = km.items.new_modal(\'%s\', \'%s\', \'%s\'" % (kmi.propvalue, kmi.type, kmi.value)] + s = ["kmi = km.keymap_items.new_modal(\'%s\', \'%s\', \'%s\'" % (kmi.propvalue, kmi.type, kmi.value)] else: - s = ["kmi = km.items.new(\'%s\', \'%s\', \'%s\'" % (kmi.idname, kmi.type, kmi.value)] + s = ["kmi = km.keymap_items.new(\'%s\', \'%s\', \'%s\'" % (kmi.idname, kmi.type, kmi.value)] if kmi.any: s.append(", any=True") @@ -468,7 +468,7 @@ class WM_OT_keyconfig_test(bpy.types.Operator): km = km.active() if src: - for item in km.items: + for item in km.keymap_items: if src.compare(item): print("===========") print(parent.name) @@ -481,15 +481,15 @@ class WM_OT_keyconfig_test(bpy.types.Operator): if self.testEntry(kc, child, src, parent): result = True else: - for i in range(len(km.items)): - src = km.items[i] + for i in range(len(km.keymap_items)): + src = km.keymap_items[i] for child in children: if self.testEntry(kc, child, src, km): result = True - for j in range(len(km.items) - i - 1): - item = km.items[j + i + 1] + for j in range(len(km.keymap_items) - i - 1): + item = km.keymap_items[j + i + 1] if src.compare(item): print("===========") print(km.name) @@ -555,13 +555,7 @@ class WM_OT_keyconfig_import(bpy.types.Operator): config_name = basename(self.filepath) - path = bpy.utils.preset_paths("keyconfig")[0] # we need some way to tell the user and system preset path - print(path) - - # create config folder if needed - if not os.path.exists(path): - os.mkdir(path) - + path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True) path = os.path.join(path, config_name) if self.keep_original: @@ -633,11 +627,11 @@ class WM_OT_keyconfig_export(bpy.types.Operator): f.write("# Map %s\n" % km.name) f.write("km = kc.keymaps.new('%s', space_type='%s', region_type='%s', modal=%s)\n\n" % (km.name, km.space_type, km.region_type, km.is_modal)) - for kmi in km.items: + for kmi in km.keymap_items: if km.is_modal: - f.write("kmi = km.items.new_modal('%s', '%s', '%s'" % (kmi.propvalue, kmi.type, kmi.value)) + f.write("kmi = km.keymap_items.new_modal('%s', '%s', '%s'" % (kmi.propvalue, kmi.type, kmi.value)) else: - f.write("kmi = km.items.new('%s', '%s', '%s'" % (kmi.idname, kmi.type, kmi.value)) + f.write("kmi = km.keymap_items.new('%s', '%s', '%s'" % (kmi.idname, kmi.type, kmi.value)) if kmi.any: f.write(", any=True") else: @@ -676,7 +670,6 @@ class WM_OT_keymap_edit(bpy.types.Operator): bl_label = "Edit Key Map" def execute(self, context): - wm = context.window_manager km = context.keymap km.copy_to_user() return {'FINISHED'} @@ -711,13 +704,12 @@ class WM_OT_keyitem_restore(bpy.types.Operator): @classmethod def poll(cls, context): - km = context.keymap - return km.is_user_defined + keymap = getattr(context, "keymap", None) + return keymap and keymap.is_user_defined def execute(self, context): - wm = context.window_manager km = context.keymap - kmi = km.items.from_id(self.item_id) + kmi = km.keymap_items.from_id(self.item_id) if not kmi.is_user_defined: km.restore_item_to_default(kmi) @@ -736,9 +728,9 @@ class WM_OT_keyitem_add(bpy.types.Operator): kc = wm.keyconfigs.default if km.is_modal: - km.items.new_modal("", 'A', 'PRESS') # kmi + km.keymap_items.new_modal("", 'A', 'PRESS') # kmi else: - km.items.new("none", 'A', 'PRESS') # kmi + km.keymap_items.new("none", 'A', 'PRESS') # kmi # clear filter and expand keymap so we can see the newly added item if context.space_data.filter_text != "": @@ -758,13 +750,12 @@ class WM_OT_keyitem_remove(bpy.types.Operator): @classmethod def poll(cls, context): - km = context.keymap - return km.is_user_defined + return hasattr(context, "keymap") and context.keymap.is_user_defined def execute(self, context): km = context.keymap - kmi = km.items.from_id(self.item_id) - km.items.remove(kmi) + kmi = km.keymap_items.from_id(self.item_id) + km.keymap_items.remove(kmi) return {'FINISHED'} @@ -776,22 +767,14 @@ class WM_OT_keyconfig_remove(bpy.types.Operator): @classmethod def poll(cls, context): wm = context.window_manager - return wm.keyconfigs.active.is_user_defined + keyconf = wm.keyconfigs.active + return keyconf and keyconf.is_user_defined def execute(self, context): - import sys wm = context.window_manager keyconfig = wm.keyconfigs.active wm.keyconfigs.remove(keyconfig) return {'FINISHED'} - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/ui/space_view3d.py b/release/scripts/startup/bl_ui/space_view3d.py index 162322a8720..7d31bc39b0a 100644 --- a/release/scripts/ui/space_view3d.py +++ b/release/scripts/startup/bl_ui/space_view3d.py @@ -42,13 +42,13 @@ class VIEW3D_HT_header(bpy.types.Header): sub.menu("VIEW3D_MT_view") # Select Menu - if mode_string not in ('EDIT_TEXT', 'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'): + if mode_string not in {'EDIT_TEXT', 'SCULPT', 'PAINT_WEIGHT', 'PAINT_VERTEX', 'PAINT_TEXTURE'}: sub.menu("VIEW3D_MT_select_%s" % mode_string.lower()) if edit_object: sub.menu("VIEW3D_MT_edit_%s" % edit_object.type.lower()) elif obj: - if mode_string not in ('PAINT_TEXTURE'): + if mode_string not in {'PAINT_TEXTURE'}: sub.menu("VIEW3D_MT_%s" % mode_string.lower()) else: sub.menu("VIEW3D_MT_object") @@ -71,11 +71,11 @@ class VIEW3D_HT_header(bpy.types.Header): row.prop(toolsettings.particle_edit, "select_mode", text="", expand=True, toggle=True) # Occlude geometry - if view.viewport_shade in ('SOLID', 'SHADED', 'TEXTURED') and (obj.mode == 'PARTICLE_EDIT' or (obj.mode == 'EDIT' and obj.type == 'MESH')): + if view.viewport_shade in {'SOLID', 'SHADED', 'TEXTURED'} and (obj.mode == 'PARTICLE_EDIT' or (obj.mode == 'EDIT' and obj.type == 'MESH')): row.prop(view, "use_occlude_geometry", text="") # Proportional editing - if obj.mode in ('EDIT', 'PARTICLE_EDIT'): + if obj.mode in {'EDIT', 'PARTICLE_EDIT'}: row = layout.row(align=True) row.prop(toolsettings, "proportional_edit", text="", icon_only=True) if toolsettings.proportional_edit != 'DISABLED': @@ -160,7 +160,7 @@ class VIEW3D_MT_transform(bpy.types.Menu): layout.separator() obj = context.object - if obj.type == 'ARMATURE' and obj.mode in ('EDIT', 'POSE') and obj.data.draw_type in ('BBONE', 'ENVELOPE'): + if obj.type == 'ARMATURE' and obj.mode in {'EDIT', 'POSE'} and obj.data.draw_type in {'BBONE', 'ENVELOPE'}: layout.operator("transform.transform", text="Scale Envelope/BBone").mode = 'BONE_SIZE' if context.edit_object and context.edit_object.type == 'ARMATURE': @@ -177,6 +177,11 @@ class VIEW3D_MT_transform(bpy.types.Menu): layout.operator("object.origin_set", text="Origin to Geometry").type = 'ORIGIN_GEOMETRY' layout.operator("object.origin_set", text="Origin to 3D Cursor").type = 'ORIGIN_CURSOR' + layout.separator() + + layout.operator("object.randomize_transform") + layout.operator("object.align") + class VIEW3D_MT_mirror(bpy.types.Menu): bl_label = "Mirror" @@ -240,9 +245,21 @@ class VIEW3D_MT_uv_map(bpy.types.Menu): layout = self.layout layout.operator("uv.unwrap") + + layout.operator_context = 'INVOKE_DEFAULT' + layout.operator("uv.smart_project") + layout.operator("uv.lightmap_pack") + layout.operator("uv.follow_active_quads") + + layout.separator() + + layout.operator_context = 'EXEC_DEFAULT' layout.operator("uv.cube_project") layout.operator("uv.cylinder_project") layout.operator("uv.sphere_project") + + layout.separator() + layout.operator("uv.project_from_view") layout.operator("uv.project_from_view", text="Project from View (Bounds)").scale_to_bounds = True @@ -250,6 +267,7 @@ class VIEW3D_MT_uv_map(bpy.types.Menu): layout.operator("uv.reset") + # ********** View menus ********** @@ -317,16 +335,17 @@ class VIEW3D_MT_view_navigation(bpy.types.Menu): def draw(self, context): layout = self.layout - layout.operator_enums("view3d.view_orbit", "type") + layout.operator_enum("view3d.view_orbit", "type") layout.separator() - layout.operator_enums("view3d.view_pan", "type") + layout.operator_enum("view3d.view_pan", "type") layout.separator() layout.operator("view3d.zoom", text="Zoom In").delta = 1 layout.operator("view3d.zoom", text="Zoom Out").delta = -1 + layout.operator("view3d.zoom_camera_1_to_1", text="Zoom Camera 1:1") layout.separator() @@ -425,6 +444,7 @@ class VIEW3D_MT_select_pose(bpy.types.Menu): layout.operator("pose.select_all", text="Select/Deselect All") layout.operator("pose.select_inverse", text="Inverse") + layout.operator("pose.select_flip_active", text="Flip Active") layout.operator("pose.select_constraint_target", text="Constraint Target") layout.operator("pose.select_linked", text="Linked") @@ -647,10 +667,11 @@ class VIEW3D_MT_select_face(bpy.types.Menu): # XXX no matching enum bl_label = "Select" def draw(self, context): - layout = self.layout + # layout = self.layout # TODO # see view3d_select_faceselmenu + pass # ********** Object menu ********** @@ -713,7 +734,6 @@ class VIEW3D_MT_object(bpy.types.Menu): class VIEW3D_MT_object_animation(bpy.types.Menu): - bl_context = "objectmode" bl_label = "Animation" def draw(self, context): @@ -763,7 +783,7 @@ class VIEW3D_MT_object_specials(bpy.types.Menu): props.data_path_item = "data.dof_distance" props.input_scale = 0.02 - if obj.type in ('CURVE', 'FONT'): + if obj.type in {'CURVE', 'FONT'}: layout.operator_context = 'INVOKE_REGION_WIN' props = layout.operator("wm.context_modal_mouse", text="Extrude Size") @@ -791,7 +811,7 @@ class VIEW3D_MT_object_specials(bpy.types.Menu): props.data_path_iter = "selected_editable_objects" props.data_path_item = "data.energy" - if obj.data.type in ('SPOT', 'AREA', 'POINT'): + if obj.data.type in {'SPOT', 'AREA', 'POINT'}: props = layout.operator("wm.context_modal_mouse", text="Falloff Distance") props.data_path_iter = "selected_editable_objects" props.data_path_item = "data.distance" @@ -831,10 +851,15 @@ class VIEW3D_MT_object_apply(bpy.types.Menu): def draw(self, context): layout = self.layout - layout.operator("object.location_apply", text="Location") - layout.operator("object.rotation_apply", text="Rotation") - layout.operator("object.scale_apply", text="Scale") + layout.operator("object.transform_apply", text="Location").location = True + layout.operator("object.transform_apply", text="Rotation").rotation = True + layout.operator("object.transform_apply", text="Scale").scale = True + props = layout.operator("object.transform_apply", text="Rotation & Scale") + props.scale = True + props.rotation = True + layout.separator() + layout.operator("object.visual_transform_apply", text="Visual Transform") layout.operator("object.duplicates_make_real") @@ -932,7 +957,7 @@ class VIEW3D_MT_make_links(bpy.types.Menu): layout.operator_menu_enum("object.make_links_scene", "scene", text="Objects to Scene...") layout.operator_menu_enum("marker.make_links_scene", "scene", text="Markers to Scene...") - layout.operator_enums("object.make_links_data", "type") # inline + layout.operator_enum("object.make_links_data", "type") # inline class VIEW3D_MT_object_game(bpy.types.Menu): @@ -1038,6 +1063,10 @@ class VIEW3D_MT_paint_weight(bpy.types.Menu): layout.operator("object.vertex_group_clean", text="Clean") layout.operator("object.vertex_group_levels", text="Levels") + layout.separator() + + layout.operator("paint.weight_set") + # ********** Sculpt menu ********** @@ -1070,12 +1099,9 @@ class VIEW3D_MT_sculpt(bpy.types.Menu): sculpt_tool = brush.sculpt_tool if sculpt_tool != 'GRAB': - layout.prop(brush, "use_airbrush") - - if sculpt_tool != 'LAYER': - layout.prop(brush, "use_anchor") + layout.prop_menu_enum(brush, "stroke_method") - if sculpt_tool in ('DRAW', 'PINCH', 'INFLATE', 'LAYER', 'CLAY'): + if sculpt_tool in {'DRAW', 'PINCH', 'INFLATE', 'LAYER', 'CLAY'}: layout.prop_menu_enum(brush, "direction") if sculpt_tool == 'LAYER': @@ -1154,31 +1180,26 @@ class VIEW3D_MT_pose(bpy.types.Menu): def draw(self, context): layout = self.layout - arm = context.active_object.data - layout.operator("ed.undo") layout.operator("ed.redo") layout.separator() layout.menu("VIEW3D_MT_transform") - layout.menu("VIEW3D_MT_snap") layout.menu("VIEW3D_MT_pose_transform") + layout.menu("VIEW3D_MT_pose_apply") - layout.separator() - - layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...") - layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframe...") - layout.operator("anim.keying_set_active_set", text="Change Keying Set...") + layout.menu("VIEW3D_MT_snap") layout.separator() - layout.operator("pose.relax") + layout.menu("VIEW3D_MT_object_animation") layout.separator() - layout.menu("VIEW3D_MT_pose_apply") + layout.menu("VIEW3D_MT_pose_slide") + layout.menu("VIEW3D_MT_pose_propagate") layout.separator() @@ -1188,7 +1209,7 @@ class VIEW3D_MT_pose(bpy.types.Menu): layout.separator() - layout.menu("VIEW3D_MT_pose_pose") + layout.menu("VIEW3D_MT_pose_library") layout.menu("VIEW3D_MT_pose_motion") layout.menu("VIEW3D_MT_pose_group") @@ -1236,7 +1257,36 @@ class VIEW3D_MT_pose_transform(bpy.types.Menu): layout.label(text="Origin") -class VIEW3D_MT_pose_pose(bpy.types.Menu): +class VIEW3D_MT_pose_slide(bpy.types.Menu): + bl_label = "In-Betweens" + + def draw(self, context): + layout = self.layout + + layout.operator("pose.push") + layout.operator("pose.relax") + layout.operator("pose.breakdown") + + +class VIEW3D_MT_pose_propagate(bpy.types.Menu): + bl_label = "Propagate" + + def draw(self, context): + layout = self.layout + + layout.operator("pose.propagate") + + layout.separator() + + layout.operator("pose.propagate", text="To Next Keyframe").mode = 'NEXT_KEY' + layout.operator("pose.propagate", text="To Last Keyframe (Make Cyclic)").mode = 'LAST_KEY' + + layout.separator() + + layout.operator("pose.propagate", text="On Selected Markers").mode = 'SELECTED_MARKERS' + + +class VIEW3D_MT_pose_library(bpy.types.Menu): bl_label = "Pose Library" def draw(self, context): @@ -1450,7 +1500,7 @@ class VIEW3D_OT_edit_mesh_extrude_individual_move(bpy.types.Operator): totface = mesh.total_face_sel totedge = mesh.total_edge_sel - totvert = mesh.total_vert_sel + # totvert = mesh.total_vert_sel if select_mode[2] and totface == 1: bpy.ops.mesh.extrude_region_move('INVOKE_REGION_WIN', TRANSFORM_OT_translate={"constraint_orientation": 'NORMAL', "constraint_axis": (False, False, True)}) @@ -1478,7 +1528,7 @@ class VIEW3D_OT_edit_mesh_extrude_move(bpy.types.Operator): totface = mesh.total_face_sel totedge = mesh.total_edge_sel - totvert = mesh.total_vert_sel + # totvert = mesh.total_vert_sel if totface >= 1: bpy.ops.mesh.extrude_region_move('INVOKE_REGION_WIN', TRANSFORM_OT_translate={"constraint_orientation": 'NORMAL', "constraint_axis": (False, False, True)}) @@ -1510,6 +1560,8 @@ class VIEW3D_MT_edit_mesh_vertices(bpy.types.Menu): layout.operator("mesh.vertices_smooth") layout.operator("mesh.remove_doubles") + layout.operator("mesh.vertices_sort") + layout.operator("mesh.vertices_randomize") layout.operator("mesh.select_vertex_path") @@ -1938,8 +1990,7 @@ class VIEW3D_MT_edit_armature_roll(bpy.types.Menu): def draw(self, context): layout = self.layout - layout.operator("armature.calculate_roll", text="Recalculate with Z-Axis Up").type = 'GLOBALUP' - layout.operator("armature.calculate_roll", text="Recalculate with Z-Axis to Cursor").type = 'CURSOR' + layout.operator_menu_enum("armature.calculate_roll", "type") layout.separator() @@ -1962,7 +2013,6 @@ class VIEW3D_PT_view3d_properties(bpy.types.Panel): layout = self.layout view = context.space_data - scene = context.scene col = layout.column() col.active = view.region_3d.view_perspective != 'CAMERA' @@ -2004,7 +2054,7 @@ class VIEW3D_PT_view3d_name(bpy.types.Panel): row.label(text="", icon='OBJECT_DATA') row.prop(ob, "name", text="") - if ob.type == 'ARMATURE' and ob.mode in ('EDIT', 'POSE'): + if ob.type == 'ARMATURE' and ob.mode in {'EDIT', 'POSE'}: bone = context.active_bone if bone: row = layout.row() @@ -2314,3 +2364,6 @@ def unregister(): if __name__ == "__main__": register() + +if __name__ == "__main__": # only for live edit. + bpy.utils.register_module(__name__) diff --git a/release/scripts/ui/space_view3d_toolbar.py b/release/scripts/startup/bl_ui/space_view3d_toolbar.py index 163e02817a4..a6db6fbdde8 100644 --- a/release/scripts/ui/space_view3d_toolbar.py +++ b/release/scripts/startup/bl_ui/space_view3d_toolbar.py @@ -122,7 +122,6 @@ class VIEW3D_PT_tools_meshedit(View3DPanel, bpy.types.Panel): col = layout.column(align=True) col.label(text="Deform:") col.operator("transform.edge_slide") - col.operator("mesh.rip_move") col.operator("mesh.noise") col.operator("mesh.vertices_smooth") @@ -173,11 +172,18 @@ class VIEW3D_PT_tools_meshedit_options(View3DPanel, bpy.types.Panel): ob = context.active_object if ob: - mesh = context.active_object.data + mesh = ob.data col = layout.column(align=True) col.prop(mesh, "use_mirror_x") - col.prop(mesh, "use_mirror_topology") - col.prop(context.tool_settings, "edge_path_mode") + sub = col.column() + sub.active = ob.data.use_mirror_x + sub.prop(mesh, "use_mirror_topology") + + ts = context.tool_settings + + col.label("Edge Select Mode") + col.prop(ts, "edge_path_mode", text="") + col.prop(context.tool_settings, "edge_path_live_unwrap") # ********** default tools for editmode_curve **************** @@ -324,12 +330,9 @@ class VIEW3D_PT_tools_armatureedit_options(View3DPanel, bpy.types.Panel): bl_label = "Armature Options" def draw(self, context): - layout = self.layout - arm = context.active_object.data - col = layout.column(align=True) - col.prop(arm, "use_mirror_x") + self.layout.prop(arm, "use_mirror_x") # ********** default tools for editmode_mball **************** @@ -424,12 +427,9 @@ class VIEW3D_PT_tools_posemode_options(View3DPanel, bpy.types.Panel): bl_label = "Pose Options" def draw(self, context): - layout = self.layout - arm = context.active_object.data - col = layout.column(align=True) - col.prop(arm, "use_auto_ik") + self.layout.prop(arm, "use_auto_ik") # ********** default tools for paint modes **************** @@ -448,7 +448,7 @@ class PaintPanel(): return ts.vertex_paint elif context.weight_paint_object: return ts.weight_paint - elif context.texture_paint_object: + elif context.image_paint_object: return ts.image_paint elif context.particle_edit_object: return ts.particle_edit @@ -515,16 +515,16 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): row.prop(brush, "unprojected_radius", text="Radius", slider=True) else: row.prop(brush, "use_locked_size", toggle=True, text="", icon='UNLOCKED') - row.prop(brush, "size", text="Radius", slider=True) + row.prop(brush, "size", slider=True) row.prop(brush, "use_pressure_size", toggle=True, text="") - if brush.sculpt_tool not in ('SNAKE_HOOK', 'GRAB', 'ROTATE'): + if brush.sculpt_tool not in {'SNAKE_HOOK', 'GRAB', 'ROTATE'}: col.separator() row = col.row(align=True) - if brush.use_space and brush.sculpt_tool not in ('SMOOTH'): + if brush.use_space and brush.sculpt_tool not in {'SMOOTH'}: if brush.use_space_atten: row.prop(brush, "use_space_atten", toggle=True, text="", icon='LOCKED') else: @@ -533,26 +533,26 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): row.prop(brush, "strength", text="Strength", slider=True) row.prop(brush, "use_pressure_strength", text="") - if brush.sculpt_tool not in ('SMOOTH'): + if brush.sculpt_tool not in {'SMOOTH'}: col.separator() row = col.row(align=True) row.prop(brush, "auto_smooth_factor", slider=True) row.prop(brush, "use_inverse_smooth_pressure", toggle=True, text="") - if brush.sculpt_tool in ('GRAB', 'SNAKE_HOOK'): + if brush.sculpt_tool in {'GRAB', 'SNAKE_HOOK'}: col.separator() row = col.row(align=True) row.prop(brush, "normal_weight", slider=True) - if brush.sculpt_tool in ('CREASE', 'BLOB'): + if brush.sculpt_tool in {'CREASE', 'BLOB'}: col.separator() row = col.row(align=True) row.prop(brush, "crease_pinch_factor", slider=True, text="Pinch") - if brush.sculpt_tool not in ('PINCH', 'INFLATE', 'SMOOTH'): + if brush.sculpt_tool not in {'PINCH', 'INFLATE', 'SMOOTH'}: row = col.row(align=True) col.separator() @@ -564,8 +564,8 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): row.prop(brush, "sculpt_plane", text="") - #if brush.sculpt_tool in ('CLAY', 'CLAY_TUBES', 'FLATTEN', 'FILL', 'SCRAPE'): - if brush.sculpt_tool in ('CLAY', 'FLATTEN', 'FILL', 'SCRAPE'): + #if brush.sculpt_tool in {'CLAY', 'CLAY_TUBES', 'FLATTEN', 'FILL', 'SCRAPE'}: + if brush.sculpt_tool in {'CLAY', 'FLATTEN', 'FILL', 'SCRAPE'}: row = col.row(align=True) row.prop(brush, "plane_offset", slider=True) row.prop(brush, "use_offset_pressure", text="") @@ -578,6 +578,10 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): row.active = brush.use_plane_trim row.prop(brush, "plane_trim", slider=True, text="Distance") + if brush.sculpt_tool == 'LAYER': + row = col.row() + row.prop(brush, "height", slider=True, text="Height") + col.separator() row = col.row() @@ -586,7 +590,7 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): col.separator() col.row().prop(brush, "direction", expand=True) - if brush.sculpt_tool in ('DRAW', 'CREASE', 'BLOB', 'INFLATE', 'LAYER', 'CLAY'): + if brush.sculpt_tool in {'DRAW', 'CREASE', 'BLOB', 'INFLATE', 'LAYER', 'CLAY'}: col.separator() col.prop(brush, "use_accumulate") @@ -608,13 +612,13 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): # Texture Paint Mode # - elif context.texture_paint_object and brush: + elif context.image_paint_object and brush: col = layout.column() col.template_color_wheel(brush, "color", value_slider=True) col.prop(brush, "color", text="") row = col.row(align=True) - row.prop(brush, "size", text="Radius", slider=True) + row.prop(brush, "size", slider=True) row.prop(brush, "use_pressure_size", toggle=True, text="") row = col.row(align=True) @@ -628,7 +632,7 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): col.prop(brush, "blend", text="Blend") col = layout.column() - col.active = (brush.blend not in ('ERASE_ALPHA', 'ADD_ALPHA')) + col.active = (brush.blend not in {'ERASE_ALPHA', 'ADD_ALPHA'}) col.prop(brush, "use_alpha") # Weight Paint Mode # @@ -639,7 +643,7 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): col = layout.column() row = col.row(align=True) - row.prop(brush, "size", text="Radius", slider=True) + row.prop(brush, "size", slider=True) row.prop(brush, "use_pressure_size", toggle=True, text="") row = col.row(align=True) @@ -657,7 +661,7 @@ class VIEW3D_PT_tools_brush(PaintPanel, bpy.types.Panel): col.prop(brush, "color", text="") row = col.row(align=True) - row.prop(brush, "size", text="Radius", slider=True) + row.prop(brush, "size", slider=True) row.prop(brush, "use_pressure_size", toggle=True, text="") row = col.row(align=True) @@ -678,7 +682,7 @@ class VIEW3D_PT_tools_brush_texture(PaintPanel, bpy.types.Panel): def poll(cls, context): settings = cls.paint_settings(context) return (settings and settings.brush and (context.sculpt_object or - context.texture_paint_object)) + context.image_paint_object)) def draw(self, context): layout = self.layout @@ -690,6 +694,8 @@ class VIEW3D_PT_tools_brush_texture(PaintPanel, bpy.types.Panel): col = layout.column() col.template_ID_preview(brush, "texture", new="texture.new", rows=3, cols=8) + if brush.use_paint_image: + col.prop(brush, "use_fixed_texture") if context.sculpt_object: #XXX duplicated from properties_texture.py @@ -703,32 +709,32 @@ class VIEW3D_PT_tools_brush_texture(PaintPanel, bpy.types.Panel): col.separator() col = layout.column() - col.active = tex_slot.map_mode in ('FIXED', ) + col.active = tex_slot.map_mode in {'FIXED'} col.label(text="Angle:") col = layout.column() - if not brush.use_anchor and brush.sculpt_tool not in ('GRAB', 'SNAKE_HOOK', 'THUMB', 'ROTATE') and tex_slot.map_mode in ('FIXED'): + if not brush.use_anchor and brush.sculpt_tool not in {'GRAB', 'SNAKE_HOOK', 'THUMB', 'ROTATE'} and tex_slot.map_mode in {'FIXED'}: col.prop(brush, "texture_angle_source_random", text="") else: col.prop(brush, "texture_angle_source_no_random", text="") #row = col.row(align=True) #row.label(text="Angle:") - #row.active = tex_slot.map_mode in ('FIXED', 'TILED') + #row.active = tex_slot.map_mode in {'FIXED', 'TILED'} #row = col.row(align=True) #col = row.column() - #col.active = tex_slot.map_mode in ('FIXED') + #col.active = tex_slot.map_mode in {'FIXED'} #col.prop(brush, "use_rake", toggle=True, icon='PARTICLEMODE', text="") col = layout.column() col.prop(tex_slot, "angle", text="") - col.active = tex_slot.map_mode in ('FIXED', 'TILED') + col.active = tex_slot.map_mode in {'FIXED', 'TILED'} #col = layout.column() #col.prop(brush, "use_random_rotation") - #col.active = (not brush.use_rake) and (not brush.use_anchor) and brush.sculpt_tool not in ('GRAB', 'SNAKE_HOOK', 'THUMB', 'ROTATE') and tex_slot.map_mode in ('FIXED') + #col.active = (not brush.use_rake) and (not brush.use_anchor) and (brush.sculpt_tool not in {'GRAB', 'SNAKE_HOOK', 'THUMB', 'ROTATE'}) and tex_slot.map_mode in {'FIXED'} split = layout.split() @@ -748,7 +754,7 @@ class VIEW3D_PT_tools_brush_texture(PaintPanel, bpy.types.Panel): row = col.row(align=True) row.label(text="Overlay:") - row.active = tex_slot.map_mode in ('FIXED', 'TILED') + row.active = tex_slot.map_mode in {'FIXED', 'TILED'} row = col.row(align=True) @@ -759,11 +765,11 @@ class VIEW3D_PT_tools_brush_texture(PaintPanel, bpy.types.Panel): else: col.prop(brush, "use_texture_overlay", toggle=True, text="", icon='MUTE_IPO_ON') - col.active = tex_slot.map_mode in ('FIXED', 'TILED') + col.active = tex_slot.map_mode in {'FIXED', 'TILED'} col = row.column() col.prop(brush, "texture_overlay_alpha", text="Alpha") - col.active = tex_slot.map_mode in ('FIXED', 'TILED') and brush.use_texture_overlay + col.active = tex_slot.map_mode in {'FIXED', 'TILED'} and brush.use_texture_overlay class VIEW3D_PT_tools_brush_tool(PaintPanel, bpy.types.Panel): @@ -774,7 +780,7 @@ class VIEW3D_PT_tools_brush_tool(PaintPanel, bpy.types.Panel): def poll(cls, context): settings = cls.paint_settings(context) return (settings and settings.brush and - (context.sculpt_object or context.texture_paint_object or + (context.sculpt_object or context.image_paint_object or context.vertex_paint_object or context.weight_paint_object)) def draw(self, context): @@ -782,24 +788,22 @@ class VIEW3D_PT_tools_brush_tool(PaintPanel, bpy.types.Panel): settings = __class__.paint_settings(context) brush = settings.brush - texture_paint = context.texture_paint_object - sculpt = context.sculpt_object col = layout.column(align=True) if context.sculpt_object: col.prop(brush, "sculpt_tool", expand=False, text="") col.operator("brush.reset") - elif context.texture_paint_object: - col.prop(brush, "imagepaint_tool", expand=False, text="") + elif context.image_paint_object: + col.prop(brush, "image_tool", expand=False, text="") elif context.vertex_paint_object or context.weight_paint_object: - col.prop(brush, "vertexpaint_tool", expand=False, text="") + col.prop(brush, "vertex_tool", expand=False, text="") row = layout.row(align=True) row.prop(brush, "use_paint_sculpt", text="", icon='SCULPTMODE_HLT') row.prop(brush, "use_paint_vertex", text="", icon='VPAINT_HLT') row.prop(brush, "use_paint_weight", text="", icon='WPAINT_HLT') - row.prop(brush, "use_paint_texture", text="", icon='TPAINT_HLT') + row.prop(brush, "use_paint_image", text="", icon='TPAINT_HLT') class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): @@ -812,14 +816,14 @@ class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): return (settings and settings.brush and (context.sculpt_object or context.vertex_paint_object or context.weight_paint_object or - context.texture_paint_object)) + context.image_paint_object)) def draw(self, context): layout = self.layout settings = __class__.paint_settings(context) brush = settings.brush - texture_paint = context.texture_paint_object + image_paint = context.image_paint_object col = layout.column() @@ -843,7 +847,7 @@ class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): row.active = brush.use_space row.prop(brush, "spacing", text="Spacing") - if brush.sculpt_tool not in ('GRAB', 'THUMB', 'SNAKE_HOOK', 'ROTATE') and (not brush.use_anchor) and (not brush.use_restore_mesh): + if (brush.sculpt_tool not in {'GRAB', 'THUMB', 'SNAKE_HOOK', 'ROTATE'}) and (not brush.use_anchor) and (not brush.use_restore_mesh): col = layout.column() col.separator() @@ -870,7 +874,7 @@ class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): col.separator() - if not texture_paint: + if not image_paint: row = col.row() row.prop(brush, "use_smooth_stroke") @@ -882,7 +886,7 @@ class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): col.separator() col = layout.column() - col.active = (not brush.use_anchor) and (brush.sculpt_tool not in ('GRAB', 'THUMB', 'ROTATE', 'SNAKE_HOOK')) + col.active = (not brush.use_anchor) and (brush.sculpt_tool not in {'GRAB', 'THUMB', 'ROTATE', 'SNAKE_HOOK'}) row = col.row() row.prop(brush, "use_space") @@ -896,7 +900,7 @@ class VIEW3D_PT_tools_brush_stroke(PaintPanel, bpy.types.Panel): #col.separator() - #if texture_paint: + #if image_paint: # row.prop(brush, "use_pressure_spacing", toggle=True, text="") @@ -941,28 +945,22 @@ class VIEW3D_PT_sculpt_options(PaintPanel, bpy.types.Panel): tool_settings = context.tool_settings sculpt = tool_settings.sculpt settings = __class__.paint_settings(context) - brush = settings.brush - - split = layout.split() - - col = split.column() - - col.prop(sculpt, "use_threaded", text="Threaded Sculpt") - col.prop(sculpt, "show_low_resolution") - col.prop(sculpt, "show_brush") - col.label(text="Unified Settings:") - col.prop(tool_settings, "sculpt_paint_use_unified_size", text="Size") - col.prop(tool_settings, "sculpt_paint_use_unified_strength", text="Strength") - - col = split.column() - - col.label(text="Lock:") - row = col.row(align=True) + layout.label(text="Lock:") + row = layout.row(align=True) row.prop(sculpt, "lock_x", text="X", toggle=True) row.prop(sculpt, "lock_y", text="Y", toggle=True) row.prop(sculpt, "lock_z", text="Z", toggle=True) + layout.prop(sculpt, "use_threaded", text="Threaded Sculpt") + layout.prop(sculpt, "show_low_resolution") + layout.prop(sculpt, "show_brush") + layout.prop(sculpt, "use_deform_only") + + layout.label(text="Unified Settings:") + layout.prop(tool_settings, "sculpt_paint_use_unified_size", text="Size") + layout.prop(tool_settings, "sculpt_paint_use_unified_strength", text="Strength") + class VIEW3D_PT_sculpt_symmetry(PaintPanel, bpy.types.Panel): bl_label = "Symmetry" @@ -973,31 +971,24 @@ class VIEW3D_PT_sculpt_symmetry(PaintPanel, bpy.types.Panel): return (context.sculpt_object and context.tool_settings.sculpt) def draw(self, context): - layout = self.layout sculpt = context.tool_settings.sculpt settings = __class__.paint_settings(context) - brush = settings.brush split = layout.split() col = split.column() - col.label(text="Mirror:") col.prop(sculpt, "use_symmetry_x", text="X") col.prop(sculpt, "use_symmetry_y", text="Y") col.prop(sculpt, "use_symmetry_z", text="Z") - col = split.column() - - col.prop(sculpt, "radial_symmetry", text="Radial") - - col = layout.column() + split.prop(sculpt, "radial_symmetry", text="Radial") - col.separator() + layout.separator() - col.prop(sculpt, "use_symmetry_feather", text="Feather") + layout.prop(sculpt, "use_symmetry_feather", text="Feather") class VIEW3D_PT_tools_brush_appearance(PaintPanel, bpy.types.Panel): @@ -1006,20 +997,19 @@ class VIEW3D_PT_tools_brush_appearance(PaintPanel, bpy.types.Panel): @classmethod def poll(cls, context): - return (context.sculpt_object and context.tool_settings.sculpt) or (context.vertex_paint_object and context.tool_settings.vertex_paint) or (context.weight_paint_object and context.tool_settings.weight_paint) or (context.texture_paint_object and context.tool_settings.image_paint) + return (context.sculpt_object and context.tool_settings.sculpt) or (context.vertex_paint_object and context.tool_settings.vertex_paint) or (context.weight_paint_object and context.tool_settings.weight_paint) or (context.image_paint_object and context.tool_settings.image_paint) def draw(self, context): layout = self.layout - sculpt = context.tool_settings.sculpt settings = __class__.paint_settings(context) brush = settings.brush col = layout.column() if context.sculpt_object and context.tool_settings.sculpt: - #if brush.sculpt_tool in ('DRAW', 'INFLATE', 'CLAY', 'PINCH', 'CREASE', 'BLOB', 'FLATTEN', 'FILL', 'SCRAPE', 'CLAY_TUBES'): - if brush.sculpt_tool in ('DRAW', 'INFLATE', 'CLAY', 'PINCH', 'CREASE', 'BLOB', 'FLATTEN', 'FILL', 'SCRAPE'): + #if brush.sculpt_tool in {'DRAW', 'INFLATE', 'CLAY', 'PINCH', 'CREASE', 'BLOB', 'FLATTEN', 'FILL', 'SCRAPE', 'CLAY_TUBES'}: + if brush.sculpt_tool in {'DRAW', 'INFLATE', 'CLAY', 'PINCH', 'CREASE', 'BLOB', 'FLATTEN', 'FILL', 'SCRAPE'}: col.prop(brush, "cursor_color_add", text="Add Color") col.prop(brush, "cursor_color_subtract", text="Subtract Color") else: @@ -1124,13 +1114,13 @@ class VIEW3D_PT_tools_vertexpaint(View3DPanel, bpy.types.Panel): class VIEW3D_PT_tools_projectpaint(View3DPanel, bpy.types.Panel): - bl_context = "texturepaint" + bl_context = "imagepaint" bl_label = "Project Paint" @classmethod def poll(cls, context): brush = context.tool_settings.image_paint.brush - return (brush and brush.imagepaint_tool != 'SOFTEN') + return (brush and brush.image_tool != 'SOFTEN') def draw_header(self, context): ipaint = context.tool_settings.image_paint @@ -1140,6 +1130,8 @@ class VIEW3D_PT_tools_projectpaint(View3DPanel, bpy.types.Panel): def draw(self, context): layout = self.layout + ob = context.active_object + mesh = ob.data ipaint = context.tool_settings.image_paint settings = context.tool_settings.image_paint use_projection = ipaint.use_projection @@ -1167,16 +1159,16 @@ class VIEW3D_PT_tools_projectpaint(View3DPanel, bpy.types.Panel): row2 = row.row(align=False) row2.active = (use_projection and ipaint.use_stencil_layer) - row2.menu("VIEW3D_MT_tools_projectpaint_stencil", text=context.active_object.data.uv_texture_stencil.name) + row2.menu("VIEW3D_MT_tools_projectpaint_stencil", text=mesh.uv_texture_stencil.name) row2.prop(ipaint, "invert_stencil", text="", icon='IMAGE_ALPHA') col = layout.column() sub = col.column() row = sub.row() - row.active = (settings.brush.imagepaint_tool == 'CLONE') + row.active = (settings.brush.image_tool == 'CLONE') row.prop(ipaint, "use_clone_layer", text="Layer") - row.menu("VIEW3D_MT_tools_projectpaint_clone", text=context.active_object.data.uv_texture_clone.name) + row.menu("VIEW3D_MT_tools_projectpaint_clone", text=mesh.uv_texture_clone.name) sub = col.column() sub.prop(ipaint, "seam_bleed") @@ -1200,14 +1192,14 @@ class VIEW3D_PT_imagepaint_options(PaintPanel): @classmethod def poll(cls, context): - return (context.texture_paint_object and context.tool_settings.image_paint) + return (context.image_paint_object and context.tool_settings.image_paint) def draw(self, context): layout = self.layout - col = layout.column() - tool_settings = context.tool_settings + + col = layout.column() col.label(text="Unified Settings:") col.prop(tool_settings, "sculpt_paint_use_unified_size", text="Size") col.prop(tool_settings, "sculpt_paint_use_unified_strength", text="Strength") @@ -1300,13 +1292,5 @@ class VIEW3D_PT_tools_particlemode(View3DPanel, bpy.types.Panel): sub.active = pe.use_fade_time sub.prop(pe, "fade_frames", slider=True) - -def register(): +if __name__ == "__main__": # only for live edit. bpy.utils.register_module(__name__) - - -def unregister(): - bpy.utils.unregister_module(__name__) - -if __name__ == "__main__": - register() diff --git a/release/scripts/keyingsets/keyingsets_builtins.py b/release/scripts/startup/keyingsets_builtins.py index cff4aecac54..8cb63ea48cf 100644 --- a/release/scripts/keyingsets/keyingsets_builtins.py +++ b/release/scripts/startup/keyingsets_builtins.py @@ -23,10 +23,15 @@ Built-In Keying Sets None of these Keying Sets should be removed, as these are needed by various parts of Blender in order for them to work correctly. + +Beware also about changing the order that these are defined +here, since this can result in old files referring to the +wrong Keying Set as the active one, potentially resulting +in lost (i.e. unkeyed) animation. """ import bpy -from keyingsets_utils import * +import keyingsets_utils ############################### # Built-In KeyingSets @@ -37,13 +42,13 @@ class BUILTIN_KSI_Location(bpy.types.KeyingSetInfo): bl_label = "Location" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for location - generate = RKS_GEN_location + generate = keyingsets_utils.RKS_GEN_location # Rotation @@ -51,13 +56,13 @@ class BUILTIN_KSI_Rotation(bpy.types.KeyingSetInfo): bl_label = "Rotation" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for location - generate = RKS_GEN_rotation + generate = keyingsets_utils.RKS_GEN_rotation # Scale @@ -65,13 +70,13 @@ class BUILTIN_KSI_Scaling(bpy.types.KeyingSetInfo): bl_label = "Scaling" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for location - generate = RKS_GEN_scaling + generate = keyingsets_utils.RKS_GEN_scaling # ------------ @@ -81,17 +86,17 @@ class BUILTIN_KSI_LocRot(bpy.types.KeyingSetInfo): bl_label = "LocRot" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator def generate(self, context, ks, data): # location - RKS_GEN_location(self, context, ks, data) + keyingsets_utils.RKS_GEN_location(self, context, ks, data) # rotation - RKS_GEN_rotation(self, context, ks, data) + keyingsets_utils.RKS_GEN_rotation(self, context, ks, data) # LocScale @@ -99,17 +104,17 @@ class BUILTIN_KSI_LocScale(bpy.types.KeyingSetInfo): bl_label = "LocScale" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator def generate(self, context, ks, data): # location - RKS_GEN_location(self, context, ks, data) + keyingsets_utils.RKS_GEN_location(self, context, ks, data) # scale - RKS_GEN_scaling(self, context, ks, data) + keyingsets_utils.RKS_GEN_scaling(self, context, ks, data) # LocRotScale @@ -117,19 +122,19 @@ class BUILTIN_KSI_LocRotScale(bpy.types.KeyingSetInfo): bl_label = "LocRotScale" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator def generate(self, context, ks, data): # location - RKS_GEN_location(self, context, ks, data) + keyingsets_utils.RKS_GEN_location(self, context, ks, data) # rotation - RKS_GEN_rotation(self, context, ks, data) + keyingsets_utils.RKS_GEN_rotation(self, context, ks, data) # scale - RKS_GEN_scaling(self, context, ks, data) + keyingsets_utils.RKS_GEN_scaling(self, context, ks, data) # RotScale @@ -137,17 +142,17 @@ class BUILTIN_KSI_RotScale(bpy.types.KeyingSetInfo): bl_label = "RotScale" # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator def generate(self, context, ks, data): # rotation - RKS_GEN_rotation(self, context, ks, data) + keyingsets_utils.RKS_GEN_rotation(self, context, ks, data) # scaling - RKS_GEN_scaling(self, context, ks, data) + keyingsets_utils.RKS_GEN_scaling(self, context, ks, data) # ------------ @@ -159,13 +164,13 @@ class BUILTIN_KSI_VisualLoc(bpy.types.KeyingSetInfo): bl_options = {'INSERTKEY_VISUAL'} # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for location - generate = RKS_GEN_location + generate = keyingsets_utils.RKS_GEN_location # Rotation @@ -175,13 +180,13 @@ class BUILTIN_KSI_VisualRot(bpy.types.KeyingSetInfo): bl_options = {'INSERTKEY_VISUAL'} # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for rotation - generate = RKS_GEN_rotation + generate = keyingsets_utils.RKS_GEN_rotation # VisualLocRot @@ -191,17 +196,17 @@ class BUILTIN_KSI_VisualLocRot(bpy.types.KeyingSetInfo): bl_options = {'INSERTKEY_VISUAL'} # poll - use predefined callback for selected bones/objects - poll = RKS_POLL_selected_items + poll = keyingsets_utils.RKS_POLL_selected_items # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator def generate(self, context, ks, data): # location - RKS_GEN_location(self, context, ks, data) + keyingsets_utils.RKS_GEN_location(self, context, ks, data) # rotation - RKS_GEN_rotation(self, context, ks, data) + keyingsets_utils.RKS_GEN_rotation(self, context, ks, data) # ------------ @@ -210,16 +215,20 @@ class BUILTIN_KSI_VisualLocRot(bpy.types.KeyingSetInfo): class BUILTIN_KSI_Available(bpy.types.KeyingSetInfo): bl_label = "Available" - # poll - use predefined callback for selected objects - # TODO: this should really check whether the selected object (or datablock) - # has any animation data defined yet - poll = RKS_POLL_selected_objects + # poll - selected objects or selected object with animation data + def poll(ksi, context): + ob = context.active_object + if ob: + # TODO: this fails if one animation-less object is active, but many others are selected + return ob.animation_data and ob.animation_data.action + else: + return bool(context.selected_objects) # iterator - use callback for selected bones/objects - iterator = RKS_ITER_selected_item + iterator = keyingsets_utils.RKS_ITER_selected_item # generator - use callback for doing this - generate = RKS_GEN_available + generate = keyingsets_utils.RKS_GEN_available ############################### @@ -278,7 +287,7 @@ class BUILTIN_KSI_WholeCharacter(bpy.types.KeyingSetInfo): path = id_path + prop else: # standard transforms/properties - path = path_add_property(id_path, prop) + path = keyingsets_utils.path_add_property(id_path, prop) # add Keying Set entry for this... if use_groups: @@ -352,6 +361,93 @@ class BUILTIN_KSI_WholeCharacter(bpy.types.KeyingSetInfo): # for now, just add all of 'em ksi.addProp(ks, bone, '["%s"]' % (prop)) +############################### + + +# Delta Location +class BUILTIN_KSI_DeltaLocation(bpy.types.KeyingSetInfo): + bl_label = "Delta Location" + + # poll - selected objects only (and only if active object in object mode) + poll = keyingsets_utils.RKS_POLL_selected_objects + + # iterator - selected objects only + iterator = keyingsets_utils.RKS_ITER_selected_objects + + # generator - delta location channels only + def generate(ksi, context, ks, data): + # get id-block and path info + id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data) + + # add the property name to the base path + path = keyingsets_utils.path_add_property(base_path, "delta_location") + + # add Keying Set entry for this... + if grouping: + ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping) + else: + ks.paths.add(id_block, path) + + +# Delta Rotation +class BUILTIN_KSI_DeltaRotation(bpy.types.KeyingSetInfo): + bl_label = "Delta Rotation" + + # poll - selected objects only (and only if active object in object mode) + poll = keyingsets_utils.RKS_POLL_selected_objects + + # iterator - selected objects only + iterator = keyingsets_utils.RKS_ITER_selected_objects + + # generator - delta location channels only + def generate(ksi, context, ks, data): + # get id-block and path info + id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data) + + # add the property name to the base path + # rotation mode affects the property used + if data.rotation_mode == 'QUATERNION': + path = path_add_property(base_path, "delta_rotation_quaternion") + elif data.rotation_mode == 'AXIS_ANGLE': + # XXX: for now, this is not available yet + #path = path_add_property(base_path, "delta_rotation_axis_angle") + return + else: + path = keyingsets_utils.path_add_property(base_path, "delta_rotation_euler") + + # add Keying Set entry for this... + if grouping: + ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping) + else: + ks.paths.add(id_block, path) + + +# Delta Scale +class BUILTIN_KSI_DeltaScale(bpy.types.KeyingSetInfo): + bl_label = "Delta Scale" + + # poll - selected objects only (and only if active object in object mode) + poll = keyingsets_utils.RKS_POLL_selected_objects + + # iterator - selected objects only + iterator = keyingsets_utils.RKS_ITER_selected_objects + + # generator - delta location channels only + def generate(ksi, context, ks, data): + # get id-block and path info + id_block, base_path, grouping = keyingsets_utils.get_transform_generators_base_info(data) + + # add the property name to the base path + path = keyingsets_utils.path_add_property(base_path, "delta_scale") + + # add Keying Set entry for this... + if grouping: + ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping) + else: + ks.paths.add(id_block, path) + +############################### + def register(): bpy.utils.register_module(__name__) @@ -363,5 +459,3 @@ def unregister(): if __name__ == "__main__": register() - -############################### diff --git a/release/scripts/templates/addon_add_object.py b/release/scripts/templates/addon_add_object.py index f4f1185e282..67e033271f4 100644 --- a/release/scripts/templates/addon_add_object.py +++ b/release/scripts/templates/addon_add_object.py @@ -31,8 +31,10 @@ def add_object(self, context): edges = [] faces = [[0, 1, 2, 3]] - mesh_data = bpy.data.meshes.new(name='New Object Mesh') - mesh_data.from_pydata(verts, edges, faces) + mesh = bpy.data.meshes.new(name='New Object Mesh') + mesh.from_pydata(verts, edges, faces) + # useful for development when the mesh may be invalid. + # mesh.validate(verbose=True) add_object_data(context, mesh_data, operator=self) @@ -55,7 +57,7 @@ class OBJECT_OT_add_object(bpy.types.Operator, AddObjectHelper): return {'FINISHED'} -#### REGISTER #### +# Registration def add_object_button(self, context): self.layout.operator( diff --git a/release/scripts/templates/background_job.py b/release/scripts/templates/background_job.py index 0337f8f4922..11b51e5a9b5 100644 --- a/release/scripts/templates/background_job.py +++ b/release/scripts/templates/background_job.py @@ -1,12 +1,18 @@ -# This script is an example of how you can run blender from the command line (in background mode with no interface) -# to automate tasks, in this example it creates a text object, camera and light, then renders and/or saves it. -# This example also shows how you can parse command line options to python scripts. +# This script is an example of how you can run blender from the command line +# (in background mode with no interface) to automate tasks, in this example it +# creates a text object, camera and light, then renders and/or saves it. +# This example also shows how you can parse command line options to scripts. # # Example usage for this test. -# blender --background --factory-startup --python $HOME/background_job.py -- --text="Hello World" --render="/tmp/hello" --save="/tmp/hello.blend" +# blender --background --factory-startup --python $HOME/background_job.py -- \ +# --text="Hello World" \ +# --render="/tmp/hello" \ +# --save="/tmp/hello.blend" # # Notice: -# '--factory-startup' is used to avoid the user default settings from interfearing with automated scene generation. +# '--factory-startup' is used to avoid the user default settings from +# interfearing with automated scene generation. +# # '--' causes blender to ignore all following arguments so python can use them. # # See blender --help for details. @@ -14,7 +20,7 @@ import bpy -def example_function(body_text, save_path, render_path): +def example_function(text, save_path, render_path): scene = bpy.context.scene @@ -27,15 +33,15 @@ def example_function(body_text, save_path, render_path): # Text Object txt_ob = bpy.data.objects.new(name="MyText", object_data=txt_data) - scene.objects.link(txt_ob) # add the data to the scene as an object - txt_data.body = body_text # set the body text to the command line arg given - txt_data.align = 'CENTER' # center text + scene.objects.link(txt_ob) # add the data to the scene as an object + txt_data.body = text # the body text to the command line arg given + txt_data.align = 'CENTER' # center text # Camera - cam_data = bpy.data.cameras.new("MyCam") # create new camera data + cam_data = bpy.data.cameras.new("MyCam") cam_ob = bpy.data.objects.new(name="MyCam", object_data=cam_data) - scene.objects.link(cam_ob) # add the camera data to the scene (creating a new object) - scene.camera = cam_ob # set the active camera + scene.objects.link(cam_ob) # instance the camera object in the scene + scene.camera = cam_ob # set the active camera cam_ob.location = 0.0, 0.0, 10.0 # Lamp @@ -65,14 +71,12 @@ def example_function(body_text, save_path, render_path): bpy.ops.render.render(write_still=True) -import sys # to get command line args -import optparse # to parse options for us and print a nice help message - - def main(): + import sys # to get command line args + import argparse # to parse options for us and print a nice help message - # get the args passed to blender after "--", all of which are ignored by blender specifically - # so python may receive its own arguments + # get the args passed to blender after "--", all of which are ignored by + # blender so scripts may receive their own arguments argv = sys.argv if "--" not in argv: @@ -81,31 +85,35 @@ def main(): argv = argv[argv.index("--") + 1:] # get all args after "--" # When --help or no args are given, print this help - usage_text = "Run blender in background mode with this script:" - usage_text += " blender --background --python " + __file__ + " -- [options]" + usage_text = \ + "Run blender in background mode with this script:" + " blender --background --python " + __file__ + " -- [options]" - parser = optparse.OptionParser(usage=usage_text) + parser = argparse.ArgumentParser(description=usage_text) - # Example background utility, add some text and renders or saves it (with options) + # Example utility, add some text and renders or saves it (with options) # Possible types are: string, int, long, choice, float and complex. - parser.add_option("-t", "--text", dest="body_text", help="This text will be used to render an image", type="string") + parser.add_argument("-t", "--text", dest="text", type=str, required=True, + help="This text will be used to render an image") - parser.add_option("-s", "--save", dest="save_path", help="Save the generated file to the specified path", metavar='FILE') - parser.add_option("-r", "--render", dest="render_path", help="Render an image to the specified path", metavar='FILE') + parser.add_argument("-s", "--save", dest="save_path", metavar='FILE', + help="Save the generated file to the specified path") + parser.add_argument("-r", "--render", dest="render_path", metavar='FILE', + help="Render an image to the specified path") - options, args = parser.parse_args(argv) # In this example we wont use the args + args = parser.parse_args(argv) # In this example we wont use the args if not argv: parser.print_help() return - if not options.body_text: + if not args.text: print("Error: --text=\"some string\" argument not given, aborting.") parser.print_help() return # Run the example function - example_function(options.body_text, options.save_path, options.render_path) + example_function(args.text, args.save_path, args.render_path) print("batch job finished, exiting") diff --git a/release/scripts/templates/builtin_keyingset.py b/release/scripts/templates/builtin_keyingset.py index 0b808e9cd26..19f92dc75e7 100644 --- a/release/scripts/templates/builtin_keyingset.py +++ b/release/scripts/templates/builtin_keyingset.py @@ -1,5 +1,4 @@ import bpy -from keyingsets_utils import * class BUILTIN_KSI_hello(bpy.types.KeyingSetInfo): @@ -7,7 +6,7 @@ class BUILTIN_KSI_hello(bpy.types.KeyingSetInfo): # poll - test for whether Keying Set can be used at all def poll(ksi, context): - return (context.active_object) or (context.selected_objects) + return context.active_object or context.selected_objects # iterator - go over all relevant data, calling generate() def iterator(ksi, context, ks): diff --git a/release/scripts/templates/operator_export.py b/release/scripts/templates/operator_export.py index 5390d32aeff..1b1c90c8a21 100644 --- a/release/scripts/templates/operator_export.py +++ b/release/scripts/templates/operator_export.py @@ -13,12 +13,11 @@ def write_some_data(context, filepath, use_some_setting): # ExportHelper is a helper class, defines filename and # invoke() function which calls the file selector. from io_utils import ExportHelper - -from bpy.props import * +from bpy.props import StringProperty, BoolProperty, EnumProperty class ExportSomeData(bpy.types.Operator, ExportHelper): - '''This appiers in the tooltip of the operator and in the generated docs.''' + '''This appears in the tooltip of the operator and in the generated docs.''' bl_idname = "export.some_data" # this is important since its how bpy.ops.export.some_data is constructed bl_label = "Export Some Data" @@ -31,7 +30,9 @@ class ExportSomeData(bpy.types.Operator, ExportHelper): # to the class instance from the operator settings before calling. use_setting = BoolProperty(name="Example Boolean", description="Example Tooltip", default=True) - type = bpy.props.EnumProperty(items=(('OPT_A', "First Option", "Description one"), ('OPT_B', "Second Option", "Description two.")), + type = EnumProperty(items=(('OPT_A', "First Option", "Description one"), + ('OPT_B', "Second Option", "Description two."), + ), name="Example Enum", description="Choose between two items", default='OPT_A') diff --git a/release/scripts/templates/operator_mesh_add.py b/release/scripts/templates/operator_mesh_add.py index 77d172b3068..65b08eebb4e 100644 --- a/release/scripts/templates/operator_mesh_add.py +++ b/release/scripts/templates/operator_mesh_add.py @@ -34,7 +34,7 @@ def add_box(width, height, depth): return vertices, faces -from bpy.props import * +from bpy.props import FloatProperty, BoolProperty, FloatVectorProperty class AddBox(bpy.types.Operator): diff --git a/release/scripts/templates/operator_modal.py b/release/scripts/templates/operator_modal.py index 6eb3843d6a6..78dbd4c6b43 100644 --- a/release/scripts/templates/operator_modal.py +++ b/release/scripts/templates/operator_modal.py @@ -1,5 +1,5 @@ import bpy -from bpy.props import * +from bpy.props import IntProperty, FloatProperty class ModalOperator(bpy.types.Operator): diff --git a/release/scripts/templates/operator_modal_timer.py b/release/scripts/templates/operator_modal_timer.py new file mode 100644 index 00000000000..d2267191cf5 --- /dev/null +++ b/release/scripts/templates/operator_modal_timer.py @@ -0,0 +1,45 @@ +import bpy + + +class ModalTimerOperator(bpy.types.Operator): + '''Operator which runs its self from a timer.''' + bl_idname = "wm.modal_timer_operator" + bl_label = "Modal Timer Operator" + + _timer = None + + def modal(self, context, event): + if event.type == 'ESC': + return self.cancel() + + if event.type == 'TIMER': + # change theme color, silly! + color = context.user_preferences.themes[0].view_3d.back + color.s = 1.0 + color.h += 0.01 + + return {'PASS_THROUGH'} + + def execute(self, context): + context.window_manager.modal_handler_add(self) + self._timer = context.window_manager.event_timer_add(0.1, context.window) + return {'RUNNING_MODAL'} + + def cancel(self, context): + context.window_manager.event_timer_remove(self._timer) + return {'CANCELLED'} + + +def register(): + bpy.utils.register_class(ModalTimerOperator) + + +def unregister(): + bpy.utils.unregister_class(ModalTimerOperator) + + +if __name__ == "__main__": + register() + + # test call + bpy.ops.wm.modal_timer_operator() diff --git a/release/scripts/templates/panel_simple.py b/release/scripts/templates/panel_simple.py index ddbd4eb7aa4..e5bf70cb654 100644 --- a/release/scripts/templates/panel_simple.py +++ b/release/scripts/templates/panel_simple.py @@ -26,7 +26,7 @@ def register(): def unregister(): - bpy.utils.unregister_class(OBJECT_PT_hello) + bpy.utils.unregister_class(OBJECT_PT_hello) if __name__ == "__main__": |