Cinema 4D - my Python scripts - convert bezier splines to linear

From NoskeWiki
Jump to: navigation, search

About

NOTE: This page is a daughter page of: Cinema 4D


This page contains code for a Python script I've written for Cinema 4D. To get it working see Cinema 4D - my Python scripts.

convert_bezier_splines_to_linear.py

convert_bezier_splines_to_linear.py

# For each selected curved spline, turns it to a linear spline by
# adding a fixed number of intermediate points to each curved
# line segment (each spline segment with a tangent point on one
# of its two points).
#
# This works for the following Cinema 4D spline types:
#   > Bezier
#   > Cubic
#   > Akima
# WARNING: This does NOT work for: 'B-splines', and since it does
# no checking for types, it's up to you to make sure you don't
# run it on B-splines.
 
 
import c4d
from c4d import gui
import math
 
# Unique id numbers for each of the GUI elements
LBL_USAGE = 1000
LBL_INFO1 = 1001
LBL_INFO2 = 1002
LBL_INFO3 = 1002
GROUP_OPTS = 10000
NUMEDIT_POINTS_TO_ADD = 10001
NUMEDIT_INFLUENCE = 10002
CHK_REPLACE = 10003
 
class OptionsDialog(gui.GeDialog):
  """ Dialog for expanding a open spline.
  """
  def CreateLayout(self):
    self.SetTitle('Convert Bezier Spline To Linear')
    self.AddMultiLineEditText(LBL_USAGE, c4d.BFH_SCALEFIT, inith=52, initw=500,
                              style=c4d.DR_MULTILINE_READONLY)
    self.SetString(LBL_USAGE,
        'USAGE: For any selected bezier/cubic/akima splines, \n'
        '    adds interplated points along curved segments \n'
        '    and converts them to linear splines.')
    # Options:
    self.AddStaticText(LBL_INFO1, c4d.BFH_LEFT, name='For each curved segment:') 
    self.GroupBegin(GROUP_OPTS, c4d.BFH_SCALEFIT, 2, 2)
    self.AddStaticText(LBL_INFO2, c4d.BFH_LEFT, name='  points to add: ')
    self.AddEditNumberArrows(NUMEDIT_POINTS_TO_ADD, c4d.BFH_LEFT, initw=100) 
    self.SetInt32(NUMEDIT_POINTS_TO_ADD, 3)
    self.AddStaticText(LBL_INFO3, c4d.BFH_LEFT, name='  tangent influence: ')
    self.AddEditNumber(NUMEDIT_INFLUENCE, c4d.BFH_SCALEFIT)
    self.SetReal(NUMEDIT_INFLUENCE, 1.5)
    self.GroupEnd()
    self.AddSeparatorH(c4d.BFH_SCALE);
    # Checkbox options:
    self.AddCheckbox(CHK_REPLACE, c4d.BFH_SCALEFIT,
                     initw=1, inith=1, name="replace spline")
    self.SetBool(CHK_REPLACE, True)
    self.AddSeparatorH(c4d.BFH_SCALE);
    # Buttons - an Ok and Cancel button:
    self.AddDlgGroup(c4d.DLG_OK|c4d.DLG_CANCEL);
    self.ok = False
    return True
 
  # React to user's input:
  def Command(self, id, msg):
    if id==c4d.DLG_CANCEL:
      self.Close()
    elif id==c4d.DLG_OK:
      self.ok = True
      self.opt_points_to_add = self.GetInt32(NUMEDIT_POINTS_TO_ADD)
      self.opt_tangent_influence = self.GetReal(NUMEDIT_INFLUENCE)
      self.opt_replace = self.GetBool(CHK_REPLACE)
      self.Close()
    return True
 
 
def add_bezier_points_to_spline(p0, p1, p2, p3,
                                max_pts, spline):
  """Determines the theta (in degrees) between 3 connected points.
 
  1st pt=(x0,y0)   o---------o Influence pt #1 (x1,y1)
                     \_
                       \
                        |   <--- Curve... (here is t=0.5)
                      _/
                     /
  2nd pt=(x3,y3)   o---------o Influence pt #2 (x2,y2)
 
 
  To get location at fraction t:
    x = At^3 + Bt^2 + Ct + D
    y = Et^3 + Ft^2 + Gt + H
 
  To calc adjusters:
    A = x3   - 3*x2 + 3*x1 - x0    E = y3   - 3*y2 + 3*y1 - y0
    B = 3*x2 - 6*x1 + 3*x0         F = 3*y2 - 6*y1 + 3*y0
    C = 3*x1 - 3*x0                G = 3*y1 - 3*y0
    D = x0                         H = y0
 
  See: http://www.tinaja.com/glib/cubemath.pdf
 
  Args:
    p0: c4d.Vector. Coordinates start point.
    p1: c4d.Vector. Coordinates influnce point #1.
    p2: c4d.Vector. Coordinates influnce point #2.
    p3: c4d.Vector. Coordinates end point.
    num_pts: Maximum number new (interpolated) points to add.
    spline: Spline object to append intermediate points to.
 
  Returns:
    number of points added to 'spline'.
  """
  A = p3.x   - 3*p2.x + 3*p1.x - p0.x
  B = 3*p2.x - 6*p1.x + 3*p0.x
  C = 3*p1.x - 3*p0.x
  D = p0.x
 
  E = p3.y   - 3*p2.y + 3*p1.y - p0.y
  F = 3*p2.y - 6*p1.y + 3*p0.y
  G = 3*p1.y - 3*p0.y
  H = p0.y
 
  I = p3.z   - 3*p2.z + 3*p1.z - p0.z
  J = 3*p2.z - 6*p1.z + 3*p0.z
  K = 3*p1.z - 3*p0.z
  L = p0.z
 
  points_added = 0
  for i in range(1, max_pts+1):
    t = float(i) / float(max_pts+1)
    x = (((A*t) + B)*t + C)*t + D
    y = (((E*t) + F)*t + G)*t + H
    z = (((I*t) + J)*t + K)*t + L
    new_psize = spline.GetPointCount()
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, spline)
    spline.ResizeObject(new_psize+1)
    new_pt = c4d.Vector(x, y, z)
    spline.SetPoint(new_psize, new_pt)
    points_added += 1
 
  return points_added
 
 
def add_intermediate_points_to_spline(spline, tangent_influence, max_pts):
  """Takes a bezier 'spline' and adds interpolates points to curved segments.
 
  Args:
    spline: Open spline object to expand.
    tangent_influence: Real. The amount to multiply each influence point by.
    max_pts: Integer. Maximum number of new (interpolated) points to add to
             each segment.
 
  Returns:
    number points added.
  """
  if (spline == None or not spline.CheckType(c4d.Ospline) or
      spline.GetPointCount() < 2):
    return 0;
 
  old_spline = spline.GetClone()
  old_psize = old_spline.GetPointCount()
  old_tsize = old_spline.GetTangentCount()
 
  doc.AddUndo(c4d.UNDOTYPE_CHANGE, spline)
  spline.ResizeObject(0)
  for i in range(0, old_psize):  # For each line segment:
    if i > old_tsize:
      break
    idx_s = (i     % old_psize)
    idx_e = ((i+1) % old_psize)
    pt_start = old_spline.GetPoint(idx_s)    # Current point.
    pt_end   = old_spline.GetPoint(idx_e)    # Next point.
 
    pt_infl1 = old_spline.GetTangent(idx_s)['vr']  # Right vector
    pt_infl2 = old_spline.GetTangent(idx_e)['vl']  # Left vector
 
    # Add current point (with no tangent):
    psize = spline.GetPointCount()
    spline.ResizeObject(psize+1)
    spline.SetPoint(psize, pt_start) 
 
    if not old_spline[c4d.SPLINEOBJECT_CLOSED] and i >= old_psize - 1:
      break
 
    # If any tangents found, add intermediate points:
    is_infl1 = not (pt_infl1.x == 0 and pt_infl1.y == 0 and pt_infl1.z == 0)
    is_infl2 = not (pt_infl2.x == 0 and pt_infl2.y == 0 and pt_infl2.z == 0)
 
    if is_infl1 or is_infl2:
      pt_infl1 *= tangent_influence
      pt_infl2 *= tangent_influence
      pt_infl1 += pt_start
      pt_infl2 += pt_end
      add_bezier_points_to_spline(pt_start, pt_infl1, pt_infl2, pt_end,
                                  max_pts, spline)
 
  return spline.GetPointCount() - old_psize
 
 
def main():
  # Get the selected objects, including children.
  selection = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_CHILDREN)
 
  if len(selection) <= 0:
    gui.MessageDialog('Must select spline object(s)!')
    return
 
  # Open the options dialogue to let users choose their options.
  dlg = OptionsDialog()
  dlg.Open(c4d.DLG_TYPE_MODAL, defaultw=300, defaulth=50)
  if not dlg.ok:
    return
 
  doc.StartUndo()  # Start undo block.
  num_splines_changed = 0
  tot_points_added = 0
  for i in range(0,len(selection)):
    spline = selection[i]
    if not spline.CheckType(c4d.Ospline) or spline.GetPointCount() < 2:
      continue
    num_splines_changed += 1
 
    # Make copy if needed:
    if not dlg.opt_replace:
      new_spline = spline.GetClone()
      doc.InsertObject(new_spline, pred=spline)
      doc.AddUndo(c4d.UNDOTYPE_NEW, new_spline)
      spline = new_spline
 
    # Apply action:
    points_added = add_intermediate_points_to_spline(spline,
                                                     dlg.opt_tangent_influence,
                                                     dlg.opt_points_to_add)
    if points_added > 0:
      num_splines_changed += 1
      tot_points_added += points_added
 
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, spline)
    spline[c4d.SPLINEOBJECT_TYPE] = 0
 
  doc.EndUndo()   # End undo block.
  c4d.EventAdd()  # Update C4D to see changes.
 
  if num_splines_changed != 0:
    gui.MessageDialog(str(num_splines_changed) + ' splines changed \n' +
                      str(tot_points_added) + ' intermediate points added')
 
if __name__=='__main__':
    main()


See Also


Code license
For all of the code on my site... if there are specific instruction or licence comments please leave them in. If you copy my code with minimum modifications to another webpage, or into any code other people will see I would love an acknowledgment to my site.... otherwise, the license for this code is more-or-less WTFPL (do what you want)! If only copying <20 lines, then don't bother. That said - if you'd like to add a web-link to my site www.andrewnoske.com or (better yet) the specific page with code, that's a really sweet gestures! Links to the page may be useful to yourself or your users and helps increase traffic to my site. Hope my code is useful!  :)