Skip to content

Instantly share code, notes, and snippets.

@andreberg
Last active March 25, 2022 05:43
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save andreberg/888507 to your computer and use it in GitHub Desktop.
Save andreberg/888507 to your computer and use it in GitHub Desktop.
[CINEMA 4D: Python Helpers Class] The Helpers class is collection of reusable methods from cgsociety, the Plugin Café and from my own scripts. #cinema4d #c4d #python #class #helpers #computergraphics #cg
class Helpers(object):
"""Contains various helper methods."""
def __init__(self, arg):
super(Helpers, self).__init__()
@staticmethod
def readConfig(filepath=None):
"""
Read settings from a configuration file.
Returns None if config file at filepath doesn't exist.
Returns the config object on success.
"""
result = None
if filepath is None:
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res/", "config.ini")
if os.path.exists(filepath):
config = ConfigParser.ConfigParser()
config.read(filepath)
result = config
return result
@staticmethod
def saveConfig(config, filepath=None):
"""
Save settings to a configuration file.
If filepath is None, it will be assumed to point to res/config.ini.
Returns True if successful, False otherwise.
"""
result = False
if filepath is None:
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res/", "config.ini")
try:
with open(filepath, 'wb') as configfile:
config.write(configfile)
result = True
except Exception, e:
print "*** Caught Exception: %r ***" % e
return result
@staticmethod
def initConfig(defaults, filepath=None):
"""
Initialize configuration file by writing the defaults.
Returns True if config file was created,
False if config file already exists or otherwise.
"""
result = False
if filepath is None:
filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), "res/", "config.ini")
if not os.path.exists(filepath):
config = ConfigParser.ConfigParser(defaults)
result = Helpers.saveConfig(config, filepath)
return result
@staticmethod
def vDeg(v, ishpb=False):
"""Convert each component of vector v to degrees"""
if v is None:
raise ValueError("v can't be None")
if not isinstance(v, c4d.Vector):
raise TypeError("Expected vector, got %s" % type(v))
if ishpb:
res = c4d.Vector(0,0,0)
res.x = Deg(v.x)
res.y = Deg(v.y)
res.z = Deg(v.z)
if res.x >= 180:
res.x -= 360
if res.y >= 180:
res.y -= 360
if res.z >= 180:
res.z -= 360
return res
else:
return c4d.Vector(Deg(v.x), Deg(v.y), Deg(v.z))
@staticmethod
def vRad(v, ishpb=False):
"""Convert each component of vector v to radians"""
if v is None:
raise ValueError("v can't be None")
if not isinstance(v, c4d.Vector):
raise TypeError("Expected vector, got %s" % type(v))
if ishpb:
if v.x >= 180:
v.x -= 360
if v.y >= 180:
v.y -= 360
if v.z >= 180:
v.z -= 360
return c4d.Vector(Rad(v.x), Rad(v.y), Rad(v.z))
@staticmethod
def vAvg(lst):
"""Calculate the average of a list of vectors"""
if lst is None:
raise ValueError("List lst can't be None")
if not isinstance(lst, list):
raise TypeError("Expected list of vectors, got %s" % type(lst))
lstlen = len(lst)
res = c4d.Vector(0,0,0)
if lstlen == 0: return res
for l in lst:
res.x += l.x
res.y += l.y
res.z += l.z
res.x = res.x / float(lstlen)
res.y = res.y / float(lstlen)
res.z = res.z / float(lstlen)
return res
@staticmethod
def select(op):
if not op.GetBit(c4d.BIT_ACTIVE):
op.ToggleBit(c4d.BIT_ACTIVE)
return op.GetBit(c4d.BIT_ACTIVE)
@staticmethod
def selectAdd(op):
"""
Same as select(op) but uses a slightly different mechanism.
See also BaseDocument.SetSelection(sel, mode).
"""
doc = op.GetDocument()
doc.SetActiveObject(op, c4d.SELECTION_ADD)
@staticmethod
def selectGroupMembers(grp):
doc = documents.GetActiveDocument()
for obj in grp:
# add each group member to the selection
# so we can group them in the object manager
#doc.AddUndo(UNDO_BITS, obj)
doc.SetActiveObject(obj, c4d.SELECTION_ADD)
@staticmethod
def selectObjects(objs):
for op in objs:
Helpers.select(op)
@staticmethod
def deselectAll(inObjMngr=False):
"""
Not the same as BaseSelect.DeselectAll().
inObjMngr bool if True, run the deselect command from Object Manager,
else the general one for editor viewport
"""
if inObjMngr is True:
c4d.CallCommand(100004767) # deselect all (Object Manager)
else:
c4d.CallCommand(12113) # deselect all
@staticmethod
def groupObjects(objs, name="Group"):
"""
CallCommand based grouping of objects from a list.
Generally unreliable, because selection state matters.
Use insertUnderNull for better effect.
"""
Helpers.deselectAll(True)
result = None
if objs is None:
return result
if not isinstance(objs, list):
objs = [objs]
else:
return result
for o in objs:
Helpers.select(o)
if DEBUG: print "creating group %s" % name
c4d.CallCommand(100004772) # group objects
doc = documents.GetActiveDocument()
grp = doc.GetActiveObject()
grp.SetName(name)
result = grp
return result
@staticmethod
def groupSelected(name="Group"):
"""
CallCommand based grouping of selected objects.
Generally unreliable, because selection state matters.
Use insertUnderNull for better effect.
"""
result = None
if DEBUG: print "creating group %s" % name
c4d.CallCommand(100004772) # group objects
doc = documents.GetActiveDocument()
grp = doc.GetActiveObject()
grp.SetName(name)
result = grp
return result
@staticmethod
def recurseBranch(obj):
child = obj.GetDown()
while child:
child = child.GetNext()
return Helpers.recurseBranch(child)
@staticmethod
def getNextObject(op, stopobj=None):
if op == None: return None
if op.GetDown(): return op.GetDown()
if stopobj is None:
while not op.GetNext() and op.GetUp():
op = op.GetUp()
else:
while (not op.GetNext() and
op.GetUp() and
op.GetUp() != stopobj):
op = op.GetUp()
return op.GetNext()
@staticmethod
def getActiveObjects(doc):
"""
Same as BaseDocument.GetSelection(), where GetSelection also selects tags and materials.
"""
lst = list()
op = doc.GetFirstObject()
while op:
if op.GetBit(c4d.BIT_ACTIVE) == True:
lst.append(op)
op = Helpers.getNextObject(op)
return lst
@staticmethod
def findObject(name):
"""Find object with name 'name'"""
if name is None: return None
if not isinstance(name, basestring):
raise TypeError("Expected string, got %s" % type(name))
doc = documents.GetActiveDocument()
if not doc: return None
result = None
op = doc.GetFirstObject()
if not op: return None
curname = op.GetName()
if curname == name: return op
op = Helpers.getNextObject(op)
while op:
curname = op.GetName()
if curname == name:
return op
else:
op = Helpers.getNextObject(op)
return result
@staticmethod
def findObjects(name):
"""Find objects with name 'name'"""
if name is None: return None
if not isinstance(name, basestring):
raise TypeError("Expected string, got %s" % type(name))
doc = documents.GetActiveDocument()
if not doc: return None
result = []
op = doc.GetFirstObject()
if not op: return result
while op:
curname = op.GetName()
if curname == name:
result.append(op)
op = Helpers.getNextObject(op)
return result
@staticmethod
def createObject(typ, name, undo=True):
obj = None
try:
doc = documents.GetActiveDocument()
if doc is None: return None
obj = c4d.BaseObject(typ)
obj.SetName(name)
c4d.StopAllThreads()
doc.InsertObject(obj)
if undo is True:
doc.AddUndo(c4d.UNDOTYPE_NEW, obj)
c4d.EventAdd()
except Exception, e:
print "*** Caught Exception: %r ***" % e
return obj
@staticmethod
def insertUnderNull(objs, grp=None, name="Group", copy=False):
"""
Inserts objects under a group (null) object, optionally creating the group.
objs BaseObject can be a single object or a list of objects
grp BaseObject the group to place the objects under
(if None a new null object will be created)
name str name for the new group
copy bool copy the objects if True
Returns the modyfied/created group on success, None on failure.
"""
if grp is None:
grp = Helpers.createObject(c4d.Onull, name)
if copy == True:
objs = [i.GetClone() for i in objs]
if DEBUG: print "inserting objs into group '%s'" % grp.GetName()
if isinstance(objs, list):
for obj in objs:
obj.Remove()
obj.InsertUnder(grp)
else:
objs.Remove()
objs.InsertUnder(grp)
c4d.EventAdd()
return grp
@staticmethod
def togglePolySelection(op):
result = False
totalpolys = op.GetPolygonCount()
psel = op.GetPolygonS()
while psel.HostAlive() == 1:
for poly in xrange(totalpolys):
psel.Toggle(poly)
result = True
break
return result
@staticmethod
def selectAllPolys(op):
result = False
totalpolys = op.GetPolygonCount()
psel = op.GetPolygonS()
while psel.HostAlive() == 1:
for poly in xrange(totalpolys):
psel.Select(poly)
result = True
break
return result
@staticmethod
def polyToList(p):
if not isinstance(p,c4d.CPolygon):
raise TypeError, "CPolygon expected"
if p.c == p.d: return [p.a,p.b,p.c]
return [p.a,p.b,p.c,p.d]
@staticmethod
def listToPoly(l):
if not isinstance(l, list):
raise TypeError, "list or dict expected"
ln = len(l)
if ln < 3:
raise IndexError, "lst must have at least 3 indieces"
elif ln == 3:
return c4d.CPolygon(l[0],l[1],l[2])
else:
return c4d.CPolygon(l[0],l[1],l[2],l[3])
@staticmethod
def calcPolyNormal(p, op):
"""
Calculate the orientation of face normal by using Newell's method.
See calcVertexNormal for an example of usage within the calling context.
"""
if not p: raise ValueError("p can't be None")
if not isinstance(p, c4d.CPolygon):
raise TypeError("Expected CPolygon, got %s" % type(p))
N = c4d.Vector(0,0,0)
lst = Helpers.polyToList(p)
llen = len(lst)
#print "lst = %r" % lst
allp = op.GetAllPoints()
for i in range(llen):
x = i
n = ((i+1) % llen)
#print x, n
vtx = allp[lst[x]]
vtn = allp[lst[n]]
#print "vtx%d = %r (%s)" % (x, vtx, type(vtx))
#print "vtn%d = %r (%s)" % (n, vtn, type(vtn))
N.x += (vtx.y - vtn.y) * (vtx.z + vtn.z)
N.y += (vtx.z - vtn.z) * (vtx.x + vtn.x)
N.z += (vtx.x - vtn.x) * (vtx.y + vtn.y)
return N.GetNormalized()
@staticmethod
def calcVertexNormal(v, idx, op):
"""
Calculate the vertex normal by averaging surrounding face normals.
Usually called from a construct like the following:
for i, point in enumerate(op.GetAllPoints()):
vn = calcVertexNormal(point, i, op):
"""
if not v: raise ValueError("v can't be None")
if not isinstance(v, c4d.Vector):
raise TypeError("Expected Vector, got %s" % type(v))
N = c4d.Vector(0,0,0)
nb = Neighbor()
nb.Init(op)
pntpolys = nb.GetPointPolys(idx)
polys = []
normals = []
allp = op.GetAllPolygons()
for poly in pntpolys:
x = poly
poly = allp[poly]
#print "poly %d = %r (%s)" % (x, poly, type(poly))
polys.append(poly)
normal = Helpers.calcPolyNormal(poly, op)
normals.append(normal)
ln = len(normals)
if ln == 0: return N # beware of div by zero
for n in normals:
N += n
N = c4d.Vector(N.x/ln, N.y/ln, N.z/ln)
return N.GetNormalized()
@staticmethod
def calcThreePointNormal(v1, v2, v3):
"""
Calculate the surface normal of a three point plane.
Doesn't take orientation of neighboring polygons into account.
"""
try:
d1 = - v1 + v3
d2 = - v2 + v3
result = d1.Cross(d2).GetNormalized()
except Exception, e:
raise ValueError("Surface normal calculation failed. The error message was: %r" % e)
return result
@staticmethod
def getGlobalPosition(obj):
return obj.GetMg().off
@staticmethod
def getGlobalRotation(obj):
return MatrixToHPB(obj.GetMg())
@staticmethod
def getGlobalScale(obj):
m = obj.GetMg()
return c4d.Vector(m.v1.GetLength(),
m.v2.GetLength(),
m.v3.GetLength())
@staticmethod
def setGlobalPosition(obj, pos):
m = obj.GetMg()
m.off = pos
obj.SetMg(m)
@staticmethod
def setGlobalRotation(obj, rot):
"""
Please remember, CINEMA 4D handles rotation in radians.
Example for H=10, P=20, B=30:
import c4d
from c4d import utils
#...
hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30))
SetGlobalRotation(obj, hpb) #object's rotation is 10, 20, 30
"""
m = obj.GetMg()
pos = m.off
scale = c4d.Vector( m.v1.GetLength(),
m.v2.GetLength(),
m.v3.GetLength())
m = HPBToMatrix(rot)
m.off = pos
m.v1 = m.v1.GetNormalized() * scale.x
m.v2 = m.v2.GetNormalized() * scale.y
m.v3 = m.v3.GetNormalized() * scale.z
obj.SetMg(m)
@staticmethod
def setGlobalScale(obj, scale):
m = obj.GetMg()
m.v1 = m.v1.GetNormalized() * scale.x
m.v2 = m.v2.GetNormalized() * scale.y
m.v3 = m.v3.GetNormalized() * scale.z
obj.SetMg(m)
@staticmethod
def setAxisRotation(op, rot, local=False):
"""
Set the rotation of the object axis (i.e. keeping points in place).
obj object
rot vector
Courtesy of Scott Ayers, source:
http://www.plugincafe.com/forum/forum_posts.asp?TID=5663&PID=23480#23480
"""
if op is None: return False
if not isinstance(rot, c4d.Vector):
raise TypeError("expected c4d.Vector, got %s" % type(rot))
currot = op.GetRelRot()
if VectorEqual(currot, rot): return
op.SetRelRot(rot)
if local is True:
mat = op.GetMl()
else:
mat = op.GetMg()
inv = ~mat
points = op.GetAllPoints()
for i in range(len(points)):
points[i] = points[i] * inv
op.SetAllPoints(points)
op.Message(c4d.MSG_UPDATE)
c4d.EventAdd()
@staticmethod
def centerObjectAxis(obj):
# check object type
if obj is None or not isinstance(obj, c4d.PointObject):
return True
maxpoints = obj.GetPointCount()
if maxpoints == 0:
return False
# get center of gravity of object vertices in parent's coordinates
cg = c4d.Vector(0,0,0)
scale = 1.0 / maxpoints
for c in xrange(0, maxpoints):
cg += (obj.GetPoint(c) * scale)
ml = obj.GetMl()
cg = ml * cg # GetMulP
# get inverse of matrix of object and the translation vector to new position
inv = ml.__invert__()
trans = inv * (cg - obj.GetRelPos()) # GetMulV
# move object to new position and compensate vertex positions
obj.SetRelPos(cg)
for c in xrange(0, maxpoints):
obj.SetPoint(c, obj.GetPoint(c) - trans)
obj.Message(c4d.MSG_UPDATE)
# compensate positions of child objects
child = obj.GetDown()
while child:
child = child.GetNext()
child.SetRelPos(child.GetRelPos() - trans)
return True
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment