| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090 |
- from .node_container_common import *
- from .base_definitions import MantisNode, NodeSocket, FLOAT_EPSILON
- from .xForm_nodes import xFormArmature, xFormBone
- from .misc_nodes_socket_templates import *
- from math import pi, tau
- def TellClasses():
- return [
- # utility
- InputFloat,
- InputIntNode,
- InputVector,
- InputBoolean,
- InputBooleanThreeTuple,
- InputRotationOrder,
- InputTransformSpace,
- InputString,
- InputMatrix,
- InputWidget,
- InputExistingGeometryObject,
- InputExistingGeometryData,
- InputThemeBoneColorSets,
- InputColorSetPallete,
- UtilityDeclareCollections,
- UtilityCollectionJoin,
- UtilityCollectionHierarchy,
- UtilityGeometryOfXForm,
- UtilityNameOfXForm,
- UtilityPointFromCurve,
- UtilityMatrixFromCurve,
- UtilityMatricesFromCurve,
- UtilityNumberOfCurveSegments,
- UtilityNumberOfSplines,
- UtilityMatrixFromCurveSegment,
- UtilityGetCurvePoint,
- UtilityGetNearestFactorOnCurve,
- UtilityKDChoosePoint,
- UtilityKDChooseXForm,
- UtilityMetaRig,
- UtilityBoneProperties,
- UtilityDriverVariable,
- UtilityDriver,
- UtilityFCurve,
- UtilityKeyframe,
- UtilitySwitch,
- UtilityCombineThreeBool,
- UtilityCombineVector,
- UtilitySeparateVector,
- UtilityCatStrings,
- UtilityGetBoneLength,
- UtilityPointFromBoneMatrix,
- UtilitySetBoneLength,
- UtilityMatrixSetLocation,
- UtilityMatrixGetLocation,
- UtilityMatrixFromXForm,
- UtilityAxesFromMatrix,
- UtilityBoneMatrixHeadTailFlip,
- UtilityMatrixTransform,
- UtilityMatrixInvert,
- UtilityMatrixCompose,
- UtilityMatrixAlignRoll,
- UtilityTransformationMatrix,
- UtilityIntToString,
- UtilityArrayGet,
- UtilityArrayLength,
- UtilitySetBoneMatrixTail,
- # Control flow switches
- UtilityCompare,
- UtilityChoose,
- # useful NoOp:
- UtilityPrint,
- ]
- def matrix_from_head_tail(head, tail, normal=None):
- from mathutils import Vector, Matrix
- if normal is None:
- rotation = Vector((0,1,0)).rotation_difference((tail-head).normalized()).to_matrix()
- m= Matrix.LocRotScale(head, rotation, None)
- else: # construct a basis matrix
- m = Matrix.Identity(3)
- axis = (tail-head).normalized()
- conormal = axis.cross(normal)
- m[0]=conormal
- m[1]=axis
- m[2]=normal
- m = m.transposed().to_4x4()
- m.translation=head.copy()
- m[3][3]=(tail-head).length
- return m
- def get_mesh_from_curve(curve_name : str, execution_id : str, bContext, ribbon=True):
- from bpy import data
- curve = data.objects.get(curve_name)
- assert curve.type == 'CURVE', f"ERROR: object is not a curve: {curve_name}"
- from .utilities import mesh_from_curve
- curve_type='ribbon' if ribbon else 'wire'
- m_name = curve_name+'.'+str(hash(curve_name+'.'+curve_type+'.'+execution_id))
- if not (m := data.meshes.get(m_name)):
- m = mesh_from_curve(curve, bContext, ribbon)
- m.name = m_name
- return m
- def cleanup_curve(curve_name : str, execution_id : str) -> None:
- import bpy
- curve = bpy_object_get_guarded(curve_name)
- m_name = curve_name+'.'+str(hash(curve.name+'.'+ execution_id))
- if (mesh := bpy.data.meshes.get(m_name)):
- bpy.data.meshes.remove(mesh)
- def kd_find(node, points, ref_pt, num_points):
- if num_points == 0:
- raise RuntimeError(f"Cannot find 0 points for {node}")
- from mathutils import kdtree
- kd = kdtree.KDTree(len(points))
- for i, pt in enumerate(points):
- try:
- kd.insert(pt, i)
- except (TypeError, ValueError) as e:
- prRed(f"Cannot get point from for {node}")
- raise e
- kd.balance()
- try:
- if num_points == 1: # make it a list to keep it consistent with
- result = [kd.find(ref_pt)] # find_n which returns a list
- else:
- result = kd.find_n(ref_pt, num_points)
- # the result of kd.find has some other stuff we don't care about
- except (TypeError, ValueError) as e:
- prRed(f"Reference Point {ref_pt} invalid for {node}")
- raise e
- return result
- def array_link_init_hierarchy(new_link):
- " Sets up hierarchy connection/dependencies for links created by Arrays."
- if new_link.is_hierarchy:
- connections = new_link.from_node.hierarchy_connections
- dependencies = new_link.to_node.hierarchy_dependencies
- else:
- connections = new_link.from_node.connections
- dependencies = new_link.to_node.dependencies
- connections.append(new_link.to_node)
- dependencies.append(new_link.from_node)
- def array_choose_relink(node, indices, array_input, output, ):
- """
- Used to choose the correct link to send out of an array-choose node.
- """
- keep_links = []
- for index in indices:
- l = node.inputs[array_input].links[index]
- keep_links.append(l)
- for link in node.outputs[output].links:
- to_node = link.to_node
- for l in keep_links:
- new_link = l.from_node.outputs[l.from_socket].connect(to_node, link.to_socket)
- array_link_init_hierarchy(new_link)
- node.rerouted.append(new_link) # so I can access this in Schema Solve
- link.die()
- def array_choose_data(node, data, output):
- """
- Used to choose the correct data to send out of an array-choose node.
- """
- # We need to make new outputs and link from each one based on the data in the array...
- node.outputs.init_sockets([output+"."+str(i).zfill(4) for i in range(len(data)) ])
- for i, data_item in enumerate(data):
- node.parameters[output+"."+str(i).zfill(4)] = data_item
- for link in node.outputs[output].links:
- to_node = link.to_node
- for i in range(len(data)):
- # Make a link from the new output.
- new_link = node.outputs[output+"."+str(i).zfill(4)].connect(to_node, link.to_socket)
- array_link_init_hierarchy(new_link)
- link.die()
- def zero_radius_error_message(node, curve):
- return f"ERROR: cannot get matrix from zero-radius curve point "\
- "in curve object: {curve.name} for node: {node}. "\
- "This is a limitation of Mantis (For now). Please inspect the curve and ensure "\
- "that each curve point has a radius greater than 0. Sometimes, this error is " \
- "caused by drivers. "
- def bpy_object_get_guarded(get_name, node=None):
- result=None
- if not isinstance(get_name, str):
- raise RuntimeError(f"Cannot get object for {node} because the """
- f"requested name is not a string, but {type(get_name)}. ")
- try:
- import bpy
- result = bpy.data.objects.get(get_name)
- except SystemError:
- raise SystemError(f"184 {node} Cannot get object, {get_name}"
- " please report this as a bug.")
- return result
- #*#-------------------------------#++#-------------------------------#*#
- # B A S E C L A S S E S
- #*#-------------------------------#++#-------------------------------#*#
- class SimpleInputNode(MantisNode):
- def __init__(self, signature, base_tree, socket_templates=[]):
- super().__init__(signature, base_tree, socket_templates)
- self.node_type = 'UTILITY'
- self.prepared, self.executed = True, True
- #*#-------------------------------#++#-------------------------------#*#
- # U T I L I T Y N O D E S
- #*#-------------------------------#++#-------------------------------#*#
- class InputFloat(SimpleInputNode):
- '''A node representing float input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = ["Float Input"]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputIntNode(SimpleInputNode):
- '''A node representing integer input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = ["Integer"]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputVector(SimpleInputNode):
- '''A node representing vector input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputBoolean(SimpleInputNode):
- '''A node representing boolean input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputBooleanThreeTuple(SimpleInputNode):
- '''A node representing a tuple of three booleans'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputRotationOrder(SimpleInputNode):
- '''A node representing string input for rotation order'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputTransformSpace(SimpleInputNode):
- '''A node representing string input for transform space'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- def evaluate_input(self, input_name):
- return self.parameters[""]
- class InputString(SimpleInputNode):
- '''A node representing string input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [""]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputMatrix(SimpleInputNode):
- '''A node representing axis-angle quaternion input'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = ["Matrix",]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- class InputThemeBoneColorSets(SimpleInputNode):
- '''A node representing the theme's colors'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- from .base_definitions import MantisSocketTemplate
- outputs = []
- for i in range(20):
- outputs.append (MantisSocketTemplate(
- name = f"Color {str(i).zfill(2)}",
- ))
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- # we'll go ahead and fill them here
- def fill_parameters(self, ui_node=None):
- if not ui_node:
- from .utilities import get_node_prototype
- ui_node = get_node_prototype(self.ui_signature, self.base_tree)
- for i in range(20):
- self.parameters[f"Color {str(i).zfill(2)}"] = ui_node.outputs[i].default_value
- return super().fill_parameters(ui_node)
- class InputColorSetPallete(SimpleInputNode):
- '''A node representing the theme's colors'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- def fill_parameters(self, ui_node=None):
- if not ui_node:
- from .utilities import get_node_prototype
- ui_node = get_node_prototype(self.ui_signature, self.base_tree)
- from .base_definitions import MantisSocketTemplate
- outputs = []
- for o in ui_node.outputs:
- outputs.append (MantisSocketTemplate( name = o.name,))
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- for o in ui_node.outputs:
- self.parameters[o.name] = o.default_value
- return super().fill_parameters(ui_node)
- class UtilityMatrixFromCurve(MantisNode):
- '''Get a matrix from a curve'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatrixFromCurveSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Matrix
- import bpy
- mat = Matrix.Identity(4)
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- prRed(f"WARN: No curve found for {self}. Using an identity matrix instead.")
- mat[3][3] = 1.0
- elif curve.type != "CURVE":
- prRed(f"WARN: Object {curve.name} is not a curve. Using an identity matrix instead.")
- mat[3][3] = 1.0
- else:
- if bContext is None: bContext = bpy.context # is this wise?
- m = get_mesh_from_curve(curve.name, self.base_tree.execution_id, bContext)
- from .utilities import data_from_ribbon_mesh
- #
- num_divisions = self.evaluate_input("Total Divisions")
- if num_divisions <= 0:
- raise GraphError("The number of divisions in the curve must be 1 or greater.")
- m_index = self.evaluate_input("Matrix Index")
- if m_index >= num_divisions:
- prRed(m_index, num_divisions)
- raise GraphError(f"{self} tried to get a matrix-index greater the total number of divisions."
- "The matrix index starts at 0. You're probably off by +1.")
- spline_index = self.evaluate_input("Spline Index")
- if spline_index > len(curve.data.splines)-1:
- raise GraphError(f"{self} is attempting to read from a spline in {curve.name} that does not exist."
- " Try and reduce the value of Spline Index.")
- splines_factors = [ [] for i in range (spline_index)]
- factors = [1/num_divisions*m_index, 1/num_divisions*(m_index+1)]
- splines_factors.append(factors)
- data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
- if data[spline_index][1][0] < FLOAT_EPSILON: # radius is None:
- raise RuntimeError(zero_radius_error_message(self, curve))
- head=data[spline_index][0][0]
- tail= data[spline_index][0][1]
- axis = (tail-head).normalized()
- if axis.length_squared < FLOAT_EPSILON:
- raise RuntimeError(f"Failed to read the curve {curve.name}.")
- normal=data[spline_index][2][0]
- # make sure the normal is perpendicular to the tail
- from .utilities import make_perpendicular
- normal = make_perpendicular(axis, normal)
- mat = matrix_from_head_tail(head, tail, normal)
- # this is in world space... let's just convert it back
- mat.translation = head - curve.location
- # TODO HACK TODO
- # all the nodes should work in world-space, and it should be the responsibility
- # of the xForm node to convert!
- self.parameters["Matrix"] = mat
- self.prepared = True
- self.executed = True
- def bFinalize(self, bContext=None):
- cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
- class UtilityPointFromCurve(MantisNode):
- '''Get a point from a curve'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, PointFromCurveSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- import bpy
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- raise RuntimeError(f"No curve found for {self}.")
- elif curve.type != "CURVE":
- raise GraphError(f"ERROR: Object {curve.name} is not a curve.")
- else:
- if bContext is None: bContext = bpy.context # is this wise?
- m = get_mesh_from_curve(curve.name, self.base_tree.execution_id, bContext)
- from .utilities import data_from_ribbon_mesh
- #
- num_divisions = 1
- spline_index = self.evaluate_input("Spline Index")
- splines_factors = [ [] for i in range (spline_index)]
- factors = [self.evaluate_input("Factor")]
- splines_factors.append(factors)
- data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
- p = data[spline_index][0][0] - curve.location
- self.parameters["Point"] = p
- self.prepared, self.executed = True, True
- def bFinalize(self, bContext=None):
- cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
- class UtilityMatricesFromCurve(MantisNode):
- '''Get matrices from a curve'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatricesFromCurveSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- import time
- # start_time = time.time()
- #
- from mathutils import Matrix
- import bpy
- m = Matrix.Identity(4)
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- prRed(f"WARN: No curve found for {self}. Using an identity matrix instead.")
- m[3][3] = 1.0
- elif curve.type != "CURVE":
- prRed(f"WARN: Object {curve.name} is not a curve. Using an identity matrix instead.")
- m[3][3] = 1.0
- else:
- if bContext is None: bContext = bpy.context # is this wise?
- mesh = get_mesh_from_curve(curve.name, self.base_tree.execution_id, bContext)
- from .utilities import data_from_ribbon_mesh
- num_divisions = self.evaluate_input("Total Divisions")
- spline_index = self.evaluate_input("Spline Index")
- splines_factors = [ [] for i in range (spline_index)]
- factors = [0.0] + [(1/num_divisions*(i+1)) for i in range(num_divisions)]
- splines_factors.append(factors)
- data = data_from_ribbon_mesh(mesh, splines_factors, curve.matrix_world)
- # [spline_index][points,tangents,normals][datapoint_index]
- from .utilities import make_perpendicular
- matrices=[]
- for i in range(num_divisions):
- if data[spline_index][1][i] < FLOAT_EPSILON: # radius is None:
- raise RuntimeError(zero_radius_error_message(self, curve))
- m = matrix_from_head_tail (
- data[spline_index][0][i], data[spline_index][0][i+1],
- make_perpendicular((data[spline_index][0][i+1]-data[spline_index][0][i]).normalized(), data[spline_index][2][i]),)
- m.translation = data[spline_index][0][i] - curve.location
- matrices.append(m)
- for link in self.outputs["Matrices"].links:
- for i, m in enumerate(matrices):
- name = "Matrix"+str(i).zfill(4)
- if not (out := self.outputs.get(name)): # reuse them if there are multiple links.
- out = self.outputs[name] = NodeSocket(name = name, node=self)
- c = out.connect(link.to_node, link.to_socket)
- # prOrange(c)
- self.parameters[name] = m
- # print (mesh)
- link.die()
- self.prepared = True
- self.executed = True
- # prGreen(f"Matrices from curves took {time.time() - start_time} seconds.")
- def bFinalize(self, bContext=None):
- import bpy
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- m_name = curve.name+'.'+self.base_tree.execution_id
- if (mesh := bpy.data.meshes.get(m_name)):
- prGreen(f"Freeing mesh data {m_name}...")
- bpy.data.meshes.remove(mesh)
- class UtilityNumberOfCurveSegments(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Curve" ,
- "Spline Index" ,
- ]
- outputs = [
- "Number of Segments" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- spline = curve.data.splines[self.evaluate_input("Spline Index")]
- if spline.type == "BEZIER":
- self.parameters["Number of Segments"] = len(spline.bezier_points)-1
- else:
- self.parameters["Number of Segments"] = len(spline.points)-1
- self.prepared = True
- self.executed = True
- class UtilityNumberOfSplines(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, NumberOfSplinesSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- self.parameters["Number of Splines"] = len(curve.data.splines)
- self.prepared, self.executed = True, True
- class UtilityMatrixFromCurveSegment(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatrixFromCurveSegmentSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- import bpy
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- raise RuntimeError(f"No curve found for {self}.")
- elif curve.type != "CURVE":
- raise GraphError(f"ERROR: Object {curve.name} is not a curve.")
- else:
- if bContext is None: bContext = bpy.context # is this wise?
- m = get_mesh_from_curve(curve.name, self.base_tree.execution_id, bContext)
- from .utilities import data_from_ribbon_mesh
- # this section is dumb, but it is because the data_from_ribbon_mesh
- # function is designed to pull data from many splines at once (for optimization)
- # so we have to give it empty splines for each one we skip.
- # TODO: Refactor this to make it so I can select spline index
- spline_index = self.evaluate_input("Spline Index")
- spline = curve.data.splines[spline_index]
- splines_factors = [ [] for i in range (spline_index)]
- factors = [0.0]
- points = spline.bezier_points if spline.type == 'BEZIER' else spline.points
- total_length=0.0
- for i in range(len(points)-1):
- total_length+= (seg_length := (points[i+1].co - points[i].co).length)
- factors.append(seg_length)
- prev_length = 0.0
- for i in range(len(factors)):
- factors[i] = prev_length+factors[i]/total_length
- prev_length=factors[i]
- # Why does this happen? Floating point error?
- if factors[i]>1.0: factors[i] = 1.0
- splines_factors.append(factors)
- #
- data = data_from_ribbon_mesh(m, splines_factors, curve.matrix_world)
- segment_index = self.evaluate_input("Segment Index")
- if data[spline_index][1][segment_index] < FLOAT_EPSILON: # radius is None:
- raise RuntimeError(zero_radius_error_message(self, curve))
- head=data[spline_index][0][segment_index]
- tail= data[spline_index][0][segment_index+1]
- axis = (tail-head).normalized()
- normal=data[spline_index][2][segment_index]
- # make sure the normal is perpendicular to the tail
- from .utilities import make_perpendicular
- normal = make_perpendicular(axis, normal)
- m = matrix_from_head_tail(head, tail, normal)
- m.translation = head - curve.location
- self.parameters["Matrix"] = m
- self.prepared, self.executed = True, True
- def bFinalize(self, bContext=None):
- cleanup_curve(self.evaluate_input("Curve"), self.base_tree.execution_id)
- class UtilityGetCurvePoint(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, GetCurvePointSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext=None):
- import bpy
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- raise RuntimeError(f"No curve found for {self}.")
- elif curve.type != "CURVE":
- raise GraphError(f"ERROR: Object {curve.name} is not a curve.")
- spline = curve.data.splines[self.evaluate_input("Spline Index")]
- if spline.type == 'BEZIER':
- bez_pt = spline.bezier_points[self.evaluate_input("Index")]
- self.parameters["Point"]=bez_pt.co
- self.parameters["Left Handle"]=bez_pt.handle_left
- self.parameters["Right Handle"]=bez_pt.handle_right
- else:
- pt = spline.points[self.evaluate_input("Index")]
- self.parameters["Point"]=(pt.co[0], pt.co[1], pt.co[2])
- self.prepared, self.executed = True, True
- class UtilityGetNearestFactorOnCurve(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, GetNearestFactorOnCurveSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- import bpy
- curve_name = self.evaluate_input("Curve")
- curve = bpy_object_get_guarded( curve_name, self)
- if not curve:
- raise RuntimeError(f"No curve found for {self}.")
- elif curve.type != "CURVE":
- raise GraphError(f"ERROR: Object {curve.name} is not a curve.")
- else:
- if bContext is None: bContext = bpy.context # is this wise?
- m = get_mesh_from_curve(curve.name,
- self.base_tree.execution_id,
- bContext, ribbon=False)
- # this is confusing but I am not re-writing these old functions
- from .utilities import FindNearestPointOnWireMesh as nearest_point
- spline_index = self.evaluate_input("Spline Index")
- ref_pt = self.evaluate_input("Reference Point")
- splines_points = [ [] for i in range (spline_index)]
- splines_points.append([ref_pt])
- pt = nearest_point(m, splines_points)[spline_index][0]
- self.parameters["Factor"] = pt
- self.prepared, self.executed = True, True
- class UtilityKDChoosePoint(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Reference Point" ,
- "Points" ,
- "Number to Find" ,
- ]
- outputs = [
- "Result Point" ,
- "Result Index" ,
- "Result Distance" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.rerouted=[]
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- points= []
- ref_point = self.evaluate_input('Reference Point')
- num_points = self.evaluate_input('Number to Find')
- for i, l in enumerate(self.inputs['Points'].links):
- pt = self.evaluate_input('Points', i)
- points.append(pt)
- if not isinstance(pt, Vector):
- prRed(f"Cannot get point from {l.from_node} for {self}")
- assert ref_point is not None, wrapRed(f"Reference Point {ref_point} is invalid in node {self}")
- result = kd_find(self, points, ref_point, num_points)
- indices = [ found_point[1] for found_point in result ]
- distances = [ found_point[2] for found_point in result ]
- array_choose_relink(self, indices, "Points", "Result Point")
- array_choose_data(self, indices, "Result Index")
- array_choose_data(self, distances, "Result Distance")
- self.prepared, self.executed = True, True
- class UtilityKDChooseXForm(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Reference Point" ,
- "xForm Nodes" ,
- "Get Point Head/Tail" ,
- "Number to Find" ,
- ]
- outputs = [
- "Result xForm" ,
- "Result Index" ,
- "Result Distance" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.rerouted=[]
- def bPrepare(self, bContext = None,):
- if len(self.hierarchy_dependencies)==0 and len(self.hierarchy_connections)==0 and \
- len(self.connections)==0 and len(self.dependencies)==0:
- self.prepared, self.executed = True, True
- return #Either it is already done or it doesn't matter.
- from mathutils import Vector
- points= []
- ref_point = self.evaluate_input('Reference Point')
- num_points = self.evaluate_input('Number to Find')
- for i, l in enumerate(self.inputs['xForm Nodes'].links):
- matrix = l.from_node.evaluate_input('Matrix')
- if matrix is None:
- raise GraphError(f"Cannot get point from {l.from_node} for {self}. Does it have a matrix?")
- pt = matrix.translation
- if head_tail := self.evaluate_input("Get Point Head/Tail"):
- # get the Y-axis of the basis, assume it is normalized
- y_axis = Vector((matrix[0][1],matrix[1][1], matrix[2][1]))
- pt = pt.lerp(pt+y_axis*matrix[3][3], head_tail)
- points.append(pt)
- if not isinstance(pt, Vector):
- prRed(f"Cannot get point from {l.from_node} for {self}")
- assert ref_point is not None, wrapRed(f"Reference Point {ref_point} is invalid in node {self}")
- result = kd_find(self, points, ref_point, num_points)
- indices = [ found_point[1] for found_point in result ]
- distances = [ found_point[2] for found_point in result ]
- array_choose_relink(self, indices, "xForm Nodes", "Result xForm")
- array_choose_data(self, indices, "Result Index")
- array_choose_data(self, distances, "Result Distance")
- self.prepared, self.executed = True, True
- class UtilityMetaRig(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Meta-Armature" ,
- "Meta-Bone" ,
- ]
- outputs = [
- "Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- #kinda clumsy, whatever
- import bpy
- from mathutils import Matrix
- m = Matrix.Identity(4)
- meta_rig = self.evaluate_input("Meta-Armature")
- if meta_rig is None:
- raise RuntimeError("Invalid input for Meta-Armature.")
- meta_bone = self.evaluate_input("Meta-Bone")
- if meta_rig is None or meta_bone is None:
- raise RuntimeError("Invalid input for Meta-Bone.")
- if meta_rig:
- if ( armOb := bpy.data.objects.get(meta_rig) ):
- m = armOb.matrix_world
- if ( b := armOb.data.bones.get(meta_bone)):
- # calculate the correct object-space matrix
- m = Matrix.Identity(3)
- bones = [] # from the last ancestor, mult the matrices until we get to b
- while (b): bones.append(b); b = b.parent
- while (bones): b = bones.pop(); m = m @ b.matrix
- m = Matrix.Translation(b.head_local) @ m.to_4x4()
- #
- m[3][3] = b.length # this is where I arbitrarily decided to store length
- # else:
- # prRed("no bone for MetaRig node ", self)
- else:
- raise RuntimeError(wrapRed(f"No meta-rig input for MetaRig node {self}"))
- self.parameters["Matrix"] = m
- self.prepared = True
- self.executed = True
- class UtilityBoneProperties(SimpleInputNode):
- '''A node representing a bone's gettable properties'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- outputs = [
- "matrix" ,
- "matrix_local" ,
- "matrix_basis" ,
- "head" ,
- "tail" ,
- "length" ,
- "rotation" ,
- "location" ,
- "scale" ,
- ]
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- def fill_parameters(self, prototype=None):
- return
- # TODO this should probably be moved to Links
- class UtilityDriverVariable(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Variable Type" ,
- "Property" ,
- "Property Index" ,
- "Evaluation Space",
- "Rotation Mode" ,
- "xForm 1" ,
- "xForm 2" ,
- ]
- outputs = [
- "Driver Variable",
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "DRIVER" # MUST be run in Pose mode
- self.prepared = True
- def reset_execution(self):
- super().reset_execution()
- # clear this to ensure there are no stale reference pointers
- self.parameters["Driver Variable"] = None
- self.prepared=True
- def evaluate_input(self, input_name):
- if input_name == 'Property':
- if self.inputs.get('Property'):
- if self.inputs['Property'].is_linked:
- trace = trace_single_line(self, input_name)
- # CANNOT UNDERSTATE HOW CRITICAL THIS CHECK IS
- if trace[0][-1].node_type == 'XFORM':
- return trace[1].name # the name of the socket
- return self.parameters["Property"]
- return super().evaluate_input(input_name)
- def GetxForm(self, index=1):
- trace = trace_single_line(self, "xForm 1" if index == 1 else "xForm 2")
- for node in trace[0]:
- if (node.__class__ in [xFormArmature, xFormBone]):
- return node #this will fetch the first one, that's good!
- return None
- def bRelationshipPass(self, bContext = None,):
- prepare_parameters(self)
- #prPurple ("Executing Driver Variable Node")
- xF1 = self.GetxForm()
- xF2 = self.GetxForm(index=2)
- # kinda clumsy
- xForm1, xForm2 = None, None
- if xF1 : xForm1 = xF1.bGetObject()
- if xF2 : xForm2 = xF2.bGetObject()
- v_type = self.evaluate_input("Variable Type")
- i = self.evaluate_input("Property Index"); dVarChannel = ""
- if not isinstance(i, (int, float)):
- raise RuntimeError(f" {self} has invalid input for \"Property Index\".")
- if (i >= 0): #negative values will use the vector property.
- if self.evaluate_input("Property") == 'location':
- if i == 0: dVarChannel = "LOC_X"
- elif i == 1: dVarChannel = "LOC_Y"
- elif i == 2: dVarChannel = "LOC_Z"
- else: raise RuntimeError("Invalid property index for %s" % self)
- if self.evaluate_input("Property") == 'rotation':
- if i == 0: dVarChannel = "ROT_X"
- elif i == 1: dVarChannel = "ROT_Y"
- elif i == 2: dVarChannel = "ROT_Z"
- elif i == 3: dVarChannel = "ROT_W"
- else: raise RuntimeError("Invalid property index for %s" % self)
- if self.evaluate_input("Property") == 'scale':
- if i == 0: dVarChannel = "SCALE_X"
- elif i == 1: dVarChannel = "SCALE_Y"
- elif i == 2: dVarChannel = "SCALE_Z"
- elif i == 3: dVarChannel = "SCALE_AVG"
- else: raise RuntimeError("Invalid property index for %s" % self)
- if self.evaluate_input("Property") == 'scale_average':
- dVarChannel = "SCALE_AVG"
- if dVarChannel: v_type = "TRANSFORMS"
- my_var = {
- "owner" : xForm1, # will be filled in by Driver
- "prop" : self.evaluate_input("Property"), # will be filled in by Driver
- "type" : v_type,
- "space" : self.evaluate_input("Evaluation Space"),
- "rotation_mode" : self.evaluate_input("Rotation Mode"),
- "xForm 1" : xForm1,#self.GetxForm(index = 1),
- "xForm 2" : xForm2,#self.GetxForm(index = 2),
- "channel" : dVarChannel,}
- self.parameters["Driver Variable"] = my_var
- print (my_var['prop'])
- self.executed = True
- class UtilityKeyframe(MantisNode):
- '''A node representing a keyframe for a F-Curve'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Frame" ,
- "Value" ,
- ]
- outputs = [
- "Keyframe" ,
- ]
- additional_parameters = {"Keyframe":{}}
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters( additional_parameters=additional_parameters)
- self.node_type = "DRIVER" # MUST be run in Pose mode
- setup_custom_props(self)
- def bPrepare(self, bContext = None,):
- key = self.parameters["Keyframe"]
- from mathutils import Vector
- key["co"]= Vector( (self.evaluate_input("Frame"), self.evaluate_input("Value"),))
- key["type"]="GENERATED"
- key["interpolation"] = "LINEAR"
- # eventually this will have the right data, TODO
- # self.parameters["Keyframe"] = key
- self.prepared = True
- self.executed = True
- class UtilityFCurve(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Extrapolation Mode",
- ]
- outputs = [
- "fCurve",
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- setup_custom_props(self)
- self.prepared = True
- def reset_execution(self):
- super().reset_execution()
- self.prepared=True
- def evaluate_input(self, input_name):
- return super().evaluate_input(input_name)
- def bTransformPass(self, bContext = None,):
- prepare_parameters(self)
- extrap_mode = self.evaluate_input("Extrapolation Mode")
- keys = [] # ugly but whatever
- #['amplitude', 'back', 'bl_rna', 'co', 'co_ui', 'easing', 'handle_left', 'handle_left_type', 'handle_right', 'handle_right_type',
- # 'interpolation', 'period', 'rna_type', 'select_control_point', 'select_left_handle', 'select_right_handle', 'type']
- for k in self.inputs.keys():
- if k == 'Extrapolation Mode' : continue
- # print (self.inputs[k])
- if (key := self.evaluate_input(k)) is None:
- prOrange(f"WARN: No keyframe connected to {self}:{k}. Skipping Link.")
- else:
- keys.append(key)
- if len(keys) <1:
- prOrange(f"WARN: no keys in fCurve {self}.")
- keys.append(extrap_mode)
- self.parameters["fCurve"] = keys
- self.executed = True
- #TODO make the fCurve data a data class instead of a dict
- class UtilityDriver(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Driver Type" ,
- "Expression" ,
- "fCurve" ,
- ]
- outputs = [
- "Driver",
- ]
- from .drivers import MantisDriver
- additional_parameters = {
- "Driver":MantisDriver(),
- }
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters(additional_parameters=additional_parameters)
- self.node_type = "DRIVER" # MUST be run in Pose mode
- setup_custom_props(self)
- self.prepared = True
- def reset_execution(self):
- super().reset_execution()
- from .drivers import MantisDriver
- self.parameters["Driver"]=MantisDriver()
- self.prepared=True
- def bRelationshipPass(self, bContext = None,):
- prepare_parameters(self)
- from .drivers import MantisDriver
- #prPurple("Executing Driver Node")
- my_vars = []
- keys = self.evaluate_input("fCurve")
- if keys is None or len(keys) <2:
- prWhite(f"INFO: no fCurve connected to {self}; using default fCurve.")
- from mathutils import Vector
- keys = [
- {"co":Vector( (0, 0,)), "type":"GENERATED", "interpolation":"LINEAR" },
- {"co":Vector( (1, 1,)), "type":"GENERATED", "interpolation":"LINEAR" },
- "CONSTANT",]
- for inp in list(self.inputs.keys() )[3:]:
- if (new_var := self.evaluate_input(inp)):
- new_var["name"] = inp
- my_vars.append(new_var)
- else:
- raise RuntimeError(f"Failed to initialize Driver variable for {self}")
- my_driver ={ "owner" : None,
- "prop" : None, # will be filled out in the node that uses the driver
- "expression" : self.evaluate_input("Expression"),
- "ind" : -1, # same here
- "type" : self.evaluate_input("Driver Type"),
- "vars" : my_vars,
- "keys" : keys[:-1],
- "extrapolation" : keys[-1] }
- my_driver = MantisDriver(my_driver)
- self.parameters["Driver"].update(my_driver)
- print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
- self.executed = True
- class UtilitySwitch(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = {
- "Parameter" ,
- "Parameter Index" ,
- "Invert Switch" ,
- }
- outputs = [
- "Driver",
- ]
- from .drivers import MantisDriver
- additional_parameters = {
- "Driver":MantisDriver(),
- }
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters(additional_parameters=additional_parameters)
- self.node_type = "DRIVER" # MUST be run in Pose mode
- self.prepared = True
- def evaluate_input(self, input_name):
- if input_name == 'Parameter':
- if self.inputs['Parameter'].is_connected:
- trace = trace_single_line(self, input_name)
- return trace[1].name # the name of the socket
- return self.parameters["Parameter"]
- return super().evaluate_input(input_name)
- def GetxForm(self,):
- trace = trace_single_line(self, "Parameter" )
- for node in trace[0]:
- if (node.__class__ in [xFormArmature, xFormBone]):
- return node #this will fetch the first one, that's good!
- return None
- def reset_execution(self):
- super().reset_execution()
- from .drivers import MantisDriver
- self.parameters["Driver"]=MantisDriver()
- self.prepared=True
- def bRelationshipPass(self, bContext = None,):
- #prepare_parameters(self)
- #prPurple ("Executing Switch Node")
- xForm = self.GetxForm()
- if xForm : xForm = xForm.bGetObject()
- if not xForm:
- raise RuntimeError("Could not evaluate xForm for %s" % self)
- from .drivers import MantisDriver
- my_driver ={ "owner" : None,
- "prop" : None, # will be filled out in the node that uses the driver
- "ind" : -1, # same here
- "type" : "SCRIPTED",
- "vars" : [ { "owner" : xForm,
- "prop" : self.evaluate_input("Parameter"),
- "name" : "a",
- "type" : "SINGLE_PROP", } ],
- "keys" : [ { "co":(0,0),
- "interpolation": "LINEAR",
- "type":"KEYFRAME",}, #display type
- { "co":(1,1),
- "interpolation": "LINEAR",
- "type":"KEYFRAME",},],
- "extrapolation": 'CONSTANT', }
- my_driver ["expression"] = "a"
- my_driver = MantisDriver(my_driver)
- # this makes it so I can check for type later!
- if self.evaluate_input("Invert Switch") == True:
- my_driver ["expression"] = "1 - a"
- # this way, regardless of what order things are handled, the
- # driver is sent to the next node.
- # In the case of some drivers, the parameter may be sent out
- # before it's filled in (because there is a circular dependency)
- # I want to support this behaviour because Blender supports it.
- # We do not make a copy. We update the driver, so that
- # the same instance is filled out.
- self.parameters["Driver"].update(my_driver)
- print("Initializing driver %s " % (wrapPurple(self.__repr__())) )
- self.executed = True
- class UtilityCombineThreeBool(MantisNode):
- '''A node for combining three booleans into a boolean three-tuple'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "X" ,
- "Y" ,
- "Z" ,
- ]
- outputs = [
- "Three-Bool",
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def reset_execution(self): # need to make sure any references are deleted
- super().reset_execution() # so we prepare the node again to reset them
- if self.parameters["Three-Bool"] is not None:
- for param in self.parameters["Three-Bool"]:
- if isinstance(param, dict):
- self.prepared=False; break
- def bPrepare(self, bContext = None,):
- self.parameters["Three-Bool"] = (
- self.evaluate_input("X"),
- self.evaluate_input("Y"),
- self.evaluate_input("Z"), )
- self.prepared = True
- self.executed = True
- # Note this is a copy of the above. This needs to be de-duplicated.
- class UtilityCombineVector(MantisNode):
- '''A node for combining three floats into a vector'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- super().__init__(signature, base_tree)
- inputs = [
- "X" ,
- "Y" ,
- "Z" ,
- ]
- outputs = [
- "Vector",
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def reset_execution(self): # need to make sure any references are deleted
- super().reset_execution() # so we prepare the node again to reset them
- if self.parameters["Vector"] is not None:
- for param in self.parameters["Vector"]:
- if isinstance(param, dict):
- self.prepared=False; break
- def bPrepare(self, bContext = None,):
- #prPurple("Executing CombineVector Node")
- prepare_parameters(self)
- self.parameters["Vector"] = (
- self.evaluate_input("X"),
- self.evaluate_input("Y"),
- self.evaluate_input("Z"), )
- self.prepared, self.executed = True, True
- class UtilitySeparateVector(MantisNode):
- '''A node for separating a vector into three floats'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Vector"
- ]
- outputs = [
- "X" ,
- "Y" ,
- "Z" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- self.parameters["X"] = self.evaluate_input("Vector")[0]
- self.parameters["Y"] = self.evaluate_input("Vector")[1]
- self.parameters["Z"] = self.evaluate_input("Vector")[2]
- self.prepared, self.executed = True, True
- class UtilityCatStrings(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "String_1" ,
- "String_2" ,
- ]
- outputs = [
- "OutputString" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- self.parameters["OutputString"] = self.evaluate_input("String_1")+self.evaluate_input("String_2")
- self.prepared, self.executed = True, True
- # TODO move this to the Xform file
- class InputWidget(MantisNode):
- '''A node representing an existing object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, InputWidgetSockets)
- self.init_parameters()
- self.node_type = "XFORM"
- def reset_execution(self):
- super().reset_execution()
- self.prepared=False
- def bPrepare(self, bContext=None):
- print(wrapGreen("Executing ")+wrapOrange("InputWidget Node ")+wrapWhite(f"{self}"))
- path = self.evaluate_input('Name')
- axes_flipped = self.evaluate_input('Flip Axes')
- scaling = self.evaluate_input('Scale')
- add_scale_modifier = False
- if scaling[0] != 1.0 or scaling[1] != 1.0 or scaling[2] != 1.0:
- add_scale_modifier = True
- do_mirror = True
- from os import path as os_path
- from .preferences import get_bl_addon_object
- bl_mantis_addon = get_bl_addon_object()
- widgets_path = bl_mantis_addon.preferences.WidgetsLibraryFolder
- path = widgets_path+path # this guards the widgets root so the end-user
- # can easily change the widgets directory without breaking things
- from .utilities import get_default_collection
- collection = get_default_collection(collection_type='WIDGET')
- file_name = os_path.split(path)[-1]
- obj_name = os_path.splitext(file_name)[0]
- obj_name_full = obj_name
- if add_scale_modifier:
- obj_name_full+="_scaled_"+".".join(self.ui_signature[1:])
- if any(axes_flipped):
- obj_name_full+="_flipped_"
- for i, axis in enumerate("XYZ"):
- if axes_flipped[i]: obj_name_full+=axis
- from bpy import data
- # what is this code doing? I thought I already linked it... TODO find out
- if obj_name in data.objects.keys() and not \
- obj_name_full in data.objects.keys():
- self.bObject = data.objects.get(obj_name).copy()
- self.bObject.name = obj_name_full
- collection.objects.link(self.bObject)
- # now check to see if it exists
- elif obj_name_full in data.objects.keys():
- prWhite(f"INFO: {obj_name_full} is already in this .blend file; skipping import.")
- self.bObject = data.objects.get(obj_name_full)
- if any(axes_flipped): # check if we need to add a Flip modifier
- if len(self.bObject.modifiers) > 1 and self.bObject.modifiers[-1].name == "Simple Flip":
- do_mirror=False
- else:
- from .utilities import import_object_from_file
- self.bObject = import_object_from_file(path)
- if any(axes_flipped) or add_scale_modifier:
- self.bObject = self.bObject.copy()
- self.bObject.name = obj_name_full
- collection.objects.link(self.bObject)
- # do the scaling...
- if add_scale_modifier:
- if (scale_modifier := self.bObject.modifiers.get("Scale Object Data")) is None:
- scale_modifier = self.bObject.modifiers.new("Scale Object Data", type='NODES')
- ng = data.node_groups.get("Scale Object Data")
- if ng is None:
- from .geometry_node_graphgen import gen_scale_object_data_modifier
- ng = gen_scale_object_data_modifier()
- scale_modifier.node_group = ng
- scale_modifier['Socket_2']=scaling
- # now we'll check for the mirrors.
- if any(axes_flipped) and do_mirror:
- if (flip_modifier := self.bObject.modifiers.get("Simple Flip")) is None:
- flip_modifier = self.bObject.modifiers.new("Simple Flip", type="NODES")
- ng = data.node_groups.get("Simple Flip")
- if ng is None:
- from .geometry_node_graphgen import gen_simple_flip_modifier
- ng = gen_simple_flip_modifier()
- flip_modifier.node_group = ng
- flip_modifier["Socket_2"]=axes_flipped[0]
- flip_modifier["Socket_3"]=axes_flipped[1]
- flip_modifier["Socket_4"]=axes_flipped[2]
- self.prepared, self.executed = True, True
- def bGetObject(self, mode=''):
- return self.bObject
- # TODO move this to the Xform file
- class InputExistingGeometryObject(MantisNode):
- '''A node representing an existing object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Name" ,
- ]
- outputs = [
- "Object" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "XFORM"
- def reset_execution(self):
- super().reset_execution()
- self.prepared=False
- def bPrepare(self, bContext=None):
- from bpy import data
- ob = None
- if name := self.evaluate_input("Name"):
- ob= data.objects.get( name )
- if ob is None and name:
- prRed(f"No object found with name {name} in {self}")
- self.bObject=ob
- self.prepared, self.executed = True, True
- def bGetObject(self, mode=''):
- return self.bObject
- class InputExistingGeometryData(MantisNode):
- '''A node representing existing object data'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Name" ,
- ]
- outputs = [
- "Geometry" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.prepared = True; self.executed = True
- def reset_execution(self):
- super().reset_execution()
- self.prepared, self.executed = True, True
- # the mode argument is only for interface consistency
- def bGetObject(self, mode=''):
- from bpy import data
- # first try Curve, then try Mesh
- bObject = data.curves.get(self.evaluate_input("Name"))
- if not bObject:
- bObject = data.meshes.get(self.evaluate_input("Name"))
- if bObject is None:
- raise RuntimeError(f"Could not find a mesh or curve datablock named \"{self.evaluate_input('Name')}\" for node {self}")
- return bObject
- class UtilityDeclareCollections(MantisNode):
- '''A node to help manage bone collections'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- self.node_type = "UTILITY"
- self.prepared, self.executed = True, True
- def reset_execution(self):
- super().reset_execution()
- self.prepared, self.executed = True, True
- def fill_parameters(self, ui_node=None):
- if ui_node is None:
- from .utilities import get_node_prototype
- ui_node = get_node_prototype(self.ui_signature, self.base_tree)
- from .base_definitions import MantisSocketTemplate as SockTemplate
- templates=[]
- for out in ui_node.outputs:
- if not (out.name in self.outputs.keys()) :
- templates.append(SockTemplate(name=out.name,
- identifier=out.identifier, is_input=False,))
- self.outputs.init_sockets(templates)
- # now we have our parameters, fill them. This is a little inefficient I guess.
- for out in ui_node.outputs:
- self.parameters[out.name] = out.default_value
- class UtilityCollectionJoin(MantisNode):
- '''A node to help manage bone collections'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, CollectionJoinSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.prepared, self.executed = False, False
- def reset_execution(self):
- super().reset_execution()
- self.prepared, self.executed = False, False
- def bPrepare(self, bContext = None,):
- if self.inputs['Collections'].links:
- bCol_groups = []
- for i, l in enumerate(self.inputs['Collections'].links):
- bCol_group = self.evaluate_input("Collections", index=i)
- if not isinstance(bCol_group, str):
- bCol_group = str(bCol_group)
- prOrange(f"Warning: coercing invalid input ({i}) to String in node: {self}")
- bCol_groups.append(bCol_group)
- bCols = '|'.join(bCol_groups)
- else:
- bCols = self.evaluate_input("Collections")
- if not isinstance(bCols, str):
- bCols = str(bCols)
- prOrange(f"Warning: coercing invalid input to String in node: {self}")
- self.parameters['Collection']=bCols
- self.prepared, self.executed = True, True
- class UtilityCollectionHierarchy(MantisNode):
- '''A node to help manage bone collections'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, CollectionHierarchySockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.prepared, self.executed = False, False
- def reset_execution(self):
- super().reset_execution()
- self.prepared, self.executed = False, False
- def bPrepare(self, bContext = None,):
- parent_col = self.evaluate_input('Parent Collection')
- if not isinstance(parent_col, str):
- parent_col = str(parent_col)
- prOrange(f"Warning: coercing invalid Parent Collection to String in node: {self}")
- child_col = self.evaluate_input('Child Collection')
- if not isinstance(child_col, str):
- child_col = str(child_col)
- prOrange(f"Warning: coercing invalid Child Collection to String in node: {self}")
- result = parent_col +">"+child_col
- self.parameters['Collection']=result
- self.prepared, self.executed = True, True
- class UtilityGeometryOfXForm(MantisNode):
- '''A node representing existing object data'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "xForm" ,
- ]
- outputs = [
- "Geometry" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.prepared = True
- self.executed = True
- def reset_execution(self):
- super().reset_execution()
- self.prepared, self.executed = True, True
- # mode for interface consistency
- def bGetObject(self, mode=''):
- if not (self.inputs.get('xForm') and self.inputs['xForm'].links):
- prOrange(f"WARN: Cannot retrieve data from {self}, there is no xForm node connected.")
- return None
- xf = self.inputs["xForm"].links[0].from_node
- if xf.node_type == 'XFORM':
- xf_ob = xf.bGetObject()
- if (xf_ob is not None) and xf_ob.type in ['MESH', 'CURVE']:
- return xf_ob.data
- prOrange(f"WARN: Cannot retrieve data from {self}, the connected xForm is not a mesh or curve.")
- return None
- class UtilityNameOfXForm(MantisNode):
- '''A node representing existing object data'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "xForm" ,
- ]
- outputs = [
- "Name" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- # mode for interface consistency
- def bPrepare(self, bContext = None,):
- if not (self.inputs.get('xForm') and self.inputs['xForm'].links):
- raise RuntimeError( f"WARN: Cannot retrieve data from {self},"
- " there is no xForm node connected.")
- xf = self.inputs["xForm"].links[0].from_node
- self.parameters["Name"] = xf.evaluate_input('Name')
- self.prepared, self.executed = True, True
- class UtilityGetBoneLength(MantisNode):
- '''A node to get the length of a bone matrix'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Bone Matrix" ,
- ]
- outputs = [
- "Bone Length" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- if (l := self.evaluate_input("Bone Matrix")) is not None:
- self.parameters["Bone Length"] = l[3][3]
- else:
- other = self.inputs["Bone Matrix"].links[0].from_node
- raise RuntimeError(f"Cannot get matrix for {self} from {other}")
- self.prepared, self.executed = True, True
- class UtilityPointFromBoneMatrix(MantisNode):
- '''A node representing an armature object'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Bone Matrix" ,
- "Head/Tail" ,
- ]
- outputs = [
- "Point" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- # TODO: find out why this is sometimes not ready at bPrepare phase
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- matrix = self.evaluate_input("Bone Matrix")
- head, rotation, _scale = matrix.copy().decompose()
- tail = head.copy() + (rotation @ Vector((0,1,0)))*matrix[3][3]
- self.parameters["Point"] = head.lerp(tail, self.evaluate_input("Head/Tail"))
- self.prepared, self.executed = True, True
- class UtilitySetBoneLength(MantisNode):
- '''Sets the length of a Bone's matrix'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Bone Matrix" ,
- "Length" ,
- ]
- outputs = [
- "Bone Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- if matrix := self.evaluate_input("Bone Matrix"):
- matrix = matrix.copy()
- # print (self.inputs["Length"].links)
- matrix[3][3] = self.evaluate_input("Length")
- self.parameters["Length"] = self.evaluate_input("Length")
- self.parameters["Bone Matrix"] = matrix
- else:
- raise RuntimeError(f"Cannot get matrix for {self}")
- self.prepared, self.executed = True, True
- class UtilityMatrixSetLocation(MantisNode):
- '''Sets the location of a matrix'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Matrix" ,
- "Location" ,
- ]
- outputs = [
- "Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- if matrix := self.evaluate_input("Matrix"):
- matrix = matrix.copy()
- # print (self.inputs["Length"].links)
- loc = self.evaluate_input("Location")
- matrix[0][3] = loc[0]; matrix[1][3] = loc[1]; matrix[2][3] = loc[2]
- self.parameters["Matrix"] = matrix
- self.prepared, self.executed = True, True
- class UtilityMatrixGetLocation(MantisNode):
- '''Gets the location of a matrix'''
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Matrix" ,
- ]
- outputs = [
- "Location" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- if matrix := self.evaluate_input("Matrix"):
- self.parameters["Location"] = matrix.to_translation()
- self.prepared = True; self.executed = True
- class UtilityMatrixFromXForm(MantisNode):
- """Returns the matrix of the given xForm node."""
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "xForm" ,
- ]
- outputs = [
- "Matrix" ,
- ]
- self.node_type = "UTILITY"
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- def GetxForm(self):
- trace = trace_single_line(self, "xForm")
- for node in trace[0]:
- if (node.node_type == 'XFORM'):
- return node
- raise GraphError("%s is not connected to an xForm" % self)
- def bPrepare(self, bContext = None,):
- from mathutils import Vector, Matrix
- self.parameters["Matrix"] = Matrix.Identity(4)
- if matrix := self.GetxForm().parameters.get("Matrix"):
- self.parameters["Matrix"] = matrix.copy()
- elif hasattr(self.GetxForm().bObject, "matrix"):
- self.parameters["Matrix"] = self.GetxForm().bObject.matrix.copy()
- elif hasattr(self.GetxForm().bObject, "matrix_world"):
- self.parameters["Matrix"] = self.GetxForm().bObject.matrix_world.copy()
- else:
- prRed(f"Could not find matrix for {self} - check if the referenced object exists.")
- self.prepared = True; self.executed = True
- class UtilityAxesFromMatrix(MantisNode):
- """Returns the axes of the given matrix."""
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Matrix" ,
- ]
- outputs = [
- "X Axis" ,
- "Y Axis" ,
- "Z Axis" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- if matrix := self.evaluate_input("Matrix"):
- matrix= matrix.copy().to_3x3(); matrix.transpose()
- self.parameters['X Axis'] = matrix[0]
- self.parameters['Y Axis'] = matrix[1]
- self.parameters['Z Axis'] = matrix[2]
- self.prepared = True; self.executed = True
- class UtilityBoneMatrixHeadTailFlip(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Bone Matrix" ,
- ]
- outputs = [
- "Bone Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector, Matrix, Quaternion
- from bpy.types import Bone
- if matrix := self.evaluate_input("Bone Matrix"):
- axis, roll = Bone.AxisRollFromMatrix(matrix.to_3x3())
- new_mat = Bone.MatrixFromAxisRoll(-1*axis, roll)
- length = matrix[3][3]
- new_mat.resize_4x4() # last column contains
- new_mat[0][3] = matrix[0][3] + axis[0]*length # x location
- new_mat[1][3] = matrix[1][3] + axis[1]*length # y location
- new_mat[2][3] = matrix[2][3] + axis[2]*length # z location
- new_mat[3][3] = length # length
- self.parameters["Bone Matrix"] = new_mat
- self.prepared, self.executed = True, True
- class UtilityMatrixTransform(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Matrix 1" ,
- "Matrix 2" ,
- ]
- outputs = [
- "Out Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- mat1 = self.evaluate_input("Matrix 1"); mat2 = self.evaluate_input("Matrix 2")
- if mat1 and mat2:
- mat1copy = mat1.copy()
- self.parameters["Out Matrix"] = mat2 @ mat1copy
- self.parameters["Out Matrix"].translation = mat1copy.to_translation()+ mat2.to_translation()
- else:
- raise RuntimeError(wrapRed(f"Node {self} did not receive all matrix inputs..."
- " found input 1? {mat1 is not None}, 2? {mat2 is not None}"))
- self.prepared, self.executed = True, True
- class UtilityMatrixInvert(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatrixInvertSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector
- mat1 = self.evaluate_input("Matrix 1")
- if mat1:
- mat1copy = mat1.copy()
- try:
- self.parameters["Matrix"] = mat1copy.inverted()
- except ValueError as e:
- prRed(f"ERROR: {self}: The matrix cannot be inverted."); prOrange(mat1)
- raise e
- else:
- raise RuntimeError(wrapRed(f"Node {self} did not receive all matrix inputs..."
- " found input 1? {mat1 is not None}"))
- self.prepared, self.executed = True, True
- class UtilityMatrixCompose(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatrixComposeSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Matrix
- matrix= Matrix.Identity(3)
- matrix[0] = self.evaluate_input('X Basis Vector')
- matrix[1] = self.evaluate_input('Y Basis Vector')
- matrix[2] = self.evaluate_input('Z Basis Vector')
- matrix.transpose(); matrix=matrix.to_4x4()
- matrix.translation = self.evaluate_input('Translation')
- self.parameters['Matrix']=matrix
- self.prepared = True; self.executed = True
- class UtilityMatrixAlignRoll(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, MatrixAlignRollSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Vector, Matrix
- align_axis = Vector(self.evaluate_input('Alignment Vector'))
- # why do I have to construct a vector here?
- # why is the socket returning a bpy_prop_array ?
- if align_axis.length_squared==0:
- raise RuntimeError(f"WARN: cannot align matrix in {self}"
- " because the alignment vector is zero.")
- input=self.evaluate_input('Matrix').copy()
- y_axis= input.to_3x3().transposed()[1]
- from .utilities import project_point_to_plane
- projected=project_point_to_plane(
- align_axis.normalized(), Vector((0,0,0)), y_axis).normalized()
- # now that we have the projected vector, transform the points from
- # the plane of the y_axis to flat space and get the signed angle
- from math import atan2
- try:
- flattened = (input.to_3x3().inverted() @ projected)
- except ValueError:
- raise ValueError(f"Cannot align the matrix in {self} because it is degenerate.")
- rotation = Matrix.Rotation(atan2(flattened.x, flattened.z), 4, y_axis)
- matrix = rotation @ input.copy()
- matrix.translation=input.translation
- matrix[3][3] = input[3][3]
- self.parameters['Matrix'] = matrix
- self.prepared = True; self.executed = True
- # NOTE: I tried other ways of setting the matrix, including composing
- # it directly from the Y axis, the normalized projection of the align
- # axis, and their cross-product. That only nearly worked.
- # this calculation should not work better, but it does. Why?
- class UtilityTransformationMatrix(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Operation" ,
- "Vector" ,
- "W" ,
- ]
- outputs = [
- "Matrix" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Matrix, Vector
- if (operation := self.evaluate_input("Operation")) == 'ROTATE_AXIS_ANGLE':
- # this can, will, and should fail if the axis is 0,0,0
- self.parameters["Matrix"] = rotMat = Matrix.Rotation(self.evaluate_input("W"), 4, Vector(self.evaluate_input("Vector")).normalized())
- elif (operation := self.evaluate_input("Operation")) == 'TRANSLATE':
- m = Matrix.Identity(4)
- if axis := self.evaluate_input("Vector"):
- m[0][3]=axis[0];m[1][3]=axis[1];m[2][3]=axis[2]
- self.parameters['Matrix'] = m
- elif (operation := self.evaluate_input("Operation")) == 'SCALE':
- self.parameters["Matrix"] = Matrix.Scale(self.evaluate_input("W"), 4, Vector(self.evaluate_input("Vector")).normalized())
- else:
- raise NotImplementedError(self.evaluate_input("Operation").__repr__()+ " Operation not yet implemented.")
- self.prepared = True; self.executed = True
- class UtilityIntToString(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Number" ,
- "Zero Padding" ,
- ]
- outputs = [
- "String" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- number = self.evaluate_input("Number")
- zeroes = self.evaluate_input("Zero Padding")
- # I'm casting to int because I want to support any number, even though the node asks for int.
- self.parameters["String"] = str(int(number)).zfill(int(zeroes))
- self.prepared = True; self.executed = True
- class UtilityArrayGet(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Index" ,
- "OoB Behaviour" ,
- "Array" ,
- ]
- outputs = [
- "Output" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- self.rerouted=[]
- def bPrepare(self, bContext = None,):
- if len(self.rerouted)>0:
- self.prepared, self.executed = True, True
- return #Either it is already done or it doesn't matter.
- elif self.prepared == False:
- # sort the array entries
- for inp in self.inputs.values():
- inp.links.sort(key=lambda a : -a.multi_input_sort_id)
- oob = self.evaluate_input("OoB Behaviour")
- index = self.evaluate_input("Index")
- from .utilities import cap, wrap
- # we must assume that the array has sent the correct number of links
- if oob == 'WRAP':
- index = wrap(0, len(self.inputs['Array'].links), index)
- if oob == 'HOLD':
- index = cap(index, len(self.inputs['Array'].links)-1)
- array_choose_relink(self, [index], "Array", "Output")
- self.prepared, self.executed = True, True
- class UtilityArrayLength(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Array" ,
- ]
- outputs = [
- "Length" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- self.parameters["Length"] = len(self.inputs["Array"].links)
- self.prepared, self.executed = True, True
- class UtilitySetBoneMatrixTail(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = {
- "Matrix" ,
- "Tail Location" ,
- }
- outputs = [
- "Result" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- from mathutils import Matrix
- matrix = self.evaluate_input("Matrix")
- if matrix is None: matrix = Matrix.Identity(4)
- #just do this for now lol
- self.parameters["Result"] = matrix_from_head_tail(matrix.translation, self.evaluate_input("Tail Location"))
- self.prepared = True; self.executed = True
- class UtilityPrint(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Input" ,
- ]
- self.inputs.init_sockets(inputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- if my_input := self.evaluate_input("Input"):
- print("Preparation phase: ", wrapWhite(self), wrapGreen(my_input))
- self.prepared = True
- def bTransformPass(self, bContext = None,):
- if my_input := self.evaluate_input("Input"):
- print("Execution phase: ", wrapWhite(self), wrapGreen(my_input))
- self.executed = True
- class UtilityCompare(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree, CompareSockets)
- self.init_parameters()
- self.node_type = "UTILITY"
- def bPrepare(self, bContext = None,):
- operation=self.evaluate_input("Comparison")
- a = self.evaluate_input("A")
- b = self.evaluate_input("B")
- if a is None:
- raise GraphError(f"Invalid first input for {self}")
- if b is None:
- raise GraphError(f"Invalid second input for {self}")
- if isinstance(a, str) and isinstance(b, str) and \
- operation not in ['EQUAL', 'NOT_EQUAL']:
- raise GraphError("Strings do not have numerical value to"
- " compute greater than or less than.")
- match operation:
- case "EQUAL":
- self.parameters["Result"] = a == b
- case "NOT_EQUAL":
- self.parameters["Result"] = a != b
- case "GREATER_THAN":
- self.parameters["Result"] = a > b
- case "GREATER_THAN_EQUAL":
- self.parameters["Result"] = a >= b
- case "LESS_THAN":
- self.parameters["Result"] = a < b
- case "LESS_THAN_EQUAL":
- self.parameters["Result"] = a <= b
- self.prepared = True; self.executed = True
- class UtilityChoose(MantisNode):
- def __init__(self, signature, base_tree):
- super().__init__(signature, base_tree)
- inputs = [
- "Condition" ,
- "A" ,
- "B" ,
- ]
- outputs = [
- "Result" ,
- ]
- self.inputs.init_sockets(inputs)
- self.outputs.init_sockets(outputs)
- self.init_parameters()
- self.node_type = "UTILITY"
- def reset_execution(self):
- prepared=self.prepared
- super().reset_execution()
- # prevent this node from attempting to prepare again.
- self.prepared, self.executed = prepared, prepared
- def bPrepare(self, bContext = None,):
- if self.outputs['Result'].links: # otherwise this doesn't matter as it is not connected.
- prGreen(f"Executing Choose Node {self}")
- condition = self.evaluate_input("Condition")
- if self.evaluate_input('A') is not None and self.evaluate_input('B') is not None:
- self.parameters['Result'] = self.evaluate_input('B') if condition else self.evaluate_input('A')
- elif self.evaluate_input('A') is None and self.evaluate_input('B') is None:
- if condition: link = self.inputs['B'].links[0]
- else: link = self.inputs['A'].links[0]
- from_node = link.from_node; from_socket = link.from_socket
- for link in self.outputs['Result'].links:
- from_node.outputs[from_socket].connect(link.to_node, link.to_socket)
- link.die()
- self.flush_links()
- # attempting to init the connections seems more error prone than leaving them be.
- else:
- raise GraphError(f"Choose Node {self} has incorrect types.")
- self.prepared = True; self.executed = True
|