Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login
    1. Maxon Developers Forum
    2. Amazing_iKe
    • Profile
    • Following 1
    • Followers 0
    • Topics 6
    • Posts 9
    • Best 0
    • Controversial 0
    • Groups 0

    Amazing_iKe

    @Amazing_iKe

    0
    Reputation
    7
    Profile views
    9
    Posts
    0
    Followers
    1
    Following
    Joined Last Online
    Location China

    Amazing_iKe Unfollow Follow

    Latest posts made by Amazing_iKe

    • RE: [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      @ferdinand
      This is my GeUserArea class

      class KidooShotManagerUserArea(gui.GeUserArea):
          def __init__(self, dialog=None):
              super(KidooShotManagerUserArea, self).__init__()
              self.dialog = dialog
      
              # 我自己的库实例
              self.GetC4dCont = GetC4DContent()
              self.PathPro = PathProcess()
              self.GuiUtils = GuiUtils()
      
      
              self.padding = 8  # 每张图片之间的间距。
              self.base_size = 128  # 每张图片的基础大小
              self.min_size = 50  # 最小尺寸
              self.max_size = 256  # 最大尺寸
              self.scale_factor = 1.0  # 缩放因子
              
              self.col_count = 4  # 列数
              self.cell_size = self.base_size  # 每个格子的实际大小
      
              self.rounded_radius = 10  # 圆角半径
      
              self.assets = []  # 存储图片
      
              self.selected_index = 0 # 用于记录选中卡片
      
              # 基础颜色变量
              self.bg_color = c4d.Vector(43/255.0, 43/255.0, 43/255.0)  # 背景颜色
              self.bg_color_01 = c4d.Vector(20/255.0, 20/255.0, 20/255.0)  # 背景颜色
      
          def load_card(self, image_assets: list) -> list:
              """
              input:
              - image_assets: [{path, name}, {path, name}, ...] 需要输入图片的路径和名称列表。
      
              returns:
              - assets: 返回[[bitmap, path, name], ...] 的列表,包含加载的图片对象、路径和名称。
              """
              assets = []
      
              for asset in image_assets:
                  path = asset.get('path')
                  name = asset.get('name', '')
                  default_image = os.path.join(_ICONPATH, 'photo-film_white.png')
                  bitmap = None
                  try:
                      if os.path.exists(path):
                          # 如果路径存在,尝试加载图片
                          bitmap = self.GuiUtils.load_bitmap(path)
                      else:
                          # 如果路径不存在,尝试加载默认图片(注:确保默认图片可以被正确加载)
                          bitmap = self.GuiUtils.load_bitmap(default_image)
                  except Exception as e:
                      bitmap = self.GuiUtils.load_bitmap(default_image)
      
                  assets.append([bitmap, path, name])
      
              self.assets = assets
      
              self.Redraw()
      
      
              return assets
      
          def GetMinSize(self):
              """
              动态计算最小尺寸,基于卡片数量和布局参数
              """
              if not self.assets:
                  return 0, 100  # 没有资产时返回最小高度
              
              # 获取当前区域宽度(如果还没有初始化,使用默认值)
              area_width = self.GetWidth() if hasattr(self, 'GetWidth') and self.GetWidth() > 0 else 400
              
              # 计算字体高度
              self.DrawSetFont(c4d.FONT_DEFAULT)
              text_height = self.DrawGetFontHeight()
              
              # 计算网格布局
              cols, cell = self._calc_grid(
                  area_width,
                  self.base_size,
                  self.padding,
                  self.min_size,
                  self.max_size,
                  self.scale_factor
              )
              
              # 计算总行数
              total_cards = len(self.assets)
              rows = (total_cards + cols - 1) // cols  # 向上取整
              
              # 计算总高度
              total_cell_height = cell + text_height + self.padding
              total_height = (rows * (total_cell_height + self.padding)) + self.padding
              
              # 确保最小高度
              min_height = max(int(total_height), 100)
              
              return 0, min_height
      
          def _calc_grid(
              self,
              width: int,
              base_size: int,
              padding: int,
              min_size: int,
              max_size: int,
              scale_factor: float = 1.0,
              mode: str = "auto",
              fixed_cols: int = None,
              fixed_cell: int = None
          ):
              """
              根据区域宽度和配置计算网格列数与 cell 尺寸
              :param width: 总区域宽度
              :param base_size: 基础尺寸(设计稿默认卡片大小)
              :param padding: 内边距
              :param min_size: 允许的最小尺寸
              :param max_size: 允许的最大尺寸
              :param scale_factor: 缩放因子(用户滑块控制,默认1.0)
              :param mode: "auto" | "fixed_cols" | "fixed_cell"
              :param fixed_cols: 固定列数
              :param fixed_cell: 固定格子大小
              """
              if mode == "fixed_cols" and fixed_cols is not None:
                  cols = fixed_cols
                  cell = (width - (cols + 1) * padding) / cols
                  return cols, max(min(cell, max_size), min_size)
      
              if mode == "fixed_cell" and fixed_cell is not None:
                  cols = max(1, int((width + padding) / (fixed_cell + padding)))
                  return cols, fixed_cell
      
              # --- 改进的 auto 模式 ---
              # 用 scale_factor 控制缩放
              scaled_size = base_size * scale_factor
              cell = max(min(scaled_size, max_size), min_size)
      
              cols = max(1, int((width + padding) / (cell + padding)))
              return cols, cell
      
          def _get_cell_rect(
              self,
              index: int,
              area_width: int,
              base_size: int,
              padding: int,
              min_size: int,
              max_size: int,
              text_height: int,
              scale_factor: float = 1.0,
              mode: str = "auto",
              fixed_cols: int = None,
              fixed_cell: int = None
          ):
              """
              获取指定 index 的 cell 坐标区域 (含文字区域)
              """
              cols, cell = self._calc_grid(
                  area_width,
                  base_size,
                  padding,
                  min_size,
                  max_size,
                  scale_factor,
                  mode,
                  fixed_cols=fixed_cols,
                  fixed_cell=fixed_cell
              )
      
              total_cell_height = cell + text_height + padding
              row = index // cols
              col = index % cols
      
              px = padding + col * (cell + padding)
              py = padding + row * (total_cell_height + padding)
      
              rect_x1 = px
              rect_y1 = py
              rect_x2 = px + cell
              rect_y2 = py + cell + text_height + padding
      
              return int(rect_x1), int(rect_y1), int(rect_x2), int(rect_y2)
      
          def set_cell_size(self, size: int = None):
              if size is None:
                  size = self.base_size
              self.scale_factor = size
              
              self.Redraw()
              
          def DrawMsg(self, x1, y1, x2, y2, msg):
              # 绘制背景
              self.SetClippingRegion(x1, y1, x2, y2) # 设置裁剪区域
              self.OffScreenOn() # 开启屏幕裁剪
              self.DrawSetPen(c4d.COLOR_BG) #设置背景颜色
              self.DrawRectangle(x1, y1, x2, y2) # 绘制背景
              self.DrawSetPen(1) #设置背景颜色
              
              
              self.DrawSetFont(c4d.FONT_DEFAULT)
              text_height = self.DrawGetFontHeight()
              
              for index, (bitmap, _, name) in enumerate(self.assets):
                  image_orig_width = bitmap.GetBw()
                  image_orig_height = bitmap.GetBh()
                  
                  
                  
                  # 获取绘制位置
                  # 获取绘制位置(当前索引 index 对应卡片的矩形区域)
                  px, py, px2, py2 = self._get_cell_rect(
                      index=index,                        # 当前要绘制的卡片索引
                      area_width=self.GetWidth(),         # 当前用户区域的总宽度(UI 绘制区域宽度)
                      base_size=self.base_size,           # 期望的基础 cell 大小(默认 128px)
                      padding=self.padding,               # cell 之间的间距(默认 8px)
                      min_size=self.min_size,             # cell 允许的最小尺寸(避免过小)
                      max_size=self.max_size,             # cell 允许的最大尺寸(避免过大)
                      text_height=text_height, # 当前字体的高度,用于计算文字占位
                      scale_factor=self.scale_factor, # 当前缩放因子(用户滑块控制,默认 1.0)
                  )
                  # 绘制背景
                  self.DrawSetPen(self.bg_color_01)
                  self.DrawRectangle(px, py, px2, py2)
                  
                  
                  
                  cols, actual_cell = self._calc_grid(
                  self.GetWidth(),
                  self.base_size,
                  self.padding,
                  self.min_size,
                  self.max_size,
                  self.scale_factor
              )
                  
                  
                  
                  cell = int(actual_cell)
      
      
                  # 缩放计算
                  scale = min(cell / image_orig_width, cell / image_orig_height)
                  dw, dh = int(image_orig_width * scale), int(image_orig_height * scale)
                  ox = px + (cell - dw) // 2
                  oy = py + (cell - dh) // 2
      
                  # 鼠标选中边缘高光绘制
                  if index == self.selected_index:
                      border_thickness = 2  # 边框线宽,可调整
                      border_color = c4d.Vector(1.0, 1.0, 1.0)  # 例如白色边框,可根据需要更改颜色
                      self.DrawSetPen(border_color)  # 设置绘制颜色为高亮边框颜色
                      # 绘制矩形边框,范围比卡片区域每边扩大2像素
                      expand = border_thickness + 2
                      self.DrawFrame(px - expand, py - expand, px2 + expand, py2 + expand, lineWidth=border_thickness)
      
      
      
                  # 绘制图片背景
                  self.DrawSetPen(1001)
                  self.DrawRectangle(px, py, px + cell, py + cell)
      
                  # 绘制图片
                  self.DrawBitmap(bitmap, ox, oy, dw, dh, 0, 0, image_orig_width, image_orig_height,
                                  c4d.BMP_ALLOWALPHA | c4d.BMP_NORMALSCALED)
      
                  # 绘制文字
                  text_width = self.DrawGetTextWidth(name)
                  text_x = px + cell // 2 - text_width // 2
                  text_y = py + cell + self.padding // 2
      
                  self.DrawSetTextCol(1, self.bg_color_01)
                  self.DrawSetFont(c4d.FONT_DEFAULT | c4d.FONT_BIG | c4d.FONT_BIG_BOLD)
                  self.DrawText(name, text_x, text_y, c4d.DRAWTEXT_STD_ALIGN)
      
      
          def InputEvent(self, msg):
      
              
              # 判断事件是否为鼠标滚轮,并且有修饰键按下(QUALIFIER,例如 Ctrl/Shift)
              if msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSEWHEEL and msg[c4d.BFM_INPUT_QUALIFIER]:
                  delta = msg.GetInt32(c4d.BFM_INPUT_VALUE)  # 获取滚轮的数值(正=向上,负=向下)
      
                  size = self.scale_factor  # 当前缩放因子
      
                  # 根据滚轮方向调整缩放因子
                  if delta < 0:
                      size -= 0.05  # 滚轮向下 → 缩小
                  else:
                      size += 0.05  # 滚轮向上 → 放大
      
                  # 限制缩放范围在 [0.4, 2.0] 之间
                  size = max(0.4, min(2.0, size))
      
                  # 只有在新值和旧值不同的时候才更新(避免重复刷新)
                  if size != self.scale_factor:
                      self.scale_factor = size
                      self.set_cell_size(self.scale_factor)  # 应用新的缩放因子到 UI
                      
              if msg[c4d.BFM_INPUT_DEVICE] == c4d.BFM_INPUT_MOUSE \
                 and msg[c4d.BFM_INPUT_CHANNEL] == c4d.BFM_INPUT_MOUSELEFT \
                 and msg[c4d.BFM_INPUT_VALUE]:
      
                  local_x = msg[c4d.BFM_INPUT_X]
                  local_y = msg[c4d.BFM_INPUT_Y]
      
                  scroll_y = self.dialog.get_scroll_offset()
      
                  actual_x = local_x
                  actual_y = local_y + scroll_y
      
                  self.on_click(actual_x, actual_y)
              return True
      
          def on_click(self, x, y):
              self.DrawSetFont(c4d.FONT_DEFAULT)
              text_height = self.DrawGetFontHeight()
              for index, (_, _, name) in enumerate(self.assets):
      
                  # 使用完整的 _get_cell_rect 公式来计算卡片矩形
                  px, py, px2, py2 = self._get_cell_rect(
                      index=index,                        # 当前卡片索引
                      area_width=self.GetWidth(),         # 用户区域宽度
                      base_size=self.base_size,           # 基础 cell 大小(默认 128px)
                      padding=self.padding,               # cell 间距
                      min_size=self.min_size,             # 最小 cell 尺寸
                      max_size=self.max_size,             # 最大 cell 尺寸
                      text_height=text_height,       # 文字高度(需要在类里存好)
                      scale_factor=self.scale_factor      # 缩放因子(用户滑动控制)
                  )
      
                  # 判断点击坐标是否在当前卡片矩形内
                  if px <= x <= px2 and py <= y <= py2:
                      self.selected_index = index
                      self.Redraw()  # 触发重绘,显示选中效果
      
                      # 发送点击消息给父级
                      magContainer = BaseContainer(ID_MSG_KIDOO_SHOT_MANAGER_IMAGE_CLICKED)
                      magContainer.SetInt32(0, index)
                      magContainer.SetString(1, name)
                      self.SendParentMessage(magContainer)
                      break
      
      
      
      posted in Cinema 4D SDK
      Amazing_iKeA
      Amazing_iKe
    • [GeUserArea] Click detection selects wrong item when clicking on lower part of cell (image + text area)

      Hello,

      I am implementing a custom thumbnail grid browser inside a GeUserArea.
      The grid cell contains an image + text label.

      I calculate the rectangle of each cell using _get_cell_rect(),
      and then check whether the mouse click is inside that rectangle.

      Here is the simplified code:

          def __init__(self, dialog=None):
              super(KidooShotManagerUserArea, self).__init__()
              self.dialog = dialog
      
              # 我自己的库实例
              self.GetC4dCont = GetC4DContent()
              self.PathPro = PathProcess()
              self.GuiUtils = GuiUtils()
      
      
              self.padding = 8  # 每张图片之间的间距。
              self.base_size = 128  # 每张图片的基础大小
              self.min_size = 50  # 最小尺寸
              self.max_size = 256  # 最大尺寸
              self.scale_factor = 1.0  # 缩放因子
              
              self.col_count = 4  # 列数
              self.cell_size = self.base_size  # 每个格子的实际大小
      
              self.rounded_radius = 10  # 圆角半径
      
              self.assets = []  # 存储图片
      
              self.selected_index = 0 # 用于记录选中卡片
      
              # 基础颜色变量
              self.bg_color = c4d.Vector(43/255.0, 43/255.0, 43/255.0)  # 背景颜色
              self.bg_color_01 = c4d.Vector(20/255.0, 20/255.0, 20/255.0)  # 背景颜色
          def _calc_grid(
              self,
              width: int,
              base_size: int,
              padding: int,
              min_size: int,
              max_size: int,
              scale_factor: float = 1.0,
              mode: str = "auto",
              fixed_cols: int = None,
              fixed_cell: int = None
          ):
              """
              根据区域宽度和配置计算网格列数与 cell 尺寸
              :param width: 总区域宽度
              :param base_size: 基础尺寸(设计稿默认卡片大小)
              :param padding: 内边距
              :param min_size: 允许的最小尺寸
              :param max_size: 允许的最大尺寸
              :param scale_factor: 缩放因子(用户滑块控制,默认1.0)
              :param mode: "auto" | "fixed_cols" | "fixed_cell"
              :param fixed_cols: 固定列数
              :param fixed_cell: 固定格子大小
              """
              if mode == "fixed_cols" and fixed_cols is not None:
                  cols = fixed_cols
                  cell = (width - (cols + 1) * padding) / cols
                  return cols, max(min(cell, max_size), min_size)
      
              if mode == "fixed_cell" and fixed_cell is not None:
                  cols = max(1, int((width + padding) / (fixed_cell + padding)))
                  return cols, fixed_cell
      
              # --- 改进的 auto 模式 ---
              # 用 scale_factor 控制缩放
              scaled_size = base_size * scale_factor
              cell = max(min(scaled_size, max_size), min_size)
      
              cols = max(1, int((width + padding) / (cell + padding)))
              return cols, cell
      
      def _get_cell_rect(
          self, index, area_width, base_size, padding,
          min_size, max_size, text_height, scale_factor=1.0
      ):
          cols, cell = self._calc_grid(area_width, base_size, padding, min_size, max_size, scale_factor)
          total_cell_height = cell + text_height + padding
          row = index // cols
          col = index % cols
      
          px = padding + col * (cell + padding)
          py = padding + row * (total_cell_height + padding)
      
          rect_x1 = px
          rect_y1 = py
          rect_x2 = px + cell
          rect_y2 = py + cell + text_height + padding  # <- include text height
      
          return int(rect_x1), int(rect_y1), int(rect_x2), int(rect_y2)
      
          def DrawMsg(self, x1, y1, x2, y2, msg):
              # 绘制背景
              self.SetClippingRegion(x1, y1, x2, y2) # 设置裁剪区域
              self.OffScreenOn() # 开启屏幕裁剪
              self.DrawSetPen(c4d.COLOR_BG) #设置背景颜色
              self.DrawRectangle(x1, y1, x2, y2) # 绘制背景
              self.DrawSetPen(1) #设置背景颜色
              
              # 计算容器大小
              container_width  = x2 - x1
              container_height = y2 - y1
              
              self.DrawSetFont(c4d.FONT_DEFAULT)
              text_height = self.DrawGetFontHeight()
              
              for index, (bitmap, _, name) in enumerate(self.assets):
                  image_orig_width = bitmap.GetBw()
                  image_orig_height = bitmap.GetBh()
                  
                  
                  
                  # 获取绘制位置
                  # 获取绘制位置(当前索引 index 对应卡片的矩形区域)
                  px, py, px2, py2 = self._get_cell_rect(
                      index=index,                        # 当前要绘制的卡片索引
                      area_width=self.GetWidth(),         # 当前用户区域的总宽度(UI 绘制区域宽度)
                      base_size=self.base_size,           # 期望的基础 cell 大小(默认 128px)
                      padding=self.padding,               # cell 之间的间距(默认 8px)
                      min_size=self.min_size,             # cell 允许的最小尺寸(避免过小)
                      max_size=self.max_size,             # cell 允许的最大尺寸(避免过大)
                      text_height=text_height, # 当前字体的高度,用于计算文字占位
                      scale_factor=self.scale_factor, # 当前缩放因子(用户滑块控制,默认 1.0)
                  )
                  self.DrawSetPen(c4d.Vector(0.5, 0.5, 0.5))
                  self.DrawRectangle(px, py, px2, py2)
                  
                  
                  
                  
                  
                  
                  
                  cell = self.cell_size
                  padding = self.padding
      
                  # # 缩放计算
                  # scale = min(cell / image_orig_width, cell / image_orig_height)
                  # dw, dh = int(image_orig_width * scale), int(image_orig_height * scale)
                  # ox = px + (cell - dw) // 2
                  # oy = py + (cell - dh) // 2
      
                  # 鼠标选中边缘高光绘制
                  if index == self.selected_index:
                      border_thickness = 2  # 边框线宽,可调整
                      border_color = c4d.Vector(1.0, 1.0, 1.0)  # 例如白色边框,可根据需要更改颜色
                      self.DrawSetPen(border_color)  # 设置绘制颜色为高亮边框颜色
                      # 绘制矩形边框,范围比卡片区域每边扩大2像素
                      self.DrawFrame(px, py, px2, py2, lineWidth=border_thickness)
      def on_click(self, x, y):
          self.DrawSetFont(c4d.FONT_DEFAULT)
          text_height = self.DrawGetFontHeight()
      
          for index, (_, _, name) in enumerate(self.assets):
              px, py, px2, py2 = self._get_cell_rect(
                  index=index,
                  area_width=self.GetWidth(),
                  base_size=self.base_size,
                  padding=self.padding,
                  min_size=self.min_size,
                  max_size=self.max_size,
                  text_height=text_height,
                  scale_factor=self.scale_factor
              )
      
              if px <= x <= px2 and py <= y <= py2:
                  self.selected_index = index
                  self.Redraw()
                  break
      
      

      When I click on the lower part of a cell (in the text label area),
      sometimes the next cell below gets selected instead of the one I clicked.
      
      So visually I click "inside" the bottom of cell A,
      but on_click reports cell B (the one below A) as selected.
      
      Question
      
      Am I calculating the rectangle wrong?
      
      Should the text label be excluded from the clickable area?
      
      Or is this expected behavior of GeUserArea and I need to treat the image and text separately?

      posted in Cinema 4D SDK windows python 2025 2024
      Amazing_iKeA
      Amazing_iKe
    • Is There a Python API Equivalent to "Make Preview" for Batch Animation Previews with Custom Output Path?

      I would like to create a script or tool that can quickly generate a preview of an animation sequence—similar to the built-in "Make Preview..." (Play > Make Preview...) function in Cinema 4D—but with the ability to specify a custom output path and automate the process fully (without user interaction).
      
      What I have tried:
      
      I have used the c4d.documents.RenderDocument() function in Python, which allows me to render the current document to a bitmap.
      
      However, this approach only supports rendering one frame at a time. If I want to render a sequence, I need to manually loop through each frame, set the time, refresh the scene, and save each image. While this works, it is relatively slow and less convenient compared to the built-in Make Preview workflow.
      
      My Question:
      Is there an official Python API or function that directly exposes the "Make Preview" functionality? Specifically, is there a way to:
      
      Automatically render a frame sequence as a preview (with preview quality/settings)
      
      Specify the output path and filename format
      
      Complete the operation programmatically without manual intervention or dialog windows
      
      Or is the only method currently available to manually loop through frames using RenderDocument, as I am doing now?
      
      Any tips, undocumented features, or future roadmap plans in this area would also be greatly appreciated!
      
      Thank you very much for your attention and support.

      posted in Cinema 4D SDK windows 2025 python
      Amazing_iKeA
      Amazing_iKe
    • RE: DrawRoundedRectangle and DrawBitmapRounded cause UI overflow and artifacts even with SetClippingRegion

      Hi everyone,

      I'm implementing mouse interaction in a custom GeUserArea to select thumbnail items by index.

      I'm currently using mouse coordinates inside MouseInput() and calculating the selected index based on the position and layout of the grid. However, when the user scrolls the view down (for example, inside a scroll group), the calculated index starts to become incorrect — it seems to be offset or accumulated incorrectly.

      Here's a simplified explanation of what I'm doing:

      I calculate the row and column based on the mouse x and y

      Then I compute the index: index = row * col_count + col

      But when I scroll the UI, the y coordinate doesn't account for the scroll offset correctly, so the index becomes wrong

      Is there a proper way to get the scroll offset or compensate for it in GeUserArea, so that the mouse position corresponds correctly to the layout and gives me the accurate index?

      Any help or suggestions would be really appreciated. Thank you!

      posted in Cinema 4D SDK
      Amazing_iKeA
      Amazing_iKe
    • DrawRoundedRectangle and DrawBitmapRounded cause UI overflow and artifacts even with SetClippingRegion

      Hi everyone,
      
      I'm encountering an issue when using DrawRoundedRectangle and DrawBitmapRounded in my custom GeUserArea.
      
      Whenever I draw elements with rounded corners, the UI content seems to overflow its intended boundary, and sometimes leaves visual artifacts (residual pixels). I've already added SetClippingRegion at the beginning of the DrawMsg
      However, it doesn't seem to help with the overflow or visual glitches when using the rounded drawing functions.
      
      Interestingly, when I switch back to DrawRectangle and DrawBitmap (without rounded corners), the problem goes away completely.
      
      Is this a known issue? Or is there any recommended way to safely use rounded drawing functions without causing overflow or artifacts?
      
      Thanks a lot for your help!!

      edit: @ferdinand -- redacted --

      import c4d, os
      from c4d import gui, bitmaps
      
      class KidooShotManagerUserArea(gui.GeUserArea):
          def __init__(self):
      
              self.padding = 8  # 每张图片之间的间距。
              self.base_size = 128  # 每张图片的基础大小
              self.min_size = 50  # 最小尺寸
              self.max_size = 256  # 最大尺寸
      
              self.col_count = 4  # 列数
              self.cell_size = self.base_size  # 每个格子的实际大小
      
              self.assets = []  # 存储图片
              print("KidooShotManagerUserArea initialized.")
          def load_card(self, image_assets : list) -> list:
              """
              input:
              - image_assets: [{path, name}, {path, name}, ...] 需要输入图片的路径和名称列表。
      
              returns:
              - assets: 返回[[bitmap, path, name], ...] 的列表,包含加载的图片对象、路径和名称。
              """
              assets = []
      
              for asset in image_assets:
                  path = asset.get('path')
                  name = asset.get('name', '')
      
      
                  bitmap = bitmaps.BaseBitmap()
                  try:
                      if os.path.exists(path):
                          # 如果路径存在,尝试加载图片
                          bitmap.InitWith(path)
                      else:
                          # 如果路径不存在,尝试加载默认图片(注:确保默认图片可以被正确加载)
                          bitmap.InitWith(r'C:\Users\19252\Desktop\test\alexander-thieme-2.jpg')
                  except Exception as e:
                      bitmap.InitWith(r'C:\Users\19252\Desktop\test\alexander-thieme-2.jpg')
      
                  assets.append([bitmap, path, name])
      
              self.assets = assets
      
              self.Redraw()
      
              return assets
      
          def GetMinSize(self):
              return (100, 10000)
      
          def _calc_grid(self, width: int):
      
              """
              input:
              - width: width 是当前绘图区域的总宽度(比如 800 像素)
      
              returns:
              - cols: 列数
              - cell: 每个格子的实际大小
      
              """
      
      
              # 每个 cell 大小大概是 base_size,加上 padding 是单个格子的占位宽度
              tentative = max(1, int((width - self.padding) / (self.base_size + self.padding)))
      
              for cols in range(tentative, 0, -1):
                  # 用当前列数 cols 反推每个格子的实际大小 cell
                  cell = (width - self.padding) / cols - self.padding
      
                  # 如果 cell 大小在允许范围内(不太小也不太大)
                  if self.min_size <= cell <= self.max_size:
                      # 说明这个 cols 和 cell 是合适的,就返回
                      return cols, int(cell)
              return 1, self.min_size
      
      
      
          def DrawMsg(self, x1, y1, x2, y2, msg):
              self.OffScreenOn()# 开启离屏绘制
      
              self.SetClippingRegion(x1, y1, x2, y2) # 设置绘制区域
              self.DrawSetPen(c4d.COLOR_BG) # 设置绘画区域的背景颜色
              self.DrawRectangle(x1, y1, x2, y2) # 绘制背景矩形
      
      
      
              for index, (bitmap, path, name) in enumerate(self.assets):
      
                  # 获取原图大小
                  image_orig_width = bitmap.GetBw()
                  image_orig_height = bitmap.GetBh()
      
                  # 获取绘制区域大小
                  area_width = x2 - x1
                  area_height = y2 - y1
      
                  # 获取文字宽度和高度
                  text_width = self.DrawGetTextWidth(name)
                  text_height = self.DrawGetFontHeight()
      
                  # 计算网格布局
                  self.col_count, self.cell_size = self._calc_grid(area_width)
      
                  # 计算每个格子的行列位置
                  row = index // self.col_count
                  col = index % self.col_count
      
                  # print(f"Row: {row}, Col: {col}, Cell Size: {self.cell_size}")
      
                  cell = self.cell_size
                  padding = self.padding
      
                  total_cell_height = cell + text_height + padding
      
                  # 计算每个格子的实际绘制位置
                  px = padding + col * (cell + padding)
                  py = padding + row * (total_cell_height + padding)
      
                  # print(f"Drawing at: ({px}, {py}) with size {self.cell_size}")
      
                  scale = min(cell / image_orig_width, cell / image_orig_height)
                  dw, dh = int(image_orig_width * scale), int(image_orig_height * scale)
      
                  ox = px + (cell - dw) // 2
                  oy = py + (cell - dh) // 2
      
      
                  # 绘制图片背景
                  self.DrawSetPen(1001)  # 设置背景颜色
                  self.DrawRoundedRectangle(px, py, px + cell, py + cell, 5, 5)
      
                  # 绘制图片
                  self.DrawBitmapRounded(bitmap, ox, oy, dw, dh, 0, 0, image_orig_width, image_orig_height,
                      c4d.BMP_ALLOWALPHA | c4d.BMP_NORMALSCALED, 2)
      
                  # 绘制文字
      
                  text_x = px + cell // 2 - text_width //2
                  text_y = py + cell + padding // 2
      
                  self.DrawSetTextCol(1, c4d.COLOR_BG)
                  self.DrawSetFont(c4d.FONT_BIG | c4d.FONT_BIG_BOLD)
                  self.DrawText(name, text_x, text_y, c4d.DRAWTEXT_STD_ALIGN)
      
      
      
      
      
      
      class KidooShotManagerDialog(gui.GeDialog):
          UA_ID = 1000
      
          def __init__(self):
              super(KidooShotManagerDialog, self).__init__()
              self.user_area = KidooShotManagerUserArea()
      
          def CreateLayout(self):
              self.SetTitle("KidooShotManager 图片预览器")
              self.ScrollGroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,c4d.SCROLLGROUP_VERT)
              self.AddUserArea(self.UA_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
              self.AttachUserArea(self.user_area, self.UA_ID)
              self.GroupEnd()
      
              return True
      
          def InitValues(self):
              # 只加载一张图片测试
              path_list = [{'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'001'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'002'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'003'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'004'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'005'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'006'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'007'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'008'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'002'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'003'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'004'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'005'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'006'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'007'},
                           {'path': r'C:\Users\19252\Desktop\test\alexander-thieme-1.jpg', 'name':'008'}]
              self.user_area.load_card(path_list)
              return True
      
      def main():
          dlg = KidooShotManagerDialog()
          dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=800, defaulth=600)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK windows python 2025
      Amazing_iKeA
      Amazing_iKe
    • GeUserArea keeps returning (null) on window move even with DrawMsg returning True

      Hi everyone,

      I'm currently working with a custom GeUserArea, and I've noticed a strange behavior: when I move the window around, the area keeps returning (null) repeatedly, as if it's constantly redrawing. This causes an unclean or "flickering" appearance.

      Is there something I might be missing? Or is there a recommended way to prevent these unnecessary redraws during window movement?

      Any insight or workaround would be greatly appreciated!

      Thanks in advance.

      import c4d, os
      from c4d import gui, bitmaps
      
      class KidooShotManagerUserArea(gui.GeUserArea):
          def __init__(self):
              super(KidooShotManagerUserArea, self).__init__()
      
              self.padding = 8  # 每张图片之间的间距。
              self.base_size = 128  # 每张图片的基础大小
              self.min_size = 50  # 最小尺寸
              self.max_size = 256  # 最大尺寸
      
              self.col_count = 4  # 列数
              self.cell_size = self.base_size  # 每个格子的实际大小
      
              self.assets = []  # 存储图片
      
          def load_card(self, image_assets : list) -> list:
              """
              input:
              - image_assets: [{path, name}, {path, name}, ...] 需要输入图片的路径和名称列表。
      
              returns:
              - assets: 返回[[bitmap, path, name], ...] 的列表,包含加载的图片对象、路径和名称。
              """
              assets = []
      
              for asset in image_assets:
                  path = asset.get('path')
                  name = asset.get('name', '')
      
      
                  bitmap = bitmaps.BaseBitmap()
                  try:
                      if os.path.exists(path):
                          # 如果路径存在,尝试加载图片
                          bitmap.InitWith(path)
                      else:
                          # 如果路径不存在,尝试加载默认图片(注:确保默认图片可以被正确加载)
                          bitmap.InitWith(r'C:\Users\19252\PycharmProjects\DCP_Forge\res\icon\file-video.png')
                  except Exception as e:
                      bitmap.InitWith(r'C:\Users\19252\PycharmProjects\DCP_Forge\res\icon\file-video.png')
      
                  assets.append([bitmap, path, name])
      
              self.assets = assets
      
              self.Redraw()
              c4d.EventAdd()
              return assets
      
          def GetMinSize(self):
              return (100, 100)
      
          def _calc_grid(self, width: int):
      
              """
              input:
              - width: width 是当前绘图区域的总宽度(比如 800 像素)
      
              returns:
              - cols: 列数
              - cell: 每个格子的实际大小
      
              """
      
      
              # 1. 先估算一个最多能排几列(不一定准确,先试探)
              # 每个 cell 大小大概是 base_size,加上 padding 是单个格子的占位宽度
              tentative = max(1, int((width - self.padding) / (self.base_size + self.padding)))
      
              # 2. 从 tentative 开始尝试减少列数,看是否能得到合法的 cell 尺寸
              for cols in range(tentative, 0, -1):
                  # 用当前列数 cols 反推每个格子的实际大小 cell
                  cell = (width - self.padding) / cols - self.padding
      
                  # 如果 cell 大小在允许范围内(不太小也不太大)
                  if self.min_size <= cell <= self.max_size:
                      # ✅ 说明这个 cols 和 cell 是合适的,就返回
                      return cols, int(cell)
      
              # 3. 如果找不到任何合法的 cell,退而求其次只返回一列+最小尺寸
              return 1, self.min_size
      
      
      
          def DrawMsg(self, x1, y1, x2, y2, msg):
              self.OffScreenOn()# 开启离屏绘制
      
              self.SetClippingRegion(x1, y1, x2, y2) # 设置绘制区域
              self.DrawSetPen(c4d.COLOR_BG) # 设置绘画区域的背景颜色
              self.DrawRectangle(x1, y1, x2, y2) # 绘制背景矩形
      
      
      
              for index, (bitmap, path, name) in enumerate(self.assets):
      
                  # 获取原图大小
                  image_orig_width = bitmap.GetBw()
                  image_orig_height = bitmap.GetBh()
      
                  # 获取绘制区域大小
                  area_width = x2 - x1
                  area_height = y2 - y1
      
                  # 获取文字宽度和高度
                  text_width = self.DrawGetTextWidth(name)
                  text_height = self.DrawGetFontHeight()
      
                  # 计算网格布局
                  self.col_count, self.cell_size = self._calc_grid(area_width)
      
                  # 计算每个格子的行列位置
                  row = index // self.col_count
                  col = index % self.col_count
      
                  # print(f"Row: {row}, Col: {col}, Cell Size: {self.cell_size}")
      
                  cell = self.cell_size
                  padding = self.padding
      
                  total_cell_height = cell + text_height + padding
      
                  # 计算每个格子的实际绘制位置
                  px = padding + col * (cell + padding)
                  py = padding + row * (total_cell_height + padding)
      
                  # print(f"Drawing at: ({px}, {py}) with size {self.cell_size}")
      
                  scale = min(cell / image_orig_width, cell / image_orig_height)
                  dw, dh = int(image_orig_width * scale), int(image_orig_height * scale)
      
                  ox = px + (cell - dw) // 2
                  oy = py + (cell - dh) // 2
      
      
                  # 绘制图片背景
                  self.DrawSetPen(1001)  # 设置背景颜色
                  self.DrawRoundedRectangle(px, py, px + cell, py + cell, 5, 5)
      
                  # 绘制图片
                  self.DrawBitmapRounded(bitmap, ox, oy, dw, dh, 0, 0, image_orig_width, image_orig_height,
                      c4d.BMP_ALLOWALPHA | c4d.BMP_NORMALSCALED, 2)
      
                  # 绘制文字
      
                  text_x = px + cell // 2 - text_width //2
                  text_y = py + cell + padding // 2
      
                  self.DrawSetTextCol(1, c4d.COLOR_BG)
                  self.DrawSetFont(c4d.FONT_BIG | c4d.FONT_BIG_BOLD)
                  self.DrawText(name, text_x, text_y, c4d.DRAWTEXT_STD_ALIGN)
      
              return True
      
      
      
      
      class KidooShotManagerDialog(gui.GeDialog):
          UA_ID = 1000
      
          def __init__(self):
              super(KidooShotManagerDialog, self).__init__()
              self.user_area = KidooShotManagerUserArea()
      
          def CreateLayout(self):
              self.SetTitle("KidooShotManager 图片预览器")
              self.GroupBegin(0, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=1)
              self.AddUserArea(self.UA_ID, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT)
              self.AttachUserArea(self.user_area, self.UA_ID)
              self.GroupEnd()
              return True
      
          def InitValues(self):
              # 只加载一张图片测试
              path_list = [{'path': r'C:\Users\19252\Desktop\test_image\preview.jpg', 'name':'001'},
                           {'path': r'C:\Users\19252\Desktop\test_image\General 3800x1584 digital art artwork illustration city cityscape.jpg', 'name':'002'},
                           {'path': r'C:\Users\19252\Desktop\test_image\85d3180dc6bf9880bd91792dd6f73f93.jpg', 'name':'003'},
                           {'path': r'C:\Users\19252\Desktop\test_image\file-video1.png', 'name':'004'},
                           {'path': r'C:\Users\19252\Desktop\test_image\preview.jpg', 'name':'001'},
                           {'path': r'C:\Users\19252\Desktop\test_image\General 3800x1584 digital art artwork illustration city cityscape.jpg', 'name':'002'},
                           {'path': r'C:\Users\19252\Desktop\test_image\85d3180dc6bf9880bd91792dd6f73f93.jpg', 'name':'003'},
                           {'path': r'C:\Users\19252\Desktop\test_image\file-video1.png', 'name':'004'}]
              self.user_area.load_card(path_list)
              return True
      
      def main():
          dlg = KidooShotManagerDialog()
          dlg.Open(c4d.DLG_TYPE_ASYNC, defaultw=800, defaulth=600)
      
      if __name__ == '__main__':
          main()
      
      posted in Cinema 4D SDK windows python 2025
      Amazing_iKeA
      Amazing_iKe
    • RE: LayoutFlushGroup Causes Unexpected Changes in Another Group

      @ferdinand Thank you very much for your response and suggestions.

      I’ll make sure to follow best practices and provide a minimal, testable code sample when posting questions in the future. I’ll also give embedding a group a try.

      Thanks again for your support!

      posted in Cinema 4D SDK
      Amazing_iKeA
      Amazing_iKe
    • LayoutFlushGroup Causes Unexpected Changes in Another Group

      Hello,

      I’m encountering an issue when I call self.LayoutFlushGroup(self.SHOT_LIST_GROUP).

      After using this function to clear the SHOT_LIST_GROUP, another group in my layout, self.FOOTAGE_GROUP (with its own ID), unexpectedly changes its appearance or position.

      Image 1: This shows the layout before calling LayoutFlushGroup. Everything looks correct.

      Image 2: This shows the layout after calling LayoutFlushGroup. You can see that FOOTAGE_GROUP has changed unexpectedly.

      Has anyone experienced something similar? I suspect there may be some dependency between groups or an issue with how the layout hierarchy is managed.

      I would really appreciate any insights, suggestions, or recommended best practices to clear and rebuild a group safely without affecting unrelated groups.

      Thank you very much for your time and help!

      11194998c263410a9f84fe67678e8f46.png

      be56452df3564bf0f6c8fb7f88308f94.png

          # 添加位图卡片
          def add_bitmap_card(self, uid : int, path : str, display : str= ''):
              ua = KidooShotManagerUserArea()
              ua.load_card(path, display)
              self.bitmapItems.append(ua)
              self.AddUserArea(uid, c4d.BFH_LEFT | c4d.BFV_TOP)
              self.AttachUserArea(ua, uid)
              return ua
      
          # 创建Kidoo镜头管理器
          def redrawAllKidooShotCards(self):
      
              self.LayoutFlushGroup(self.SHOT_LIST_GROUP)  # 删除组内容
      
              if self.GroupBegin(self.ID_GROUP_UA_REDRAW, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=0, rows=2,
                                 title='重绘卡片组', initw=0, inith=0):
                  for i in range(10):
                      # 加载缩略图
                      self.add_bitmap_card(uid=self.SHOT_MANAGER_USER_AREA + i,
                                           path=r'C:\Users\19252\Desktop\test_image\Anime 3840x2160 BOCCHI THE ROCK! Ryo Yamada Gotou Hitori Nijika I.png',
                                           display='#0001')
      
      
                  self.GroupEnd()  # 明确对应结束此分组
      
              self.LayoutChanged(self.FOOTAGE_GROUP)
              self.LayoutChanged(self.SHOT_MANAGER_GROUP)
          # <<<<<<<<<<<<<<<<<Kidoo镜头管理器内容区域>>>>>>>>>>>>>>>>>>>>
      
          # 创建Layout 布局函数
          def CreateLayout(self):
              # 窗口初始化设置
              self.SetTitle('Kidoo-场景管理器' + f' V.{self.WINDOW_VERSION}' )
      
              # 创建Menu 菜单
              self.CreateMenu()
      
              # 创建完Menu后,刷新镜头数据
              self.Refresh_Kidoo_Shot_Data()
      
              # 总体布局
              if self.GroupBegin(self.MAIN_GROUP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=0, rows=0,
                                  title="Kidoo-场景装配器",initw=0, inith=0, groupflags=c4d.BFV_GRIDGROUP_ALLOW_WEIGHTS):
      
                  # ——— 第一次打开时,初始化主组左右列权重:30% / 70% ———
                  if not hasattr(self, 'main_weights_saved'):
                      # 创建一个 BaseContainer 用于保存主布局的列权重
                      self.main_weights = c4d.BaseContainer()
                      # 告诉 C4D 这是两列的权重设置
                      self.main_weights.SetInt32(c4d.GROUPWEIGHTS_PERCENT_W_CNT, 2)
                      # 设置第一列占比:30%
                      self.main_weights.SetFloat(c4d.GROUPWEIGHTS_PERCENT_W_VAL + 0, 30.0)
                      # 设置第二列占比:70%
                      self.main_weights.SetFloat(c4d.GROUPWEIGHTS_PERCENT_W_VAL + 1, 70.0)
                      # 标记已初始化,后续就不会重复设置默认值
                      self.main_weights_saved = True
      
                  # 每次布局时,将保存的权重加载到 MAIN_GROUP 上,以保持用户上次的拖拽状态
                  self.GroupWeightsLoad(self.MAIN_GROUP, self.main_weights)
      
                  # ——— 左边组:FOOTAGE_GROUP ———
                  if self.GroupBegin(self.FOOTAGE_GROUP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=0, rows=2,
                                  title="镜头",initw=100, inith=0):
                      border_space_pixels = 3
                      self.GroupBorderSpace(border_space_pixels, border_space_pixels, border_space_pixels,
                                            border_space_pixels)  # 设置边框空间
                      self.GroupBorderNoTitle(c4d.BORDER_BLACK | c4d.BORDER_ACTIVE_1)
      
                      # 创建控件
                      self.Kidoo_Shot_Manager_Title()
      
                      # 列表卡片图片
                      if self.ScrollGroupBegin(id=self.SHOT_LIST_GROUP,
                                               flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT,
                                               scrollflags=c4d.SCROLLGROUP_VERT,
                                               initw=0,
                                               inith=0):
                          self.GroupSpace(10, 10)
                          self.GroupBorderNoTitle(c4d.BORDER_ACTIVE_4)
                          self.GroupBorderSpace(5, 5, 5, 5)
      
                          self.redrawAllKidooShotCards()
      
                      self.GroupEnd()
                  self.GroupEnd()
      
                  # ——— 右边组:TAB_GROUP(包含多个子 Tab) ———
                  if self.TabGroupBegin(self.TAB_GROUP , c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, c4d.TAB_TABS):
                      border_space_pixels = 3
                      self.GroupBorderSpace(border_space_pixels, border_space_pixels, border_space_pixels, border_space_pixels)  # 设置边框空间
                      self.GroupBorder(c4d.BORDER_BLACK | c4d.BORDER_ACTIVE_1)
      
                      # 创建SHOP组
                      if self.GroupBegin(id= self.SCENE_BUILDER_GROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=1, rows=0,
                                         title="场景装配", initw=0, inith=0):
                          # 创建控件
                          self.Kidoo_Scene_Builder_Title()
                          self.Kidoo_Scene_Builder_Set_Scene()
                          # self.Kidoo_Scene_Builder_Camera()
                          # self.Kidoo_Scene_Builder_Asset()
      
                      self.GroupEnd()
      
                      # 创建SHOP组
                      if self.GroupBegin(id= self.SHOP_GROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=0, rows=0,
                                         title="镜头", initw=0, inith=0):
                          ...
      
      
      
      
                      self.GroupEnd()
                      # 创建Asset组
                      if self.GroupBegin(id=self.ASSET_GROUP, flags=c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, cols=0, rows=0,
                                         title="资产", initw=0, inith=0):
      
                          ...
      
      
                      self.GroupEnd()
                  self.GroupEnd()
              self.GroupEnd()
      
              return True
      
      posted in Cinema 4D SDK 2025 python windows
      Amazing_iKeA
      Amazing_iKe
    • How to Get and Set Specific Node Attributes in Maxon Graph API?

      I've just finished reading the Graph Descriptions Manual, and I now understand how to use dictionaries to create materials. However, I’m not sure how to query a specific attribute of a node.
      Do I need to first find the node's ID?

      In Maya's API, I can do something like this:```
      code_text

      texture_path = cmds.getAttr(file_node + ".fileTextureName")
      

      But in the Maxon API, I don’t know how to get or set a specific property of a node.
      Could you help me understand how to read or modify specific attributes (e.g., texture path or scale) in an existing graph node?

      posted in Cinema 4D SDK windows python 2025
      Amazing_iKeA
      Amazing_iKe