Skip to content

Instantly share code, notes, and snippets.

@gr4ph0s
Created May 23, 2017 11:41
Show Gist options
  • Save gr4ph0s/53ca6300b15ba0819a666e9d7acb7c29 to your computer and use it in GitHub Desktop.
Save gr4ph0s/53ca6300b15ba0819a666e9d7acb7c29 to your computer and use it in GitHub Desktop.
Exemple of GeUserArea who imitate browser
# 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