Skip to content

Instantly share code, notes, and snippets.

@NiklasRosenstein
Created December 16, 2016 01:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NiklasRosenstein/9801b159183ede6947d285efd29a3bc7 to your computer and use it in GitHub Desktop.
Save NiklasRosenstein/9801b159183ede6947d285efd29a3bc7 to your computer and use it in GitHub Desktop.
Sample plugin created by knickknack -- http://www.plugincafe.com/forum/forum_posts.asp?TID=7157
"""Offset-Y Spline
Takes a child-spline as input and offset all its points on the y-axis by a specific value. Tangents are unaffected.
 
Usage Instructions
------------------
1. Save in a file called OffsetYSpline.pyp
2. Locate it in the plugin folder and create the resources needed to have the offset value displayed as a parameter
  with PY_OFFSETYSPLINE_OFFSET (ID 1000) in the Attribute Manager
3. Start Cinema
4. Create a generating spline
5. From the Plugin menu, select OffsetYSpline
6. Set the generating spline as input child of the OffsetySpline
"""
# =====================================================================================================================#
# Imports
# =====================================================================================================================#
import os
import c4d
PLUGIN_ID = 12345678 # change it with one obtained from PluginCafe
# =====================================================================================================================#
# Global Functions Definitions
# =====================================================================================================================#
# Global function responsible to set the close status of a spline
def SetClosed(spline, value):
if spline is not None:
spline.SetParameter(c4d.DescID(c4d.DescLevel(c4d.SPLINEOBJECT_CLOSED)), value, c4d.DESCFLAGS_SET_FORCESET)
spline.GetDataInstance().SetBool(c4d.SPLINEOBJECT_CLOSED, value)
return True
return False
# Global function responsible to check the close status of a spline
def IsClosed(spline):
if spline is None:
return False
if spline.GetCacheParent() is not None:
return spline.GetCacheParent().IsClosed()
else:
return spline.IsClosed()
# Global function responsible to copy the spline parameters across a source and a destination
def CopySplineParamsValue(sourceSpline, destSpline):
if sourceSpline is None or destSpline is None:
return False
sourceRealSpline = sourceSpline.GetRealSpline()
if sourceRealSpline is not None:
sourceRealSplineBC = sourceRealSpline.GetDataInstance()
if sourceRealSplineBC is not None:
destSpline.SetParameter(c4d.SPLINEOBJECT_INTERPOLATION, sourceRealSplineBC.GetInt32(c4d.SPLINEOBJECT_INTERPOLATION), c4d.DESCFLAGS_SET_FORCESET)
destSpline.SetParameter(c4d.SPLINEOBJECT_MAXIMUMLENGTH, sourceRealSplineBC.GetFloat(c4d.SPLINEOBJECT_MAXIMUMLENGTH), c4d.DESCFLAGS_SET_FORCESET)
destSpline.SetParameter(c4d.SPLINEOBJECT_SUB, sourceRealSplineBC.GetInt32(c4d.SPLINEOBJECT_SUB), c4d.DESCFLAGS_SET_FORCESET)
destSpline.SetParameter(c4d.SPLINEOBJECT_ANGLE, sourceRealSplineBC.GetFloat(c4d.SPLINEOBJECT_ANGLE), c4d.DESCFLAGS_SET_FORCESET)
return True
return False
# Global function responsible to return the final representation of the spline
def FinalSpline(source):
if source is None:
return None
# check is source can be threated as a spline
if (not source.IsInstanceOf(c4d.Oline)) and (not(source.GetInfo()&c4d.OBJECT_SPLINE)):
return None
if source.GetDeformCache() is not None:
# it seems it's never hit
source = source.GetDeformCache()
# return the spline is a procedural curve
if (not source.IsInstanceOf(c4d.Ospline)) and (source.GetRealSpline() is not None):
return source.GetRealSpline()
return source
# Global function responsible for modifying the spline
def OffsetSpline(inputSpline, offsetValue):
if inputSpline is None:
return None
inputML = inputSpline.GetMl()
# retrieve child points count and type
pointsCnt = inputSpline.GetPointCount()
tangentsCnt = 0
splineType = 0
if (inputSpline.GetType() == c4d.Ospline):
tangentsCnt = inputSpline.GetTangentCount()
splineType = inputSpline.GetInterpolationType()
# allocate the resulting spline
resSpline = c4d.SplineObject(pointsCnt, splineType)
if resSpline is None:
return None
# set the points position and tangency data
for i in range(pointsCnt):
#currPos = inputSpline.GetPoint(i)
currPos = inputML * inputSpline.GetPoint(i)
resSpline.SetPoint(i, c4d.Vector(currPos.x,currPos.y+offsetValue, currPos.z))
# set in case the tangency data
if tangentsCnt != 0:
currTan = inputSpline.GetTangent(i)
resSpline.SetTangent(i, currTan["vl"], currTan["vr"])
# return the computed spline
return resSpline
# Global function responsible to return the first enabled object in a hierarchy
def RecurseOnChild(op):
if op is None:
return None
child = op.GetDown()
if child is None:
return None
#skip deformers
isModifier = child.GetInfo()&c4d.OBJECT_MODIFIER
if isModifier:
return None
# check and return the first active child
if child.GetDeformMode():
return child
else:
return RecurseOnChild(child)
# Global function responsible to recursively check the dirty flag in a hierarchy
def RecursiveCheckDirty(op):
res = 0
if op is None:
return res
current = op
next = op.GetNext()
child = op.GetDown()
res += current.GetDirty(c4d.DIRTYFLAGS_DATA | c4d.DIRTYFLAGS_MATRIX)
if child is not None:
res += RecursiveCheckDirty(child)
if next is not None:
res += RecursiveCheckDirty(next)
return res
# =====================================================================================================================#
# Class Definitions
# =====================================================================================================================#
class OffsetYSpline(c4d.plugins.ObjectData):
def Init(self, op):
if op is None:
return False
bc = op.GetDataInstance()
if bc is None:
return False
bc.SetInt32(c4d.PY_OFFSETYSPLINE_OFFSET, 100)
self._countourChildDirty = -1
self._childDirty = -1
return True
def GetDimension(self, op, mp, rad):
mp.x = 0
mp.y = 0
mp.z = 0
rad.x = 0
rad.y = 0
rad.z = 0
if op is None:
return
bc = op.GetDataInstance()
if op.GetDown() is not None:
rad.x = op.GetDown().GetRad().x
rad.y = op.GetDown().GetRad().y
rad.z = op.GetDown().GetRad().z
mp.x = op.GetMg().off.x
mp.y = op.GetMg().off.y + bc.GetFloat(c4d.PY_OFFSETYSPLINE_OFFSET)
mp.z = op.GetMg().off.z
def CheckDirty(self, op, doc):
if op is None or doc is None:
return
childDirty = 0
child = op.GetDown()
if child is not None:
childDirty = RecursiveCheckDirty(child)
if (childDirty != self._countourChildDirty):
op.SetDirty(c4d.DIRTYFLAGS_DATA)
self._countourChildDirty = childDirty
def GetVirtualObjects(self, op, hh):
if op is None or hh is None:
return
child = None
childSpline = None
resSpline = None
dirty = False
cloneDirty = False
temp = None
childDirty = -1
cache = op.GetCache()
bc = op.GetDataInstance()
if bc is None:
return c4d.BaseObject(c4d.Onull)
offsetValue = bc.GetFloat(c4d.PY_OFFSETYSPLINE_OFFSET)
# look for the first enabled child in order to support hierarchical
child = RecurseOnChild(op)
if child is None:
self._childDirty = -1
self._countourChildDirty = -1
return c4d.BaseObject(c4d.Onull)
# Store now the closure state of the child cause child will be later on overwritten
isChildClosed = IsClosed(child.GetRealSpline())
# Use the GetHierarchyClone and the GetAndCheckHierarchyClone to operate as a two-step
# GetHierarchyClone operates when passing a bool reference in the first step to check
# the dirtyness and a nullptr on a second step to operate the real clone
resGHC = op.GetHierarchyClone(hh, child, c4d.HIERARCHYCLONEFLAGS_ASSPLINE)
if resGHC is None:
resGHC = op.GetAndCheckHierarchyClone(hh, child, c4d.HIERARCHYCLONEFLAGS_ASSPLINE, False)
if resGHC is not None:
cloneDirty = resGHC["dirty"]
temp = resGHC["clone"]
dirty |= cloneDirty
# recursively check the dirty flag for the children (deformers or other generators)
if temp is not None and (temp.IsInstanceOf(c4d.Ospline) or (temp.GetInfo()&c4d.OBJECT_ISSPLINE) or temp.IsInstanceOf(c4d.Oline)):
childDirty = RecursiveCheckDirty(child)
if childSpline is None:
childSpline = temp
child.Touch()
dirty |= op.IsDirty(c4d.DIRTYFLAGS_DATA)
# compare the dirtyness of local and member variable and accordingly update the generator's
# dirty status and the member variable value
dirty |= self._childDirty != childDirty
self._childDirty = childDirty
if not dirty and cache is not None:
return cache
if childSpline is None:
return c4d.BaseObject(c4d.Onull)
#operate the spline modification
resSpline = OffsetSpline(FinalSpline(childSpline), offsetValue)
if resSpline is None:
return c4d.BaseObject(c4d.Onull)
# restore the closing status of the spline
SetClosed(resSpline, isChildClosed)
# copy the spline tags
childSpline.CopyTagsTo(resSpline, True, c4d.NOTOK, c4d.NOTOK)
# copy the spline parameters value
CopySplineParamsValue(childSpline, resSpline)
# notify about the generator update
resSpline.Message(c4d.MSG_UPDATE)
return resSpline
def GetContour(self, op, doc, lod, bt):
if op is None:
return None
if doc is None:
doc = op.GetDocument()
if doc is None:
return None
child = None
childSpline = None
resSpline = None
bc = op.GetDataInstance()
if bc is None:
return None
offsetValue = bc.GetFloat(c4d.PY_OFFSETYSPLINE_OFFSET)
child = RecurseOnChild(op)
if child is None:
self._childDirty = 0
self._countourChildDirty = 0
return None
# Store now the closure state of the child cause child will be later on overwritten
isChildClosed = IsClosed(child.GetRealSpline())
# emulate the GetHierarchyClone in the GetContour by using the SendModelingCommand
temp = child
if temp is not None:
temp = temp.GetClone(c4d.COPYFLAGS_NO_ANIMATION)
if temp is None:
return None
result = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_CURRENTSTATETOOBJECT, list = [temp], doc = doc)
if result[0] is not None and temp is not result[0]:
temp = result[0]
if (temp.GetType() == c4d.Onull):
result2 = c4d.utils.SendModelingCommand(command = c4d.MCOMMAND_JOIN, list = [temp], doc = doc)
if result2[0] is not None and temp is not result2[0]:
temp = result2[0]
if (temp is not None and (temp.IsInstanceOf(c4d.Ospline) or (temp.GetInfo()&c4d.OBJECT_SPLINE) or temp.IsInstanceOf(c4d.Oline))):
if childSpline is None:
childSpline = temp
if childSpline is None:
return None
#operate the spline modification
resSpline = OffsetSpline(FinalSpline(childSpline), offsetValue)
if resSpline is None:
return None
# restore the closing status of the spline
SetClosed(resSpline, isChildClosed)
# copy the spline parameters value
CopySplineParamsValue(childSpline, resSpline)
# notify about the generator update
resSpline.Message(c4d.MSG_UPDATE)
return resSpline
# =====================================================================================================================#
# Plugin registration
# =====================================================================================================================#
if __name__ == "__main__":
c4d.plugins.RegisterObjectPlugin(id=PLUGIN_ID, str="OffsetYSpline",
g=OffsetYSpline,
description="OoffsetYSpline", icon=None,
info=c4d.OBJECT_GENERATOR | c4d.OBJECT_INPUT | c4d.OBJECT_ISSPLINE)
enum {
PY_OFFSETYSPLINE_OFFSET = 1000
};
CONTAINER OoffsetYSpline {
INCLUDE Obase;
GROUP ID_OBJECTPROPERTIES {
REAL PY_OFFSETYSPLINE_OFFSET { UNIT METER; }
}
}
STRINGTABLE OoffsetYSpline {
PY_OFFSETYSPLINE_OFFSET "Offset";
}
@donovankeith
Copy link

donovankeith commented Dec 16, 2016

The .py file should be renamed to .pyp so that C4D will load it as a plugin.

Here's a recommended directory structure from the C++ SDK.

  • myPlugin/
    • myPlugin.pyp
    • res/
      • c4d_symbols.h
      • description/
        • myDescription.h
        • myDescription.res
      • strings_us/
        • c4d_strings.str

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment