Last active
May 13, 2025 17:23
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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