Thanks a lot kbar.. i'll watch and learn!
still... the hurdles to compile a c++ plugin are extremely high nowadays.
it would be great if the sdk plugins would be downloadable ready compiled as well
Posts made by indexofrefraction
this "Active Object Dialog" plugin looks very interesting
if it would be understandably described how to compile it / the c++ sdk, we could check it out
but sadly this got so complicated that you need a degree in IT to do it. .-)
FYI, developers.maxon.net is down since about 24 hours....
I have found another way to do this, which is maybe better (?)
because it seems to work around tackling with parallel vectors....
LookAtCamera::Execute() translated to python ...
https://github.com/PluginCafe/cinema4d_cpp_sdk_extended/blob/master/plugins/cinema4dsdk/source/tag/lookatcamera.cpp
def lookat(op, target, pitch=True):
''' orient op to look at target '''
local = (~(op.GetUpMg() * op.GetFrozenMln())) * target.GetMg().off - op.GetRelPos()
hpb = c4d.utils.VectorToHPB(local)
if not pitch: hpb.y = op.GetRelRot().y
hpb.z = op.GetRelRot().z
op.SetRelRot(hpb)
c4d.EventAdd()
It seems to work fine... but for clarity... after checking the Matrix Manual (see end of the page) ...
https://developers.maxon.net/docs/Cinema4DPythonSDK/html/manuals/data_algorithms/classic_api/matrix.html?highlight=matrix
Is this correct / covers all cases to get the targets position in local space?
(~(op.GetUpMg() * op.GetFrozenMln())) * target.GetMg().off
specifically.. what is
m = utils.MatrixMove(op.GetAbsPos()) * utils.HPBToMatrix(op.GetAbsRot()) * utils.MatrixScale(op.GetAbsScale())
in this context? (ps. GetAbsScale misses the brackets in the docs)
and... is this the real code of the Target Tag ?
Hi Ferdinand, yes all solved
and sorry if the gimbal lock was a false interpretation of what was happening.
From reading the old threads I thought the quaternion solution was mandatory to solve this.
for clarity and as an example I'm including a comparison of
your solution (working) and riccardos solution (showing the flip)
but for me the case is closed...
Thanks a lot for helping with this !
Hi Ferdinand,
thanks a lot, seems I'm not the only one missing the good weather outside .-)
The code in the first lookat() function should be abs(ri.x - 1.0),
this comes from the post in the mentioned link. I corrected that in the original post.
(it was just missing in the post, my code had the ri.x)
what I'm on to is a 2 axis ceiling spot.
its a null with a python tag having a target link userdata, then inside a hierarchy with two axes.
atm they are driven by 2 target tags (initialized by the pyTag) one needs pitch on, one needs pitch off.
this works fine, but as i have 900 of these in a file,
i am trying to get rid of the target tags and do all in the pyTag.
I can post the file tomorrow.
what happens with my lookat() is that if you move the target,
the spot sometimes turns upside down 180 degrees and sticks in the ceiling.
with lookatQ() the spot simply doesn't point in the correct direction.
but it would be very interesting to learn how to do it with quaternions.
edit: your solution seems to work nicely, btw!
p.s. i guess ~ is normalise and % is the cross product
Hi,
I have seen and as expected this was discussed already in the past, but I could not find any working example in this forum.
I'd simply like to have a (gimbal lock safe) python replacement for the Target (Expression) Tag.
I'm attaching what I have come up with until now...
two versions #1 without , #2 with quaternion --> sadly #1 has gimbal issues and #2 fails completely
both with a pitch lock like in the Target Tag --> sure this could be done more elegant .-)
It was stated in one of the (below) mentioned threads, that support can't solve such problems for the users,
but this would be such a valuable tool in the quiver that it might be an exception (?)
best, index
def lookat(op, target, pitchlock=0):
''' gimbal lock UNSAFE target expression
this basically works, but results in axis flips sometimes '''
r1 = op.GetRelRot() # backup
p1 = op.GetMg().off # from
p2 = target.GetMg().off # to
tmp = c4d.Vector(0, 1, 0) # temp-up
fo = (p2 - p1).GetNormalized() # forward
ri = tmp.Cross(fo).GetNormalized() # right
up = fo.Cross(ri).GetNormalized() # up
# https://plugincafe.maxon.net/topic/11152/object-target-expression-in-python/2
# try to mitigate gimbal-lock --> UNSAFE, RESULTS IN AXIS FLIPS !
if (fo + tmp).z < 0: up.x = -up.x
if abs(ri.x - 1.0) < 0.001: ri = -ri
m = c4d.Matrix()
m.v1 = ri
m.v2 = up
m.v3 = fo
m.off = p1
op.SetMg(m)
if pitchlock:
r = op.GetRelRot()
op.SetRelRot(c4d.Vector(r.x, r1.y, r.z))
def lookatQ(op, target, pitchlock=0):
''' quaternion based (gimbal lock safe ???) target expression
sadly doesn't work and results in a wrong rotation '''
r1 = op.GetRelRot() # backup
p1 = op.GetMg().off # from
p2 = target.GetMg().off # to
tmp = c4d.Vector(0, 1, 0) # temp-up
fo = (p2 - p1).GetNormalized() # forward
ri = tmp.Cross(fo).GetNormalized() # right
up = fo.Cross(ri).GetNormalized() # up
# copied from
# https://www.gamedev.net/forums/topic/613595-quaternion-lookrotationlookat-up/
w = math.sqrt(1.0 + ri.x + up.y + fo.z) * 0.5
w4_recip = 1.0 / (4.0 * w)
x = (up.z - fo.y) * w4_recip
y = (fo.x - ri.z) * w4_recip
z = (ri.y - up.x) * w4_recip
# given the above is correct, is this correctly applied here ???
q = c4d.Quaternion()
v = c4d.Vector(x, y, z)
q.SetAxis(v, w)
m = q.GetMatrix()
m.off = p1
op.SetMg(m)
if pitchlock:
r = op.GetRelRot()
op.SetRelRot(c4d.Vector(r.x, r1.y, r.z))
ups, sorrryy. my bad .-)
I know that list ... but LOL,
now i cant find the object type for the Cloner Object
hi.. just to report....
the object type for the Subdivision Surface object seems to be missing in the python and C++ sdk...
c4d.Osubdivisionsurface = 1007455
best, index
hi Ferdinand,
i noticed that there is actually an issue with excessive gelist heads....
if you have this :
<c4d.BaseShader object called 'Filter/Filter' with ID 1011128 at 0x7fcebe74eeb0>
+ <c4d.GeListHead object at 0x7fcebe787a70> branch 'Shaders'
+ + <c4d.LayerShader object called 'Layer/Layer' with ID 1011123 at 0x7fcebe787ab0>
+ + + <c4d.BaseShader object called 'Noise/Noise' with ID 1011116 at 0x7fcebe787b90>
GetUp() on the LayerShader seems to return None,
which is a problem if - like in this case - you need to know if a shader is in the middle inside a shader tree,
or if it is at the top / a direct child of the material
this makes something like this necessary to properly "insert" a shader ....
if isinstance(target, c4d.BaseShader):
shader.InsertUnder(target)
else:
target.InsertShader(shader)
tx ferdinand,
and yes intertwined threads .-)
ok, i think i figured out my issue mentioned above ..
it would be nice if this could be confirmed:
materials and shaders can only have shader type nodes,
there must be a shader gelist head at the beginning and followed by normal nodes.
i guess this is was InsertShader() actually does.
now if you InsertShader again in the middle of a tree, you get a second shader gelist head
and as expected this the point is where my simple shader traversing failed.
but.. if you insert the filter shader manually in the material manager,
there is no second shader gelist head, just the one at the beginning.
so i guess that c4d accepts both ways,
but the proper way would be already to use InsertUnder if we're already in a shader type node list (?)
using InsertShader in the middle of a shader list is not necessary and bad practice (?)
a list with multiple shader gelist heads could (should?) be pruned of excessive heads (?)
trying myself...
a walker as above is probably not possible
but this might be something handy for others :
def nodesToList(op, li=None):
''' returns a list of a complete shader tree '''
if not li: li = []
if isinstance(op, c4d.GeListNode):
while op:
if not isinstance(op, c4d.GeListHead):
li.append(op)
if op.GetBranchInfo():
for branch in op.GetBranchInfo():
li = nodesToList(branch["head"].GetDown(), type, li)
li = nodesToList(op.GetDown(), type, li)
op = op.GetNext()
return li
# example
for op in nodesToList(Mat):
print(op)
I have a follow up question.... and post it here because of the sample code above...
if that is not good, could you move it to a new thread?
Isn't there a code example somewhere how to properly walk the shader tree?
i struggle with extending a "simple" walker function like the one below
also if there are different node types wouldnt that need/allow an argument on which ones to follow ?
e.g. would it be possible to walk from doc.GetFirstObject() and just get the tags?
and finally is the walker below at least ok for objects?
(its not my code, just copied from some old library)
def walker(op):
if not op: return None
if op.GetDown():
return op.GetDown()
while op.GetUp() and not op.GetNext():
op = op.GetUp()
return op.GetNext()
hm, actually i think the script does work correctly.
i was additionally checking if the shader tree was correct
and i was doing this using my own shader walker
and it failed, it stopps walking after the Filter Shader
if i use your PrintRelatedNodes() everything looks ok
i will post a follow up question in the mentioned thread :
https://plugincafe.maxon.net/topic/14056/copy-layershader-to-a-new-material/5
hi ferdinand,
our posts probably crossed each other ...
thanks a lot, and yes you understood it right
could you maybe have a look at the code above ?
i replaced my findIndex() by a new findDescId() using your example
but inserting the Layer Shader into the Filter Shader still doesn't work correctly
best, index
ok, I've come up with this and it kind of works...
in the GUI it looks correct, but the shader tree is broken.
only the filter shader is in the tree, the layer shader is missing
i'd be thankful for a hint what i'm missing here.
import c4d
'''
before running the script,
create a material with a layer shader in the color slot
'''
def main():
li = doc.GetActiveMaterials()
if not li: li = doc.GetMaterials()
mat = li[0]
layerShaders = []
sh = mat.GetFirstShader()
while sh is not None:
if sh.GetType() == c4d.Xlayer:
layerShaders.append(sh)
sh = walker(sh)
for shLayer in layerShaders:
parent = shLayer.GetUp()
if parent is None:
parent = mat
# index = findIndex(parent, shLayer) # old
index = findDescId(parent, shLayer) # new :)
if index:
shLayer = shLayer.GetClone()
parent[index].Remove() # the original != the clone
shFilter = c4d.BaseShader(c4d.Xfilter)
shFilter.InsertShader(shLayer)
shFilter[c4d.SLA_FILTER_TEXTURE] = shLayer
parent.InsertShader(shFilter)
parent[index] = shFilter
#def findIndex(parent, shader):
# description = parent.GetDescription(c4d.DESCFLAGS_DESC_0)
# for bc, paramid, groupid in description:
# if paramid:
# index = paramid[0].id + 0
# try:
# if parent[index] == shader:
# return index
# except AttributeError:
# pass
# return None
def findDescId(parent, shader):
description = parent.GetDescription(c4d.DESCFLAGS_DESC_NONE)
for bc, descId, _ in description:
if bc[c4d.DESC_SHADERLINKFLAG] == True:
if parent[descId] == shader:
return descId
def walker(op):
if not op: return None
if op.GetDown():
return op.GetDown()
while op.GetUp() and not op.GetNext():
op = op.GetUp()
return op.GetNext()
if __name__=='__main__':
main()
Hi...
I know how to add shaders to materials or other shaders, set their parameters etc.
but i can't wrap my head around on how to insert a shader into an unknown tree.
explanation ..
lets take Mat[c4d.MATERIAL_COLOR_SHADER] with an unknown content.
now lets use a walker to process the complete shader hierarchy
and for any Layer Shader, create a Filter Shader in place, with the Layer Shader in the link.
creating the Filter Shader and adding the Layer Shader inside is easy,
but how can you add the Filter Shader to the (former) parent of the Layer Shader ?
Is there a possibility to know which slot the Layer Shader was in?
Eg. even if you'd type check and know the parent is a Fusion Shader,
this shader would have 3 possible slots (Blend, Mask and Channel)
and you'd not know which slot the Layer Shader has been in.
Is this possible somehow?
hope this is explained in an understandable way .-)
thanks ferdinand... something to study when this summer heat is over .-)
Hi,
I didn't really find a good post about it...
Is it possible to modify / create Node Materials with Python ?
Is there maybe an example somewhere how to
- add/remove a color shader to/from the diffuse slot
- select an existing color shader and change its value
best, index