Created
May 23, 2017 11:41
-
-
Save gr4ph0s/53ca6300b15ba0819a666e9d7acb7c29 to your computer and use it in GitHub Desktop.
Exemple of GeUserArea who imitate browser
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding: utf-8 | |
""" | |
A GeUserArea Exemple for display multiple content. | |
Hardly based on https://github.com/NiklasRosenstein/c4d-2048 from Niklas | |
""" | |
__author__ = 'Adam Maxime - Graphos <gr4ph0s(at)hotmail.fr>' | |
__version__ = '1.0' | |
import c4d | |
import collections | |
PLUGIN_ID = 1039317 #PoseMaster | |
Coord = collections.namedtuple('Coord', 'x y') | |
class UiPose(object): | |
""" | |
This class represents a pose in the GeUserArea. | |
.. attribute:: id | |
The id of the pose in the DB | |
.. attribute:: bmp | |
c4d.bitmaps.BaseBitmap of the pose | |
.. attribute:: name | |
The name of the pose. Can be shrinked in the display function according the size of a miniature | |
.. attribute:: selected | |
True if the pose is actually selected on the GeUserArea | |
""" | |
__slots__ = ('id', 'bmp', 'name', 'selected') | |
def __init__(self, id, bmp, name): | |
super(UiPose, self).__init__() | |
self.id = id | |
self.bmp = bmp | |
self.name = name | |
self.selected = False | |
def __repr__(self): | |
return str(self.name) | |
def setId(self, id): | |
self.id = id | |
def setBmp(self, bmp): | |
self.bmp = bmp | |
def select(self): | |
self.selected = True | |
def toggle_select(self): | |
self.selected = not bool(self.selected) | |
def deselect(self): | |
self.selected = False | |
class UiPoseList(object): | |
""" | |
This class hold all :class: UiPose | |
""" | |
def __init__(self): | |
super(UiPoseList, self).__init__() | |
self.all_poses = list() | |
#Adding pose for exemple usage only ! | |
self.add_pose(0, None, "myName") | |
self.add_pose(1, None, "myNfdfdsfdame") | |
self.add_pose(2, None, "myName") | |
self.add_pose(3, None, "myName") | |
self.all_poses[2].select() | |
def reset(self): | |
self.all_poses = list() | |
def add_pose(self, pose_id, pose_bmp, pose_name): | |
""" | |
Add a UiPose in the list all_poses. | |
Returns True if the pose were created | |
""" | |
self.all_poses.append(UiPose(pose_id, pose_bmp, pose_name)) | |
return True | |
def select_pose(self, pose_id): | |
if self.get_number_of_poses() <= pose_id: | |
return False | |
self.all_poses[pose_id].select() | |
def deselect_pose(self, pose_id): | |
if self.get_number_of_poses() <= pose_id: | |
return False | |
self.all_poses[pose_id].deselect() | |
def toggle_select_pose(self, pose_id): | |
if self.get_number_of_poses() <= pose_id: | |
return False | |
self.all_poses[pose_id].toggle_select() | |
def deselect_all(self): | |
for pose in self.iter_poses(): | |
pose.deselect() | |
def get_number_of_poses(self): | |
return len(self.all_poses) | |
def iter_poses(self): | |
""" | |
iter_poses() -> iterator of UiPose | |
Use this function to iterate over all poses | |
""" | |
for pose in self.all_poses: | |
yield pose | |
# Cinema 4D GUI | |
class PoseLibrary_View(c4d.gui.GeUserArea): | |
""" | |
This class implements the visual representation of PoseList | |
""" | |
def __init__(self, ui_pose_list, tile_size=100, tile_space=8,): | |
super(PoseLibrary_View, self).__init__() | |
self.pose_list = ui_pose_list | |
self.set_tile_size(tile_size) | |
self.tile_space = tile_space | |
#only used for speed performance in draw_pose | |
self.total_tile_width = 0 | |
self.max_x_row = 0 | |
def set_tile_size(self, tile_size): | |
self.tile_width = tile_size | |
self.tile_height = tile_size * 1.25 | |
def get_color_vector(self, color_id): | |
""" | |
get_color_vector(color_id) -> c4d.Vector | |
Returns a color :class:`c4d.Vector` for *color_id*. The | |
existing :meth:`GetColorRGB` method returns a dictionary | |
with RGB values in range [0,255] which is rather inconvenient. | |
""" | |
data = self.GetColorRGB(color_id) | |
rgbv = c4d.Vector(data['r'], data['g'], data['b']) | |
return rgbv ^ c4d.Vector(1.0 / 255.0) | |
def calc_pose_pos(self, index): | |
""" | |
Helper function to compute the pixel offset of a miniature. | |
:param index: integer representing the order of the miniature | |
:return: Coord obj egual to the left top corner of a miniature | |
""" | |
if not self.max_x_row: | |
return Coord(0, 0) | |
x_row = index % self.max_x_row | |
y_row = index // self.max_x_row | |
x = (self.tile_space // 2) + ((x_row * self.tile_width) + (x_row * self.tile_space)) | |
y = (self.tile_space // 2) + ((y_row * self.tile_height) + (y_row * self.tile_space)) | |
return Coord(x, y) | |
def draw_pose(self, pose_id, pose_name, pose_bmp, pose_selected): | |
""" | |
Helper function to draw a pose. | |
:param pose_id: integer | |
the pose_id in the grid ! | |
Not equivalent to UiPose.id wich is the db one | |
:param pose_name: string | |
The actual pose name | |
:param pose_bmp: c4d.bitmaps.BaseBitmap | |
The bitmap to be used. | |
:param pose_selected: Bool | |
The state of the selection of the pose | |
""" | |
# Calculate the tile's position on the User Area for both | |
# components and convert it to a Coord object immediately. | |
pos = self.calc_pose_pos(pose_id) | |
size = Coord(self.tile_width, self.tile_height) | |
#Draw the top rectangle // Will be BaseBitmpap after | |
color = self.get_color_vector(c4d.COLOR_BGFOCUS) | |
self.DrawSetPen(color) | |
self.DrawRectangle(pos.x, pos.y, pos.x + size.x, pos.y + size.y * 0.75) | |
#write pose id | |
flags = c4d.DRAWTEXT_HALIGN_CENTER | c4d.DRAWTEXT_VALIGN_CENTER | |
self.DrawSetTextCol(c4d.COLOR_TEXT, c4d.COLOR_TRANS) | |
self.DrawText( | |
str(pose_id), pos.x + size.x / 2, pos.y + (size.y * 0.75) / 2, flags) | |
#Draw the bottom rectangle | |
color = self.get_color_vector(c4d.COLOR_BGEDIT) | |
self.DrawSetPen(color) | |
self.DrawRectangle(pos.x, pos.y + size.y * 0.75, pos.x + size.x, pos.y + size.y) | |
#Draw the name of the pose | |
if pose_selected: | |
self.DrawSetTextCol(c4d.COLOR_TEXTFOCUS, c4d.COLOR_TRANS) | |
else: | |
self.DrawSetTextCol(c4d.COLOR_TEXT, c4d.COLOR_TRANS) | |
#reduce the string for matching our block | |
while pose_name: | |
if self.DrawGetTextWidth(pose_name) > self.tile_width: | |
pose_name = pose_name[:-1] | |
else: | |
break | |
self.DrawText( | |
str(pose_name), pos.x + size.x / 2, pos.y + size.y * 0.875 , flags) | |
#Draw outline if selected | |
if pose_selected: | |
self.DrawBorder(c4d.BORDER_ACTIVE_4, pos.x, pos.y, pos.x + size.x, pos.y + size.y) | |
def DrawMsg(self, x1, y1, x2, y2, msg): | |
""" | |
This method is called to render the content of the view. | |
""" | |
# Enables double buffering to avoid flickering. | |
self.OffScreenOn() | |
# Draw the background. | |
self.DrawSetPen(c4d.COLOR_BG) | |
self.DrawRectangle(x1, y1, x2, y2) | |
# Set the text font, we only need to do this once. | |
self.DrawSetFont(c4d.FONT_BOLD) | |
#set data for drawing | |
self.total_tile_width = self.tile_width + self.tile_space | |
self.max_x_row = self.GetWidth() // self.total_tile_width | |
# Draw all the poses. | |
for tile in self.pose_list.iter_poses(): | |
self.draw_pose(tile.id, tile.name, tile.bmp, tile.selected) | |
def calc_pose_id_by_coord(self, coord): | |
""" | |
Retrieve the actual UiPose id from a coord in UiPoseList.all_poses | |
:param coord: the coord in the GeUserArea | |
:return: int. Calc_pos_id can retrieve a pose_id which is not into the list ALWAYS CHECK! | |
""" | |
x_row = coord.x // self.total_tile_width | |
y_row = coord.y // self.tile_height | |
return int(x_row + y_row * self.max_x_row) | |
def get_ctrl_shift_alt(self, msg): | |
""" | |
Get if ctrl / shift or alt is pressed | |
""" | |
bc_keyboard = c4d.BaseContainer() | |
ctrl = False | |
shift = False | |
alt = False | |
self.GetInputState(c4d.BFM_INPUT_KEYBOARD, c4d.BFM_INPUT_CHANNEL, bc_keyboard) | |
if bc_keyboard[c4d.BFM_INPUT_QUALIFIER] & c4d.QCTRL: | |
ctrl = True | |
if bc_keyboard[c4d.BFM_INPUT_QUALIFIER] & c4d.QSHIFT: | |
shift = True | |
if bc_keyboard[c4d.BFM_INPUT_QUALIFIER] & c4d.QALT: | |
alt = True | |
return ctrl, shift, alt | |
def get_coord_clicked(self, msg): | |
""" | |
Get where user click in our GeUserArea | |
""" | |
bc_click = c4d.BaseContainer() | |
self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSELEFT, bc_click) | |
# Get position clicked | |
base = self.Local2Global() | |
coord = Coord(bc_click.GetLong(c4d.BFM_INPUT_X) - base['x'], | |
bc_click.GetLong(c4d.BFM_INPUT_Y) - base['y']) | |
return coord | |
def Message(self, msg, result): | |
if msg.GetId() == c4d.BFM_INPUT: | |
#Get state of ctrl / shift / alt | |
ctrl, shift, alt = self.get_ctrl_shift_alt(msg) | |
#Get position clicked and convert it into pose_id | |
pose_id_click = self.calc_pose_id_by_coord(self.get_coord_clicked(msg)) | |
#Click into a pose | |
if self.pose_list.get_number_of_poses() - 1 >= pose_id_click: | |
#if only shift is pressed | |
if shift and not ctrl and not alt: | |
self.pose_list.toggle_select_pose(pose_id_click) | |
#if only ctrl is pressed | |
elif ctrl and not shift and not alt: | |
self.pose_list.deselect_pose(pose_id_click) | |
#click normal | |
else: | |
self.pose_list.deselect_all() | |
self.pose_list.select_pose(pose_id_click) | |
#redraw change | |
self.Redraw() | |
#Click elsewhere on the GeUserArea | |
else: | |
self.pose_list.deselect_all() | |
self.pose_list.select_pose(pose_id_click) | |
self.Redraw() | |
return super(PoseLibrary_View, self).Message(msg, result) | |
class MainWindows(c4d.gui.GeDialog): | |
""" | |
This dialog contains the :class:`PoseLibrary_View` class and displays | |
it in its very own window. | |
""" | |
# Symbolic IDs for parameters in the dialog. | |
ID_SCORE = 1000 | |
ID_POSELIBRARY = 1001 | |
ID_SLIDER = 1002 | |
def __init__(self): | |
super(MainWindows, self).__init__() | |
self.poses_list = UiPoseList() | |
self.view = PoseLibrary_View(self.poses_list) | |
def Command(self, id, data): | |
if id == self.ID_SLIDER: | |
self.view.set_tile_size(int(100* self.GetFloat(self.ID_SLIDER))) | |
self.view.Redraw() | |
return True | |
def CreateLayout(self): | |
self.SetTitle("Pose Master") | |
# Add the field to display the score in the menu line of | |
# the dialog. | |
self.GroupBeginInMenuLine() | |
self.AddStaticText(self.ID_SCORE, 0) | |
self.GroupEnd() | |
# Add and attach the TFE_View to the main dialog area. | |
self.AddUserArea(self.ID_POSELIBRARY, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT) | |
self.AttachUserArea(self.view, self.ID_POSELIBRARY) | |
self.AddSlider(self.ID_SLIDER, c4d.BFH_SCALEFIT | c4d.BFV_BOTTOM, 10) | |
self.SetFloat(self.ID_SLIDER, 1, 0.5, 3, 0.001) | |
return True | |
class CommandLuncher(c4d.plugins.CommandData): | |
""" | |
This plugin class opens the MainWindows when it is | |
selected from the Plugin's menu. | |
""" | |
@property | |
def dialog(self): | |
""" | |
Returns the MainWindows that is managed by this command | |
plugin. The dialog is generated on-demand to avoid creating it | |
when it would never be opened by the user. | |
""" | |
dialog = getattr(self, '_dialog', None) | |
if dialog is None: | |
dialog = MainWindows() | |
self._dialog = dialog | |
return dialog | |
def register(self): | |
""" | |
Registers the plugin command to Cinema 4D. | |
""" | |
flags = 0 | |
icon = None | |
help_ = "Pose Library for Cinema 4D" | |
return c4d.plugins.RegisterCommandPlugin( | |
PLUGIN_ID, "Pose Master", flags, icon, help_, self) | |
def Execute(self, doc): | |
return self.dialog.Open(c4d.DLG_TYPE_ASYNC, PLUGIN_ID, defaultw=100, defaulth=100) | |
if __name__ == "__main__": | |
# Register the Plugin Command. | |
CommandLuncher().register() | |
print "Pose Master registered. Visit https://github.com/gr4ph0s/PoseMaster" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment