diff --git a/lib/linguist/languages.yml b/lib/linguist/languages.yml index 7a6ee504..2e3ba7ed 100644 --- a/lib/linguist/languages.yml +++ b/lib/linguist/languages.yml @@ -1702,6 +1702,7 @@ Python: - .pyw - .wsgi - .xpy + - .pyp filenames: - wscript - SConstruct diff --git a/samples/Python/Cinema4DPythonPlugin.pyp b/samples/Python/Cinema4DPythonPlugin.pyp new file mode 100644 index 00000000..30c2aa78 --- /dev/null +++ b/samples/Python/Cinema4DPythonPlugin.pyp @@ -0,0 +1,241 @@ +# +# Cinema 4D Python Plugin Source file +# https://github.com/nr-plugins/nr-xpresso-alignment-tools +# + +# coding: utf-8 +# +# Copyright (C) 2012, Niklas Rosenstein +# Licensed under the GNU General Public License +# +# XPAT - XPresso Alignment Tools +# ============================== +# +# The XPAT plugin provides tools for aligning nodes in the Cinema 4D +# XPresso Editor, improving readability of complex XPresso set-ups +# immensively. +# +# Requirements: +# - MAXON Cinema 4D R13+ +# - Python `c4dtools` library. Get it from +# http://github.com/NiklasRosenstein/c4dtools +# +# Author: Niklas Rosenstein +# Version: 1.1 (01/06/2012) + +import os +import sys +import json +import c4d +import c4dtools +import itertools + +from c4d.modules import graphview as gv +from c4dtools.misc import graphnode + +res, importer = c4dtools.prepare(__file__, __res__) +settings = c4dtools.helpers.Attributor({ + 'options_filename': res.file('config.json'), +}) + +def align_nodes(nodes, mode, spacing): + r""" + Aligns the passed nodes horizontally and apply the minimum spacing + between them. + """ + + modes = ['horizontal', 'vertical'] + if not nodes: + return + if mode not in modes: + raise ValueError('invalid mode, choices are: ' + ', '.join(modes)) + + get_0 = lambda x: x.x + get_1 = lambda x: x.y + set_0 = lambda x, v: setattr(x, 'x', v) + set_1 = lambda x, v: setattr(x, 'y', v) + + if mode == 'vertical': + get_0, get_1 = get_1, get_0 + set_0, set_1 = set_1, set_0 + + nodes = [graphnode.GraphNode(n) for n in nodes] + nodes.sort(key=lambda n: get_0(n.position)) + midpoint = graphnode.find_nodes_mid(nodes) + + # Apply the spacing between the nodes relative to the coordinate-systems + # origin. We can offset them later because we now the nodes' midpoint + # already. + first_position = nodes[0].position + new_positions = [] + prev_offset = 0 + for node in nodes: + # Compute the relative position of the node. + position = node.position + set_0(position, get_0(position) - get_0(first_position)) + + # Obtain it's size and check if the node needs to be re-placed. + size = node.size + if get_0(position) < prev_offset: + set_0(position, prev_offset) + prev_offset += spacing + get_0(size) + else: + prev_offset = get_0(position) + get_0(size) + spacing + + set_1(position, get_1(midpoint)) + new_positions.append(position) + + # Center the nodes again. + bbox_size = prev_offset - spacing + bbox_size_2 = bbox_size * 0.5 + for node, position in itertools.izip(nodes, new_positions): + # TODO: Here is some issue with offsetting the nodes. Some value + # dependent on the spacing must be added here to not make the nodes + # move horizontally/vertically although they have already been + # aligned. + set_0(position, get_0(midpoint) + get_0(position) - bbox_size_2 + spacing) + node.position = position + +def align_nodes_shortcut(mode, spacing): + master = gv.GetMaster(0) + if not master: + return + + root = master.GetRoot() + if not root: + return + + nodes = graphnode.find_selected_nodes(root) + if nodes: + master.AddUndo() + align_nodes(nodes, mode, spacing) + c4d.EventAdd() + + return True + +class XPAT_Options(c4dtools.helpers.Attributor): + r""" + This class organizes the options for the XPAT plugin, i.e. + validating, loading and saving. + """ + + defaults = { + 'hspace': 50, + 'vspace': 20, + } + + def __init__(self, filename=None): + super(XPAT_Options, self).__init__() + self.load(filename) + + def load(self, filename=None): + r""" + Load the options from file pointed to by filename. If filename + is None, it defaults to the filename defined in options in the + global scope. + """ + + if filename is None: + filename = settings.options_filename + + if os.path.isfile(filename): + self.dict_ = self.defaults.copy() + with open(filename, 'rb') as fp: + self.dict_.update(json.load(fp)) + else: + self.dict_ = self.defaults.copy() + self.save() + + def save(self, filename=None): + r""" + Save the options defined in XPAT_Options instance to HD. + """ + + if filename is None: + filename = settings.options_filename + + values = dict((k, v) for k, v in self.dict_.iteritems() + if k in self.defaults) + with open(filename, 'wb') as fp: + json.dump(values, fp) + +class XPAT_OptionsDialog(c4d.gui.GeDialog): + r""" + This class implements the behavior of the XPAT options dialog, + taking care of storing the options on the HD and loading them + again on startup. + """ + + # c4d.gui.GeDialog + + def CreateLayout(self): + return self.LoadDialogResource(res.DLG_OPTIONS) + + def InitValues(self): + self.SetLong(res.EDT_HSPACE, options.hspace) + self.SetLong(res.EDT_VSPACE, options.vspace) + return True + + def Command(self, id, msg): + if id == res.BTN_SAVE: + options.hspace = self.GetLong(res.EDT_HSPACE) + options.vspace = self.GetLong(res.EDT_VSPACE) + options.save() + self.Close() + return True + +class XPAT_Command_OpenOptionsDialog(c4dtools.plugins.Command): + r""" + This Cinema 4D CommandData plugin opens the XPAT options dialog + when being executed. + """ + + def __init__(self): + super(XPAT_Command_OpenOptionsDialog, self).__init__() + self._dialog = None + + @property + def dialog(self): + if not self._dialog: + self._dialog = XPAT_OptionsDialog() + return self._dialog + + # c4dtools.plugins.Command + + PLUGIN_ID = 1029621 + PLUGIN_NAME = res.string.XPAT_COMMAND_OPENOPTIONSDIALOG() + PLUGIN_HELP = res.string.XPAT_COMMAND_OPENOPTIONSDIALOG_HELP() + + # c4d.gui.CommandData + + def Execute(self, doc): + return self.dialog.Open(c4d.DLG_TYPE_MODAL) + +class XPAT_Command_AlignHorizontal(c4dtools.plugins.Command): + + PLUGIN_ID = 1029538 + PLUGIN_NAME = res.string.XPAT_COMMAND_ALIGNHORIZONTAL() + PLUGIN_ICON = res.file('xpresso-align-h.png') + PLUGIN_HELP = res.string.XPAT_COMMAND_ALIGNHORIZONTAL_HELP() + + def Execute(self, doc): + align_nodes_shortcut('horizontal', options.hspace) + return True + +class XPAT_Command_AlignVertical(c4dtools.plugins.Command): + + PLUGIN_ID = 1029539 + PLUGIN_NAME = res.string.XPAT_COMMAND_ALIGNVERTICAL() + PLUGIN_ICON = res.file('xpresso-align-v.png') + PLUGIN_HELP = res.string.XPAT_COMMAND_ALIGNVERTICAL_HELP() + + def Execute(self, doc): + align_nodes_shortcut('vertical', options.vspace) + return True + +options = XPAT_Options() + +if __name__ == '__main__': + c4dtools.plugins.main() + +