CompStart

ProAulasCursosElementosSoftware

CompStart

Domine efeitos visuais com técnicas de ponta.

Aprendizado

CursosLiçõesRecursos ProRecursosSoftware

Tools

Tech CheckDisplay CheckMotion BlurCIE 1931PhysLightNuke Tools

Empresa

InstructorMissãoImpressumPrivacidadeTermos

Projetos

Moray RenderEXR ConverterOCIO.ccNuke ToolsDerekVFX

Follow

YouTubeInstagramTikTokThreadsContato

© 2026 CompStart. Todos os direitos reservados.

Reconciliar 3D Rápido

ToolSets / 3D
3D

2026-09-07

ToolSets/3d/reconcile3DFast.nk

3D
conciliar
câmera
eixo
projeção
utilidade
transformar
monitorando
View on GitHub

Projeta a translação de um eixo através de uma câmera animada em um vetor 2D XY ao longo do alcance da câmera, com exportação opcional de transformação.

O Reconcile3D Fast recebe um plano de fundo (ou formato raiz), uma câmera e um eixo, e então projeta a translação do eixo no espaço da tela. Clique em Calcular para gravar os pontos 2D reconciliados no botão de saída XY ao longo do alcance de animação da câmera.

Use esta função quando precisar de trajetórias 2D ou deslocamentos a partir de pontos 3D conhecidos sem executar uma passagem completa do Reconcile3D. A câmera e o eixo podem ficar atrás de pontos; o script resolve o nó superior. Câmeras não animadas são rejeitadas; se o intervalo não puder ser lido a partir de curvas de translação ou rotação, o intervalo do quadro raiz será usado.

O comando Limpar redefine a saída XY. O comando Transformar cria um nó de Transformação e copia as chaves XY animadas para o nó de Translação, permitindo controlar o trabalho 2D a partir do caminho reconciliado.

Script code

set cut_paste_input [stack 0]
version 13.0 v1
push 0
push 0
push $cut_paste_input
Group {
 inputs 3
 name reconcile3DFast
 tile_color 0x421010ff
 selected true
 xpos 12819
 ypos -4378
 addUserKnob {20 PlanarProjection l reconcile3DFast}
 addUserKnob {22 Calculate t "Calculate the reconciled 2D points for each 3D point." T "import nuke\nfrom nukescripts import snap3d as sn\nimport math\n\n\n## Based on Planar Projection by Vit Sedlacek and Jed Smith\n\n\ndef cameraProjectionMatrix(cameraNode, frame, imageformat):\n    ## modified code from nukescripts/Snap3D\n\n    # Matrix to transform points into camera-relative coords.\n    wm = nuke.math.Matrix4()\n    for i in range(16):\n        wm\[i] = cameraNode\[\"matrix\"].getValueAt(frame, i)\n\n    wm.transpose()\n    camTransform = wm.inverse()\n\n    # Matrix to take the camera projection knobs into account\n    roll = float(cameraNode\[\"winroll\"].getValueAt(frame, 0))\n    scale_x = float(cameraNode\[\"win_scale\"].getValueAt(frame, 0))\n    scale_y = float(cameraNode\[\"win_scale\"].getValueAt(frame, 1))\n    translate_x = float(cameraNode\[\"win_translate\"].getValueAt(frame, 0))\n    translate_y = float(cameraNode\[\"win_translate\"].getValueAt(frame, 1))\n    m = nuke.math.Matrix4()\n    m.makeIdentity()\n    m.rotateZ(math.radians(roll))\n    m.scale(1.0 / scale_x, 1.0 / scale_y, 1.0)\n    m.translate(-translate_x, -translate_y, 0.0)\n\n    # Projection matrix based on the focal length, aperture and clipping planes of the camera\n    focal_length = float(cameraNode\[\"focal\"].getValueAt(frame))\n    h_aperture = float(cameraNode\[\"haperture\"].getValueAt(frame))\n    near = float(cameraNode\[\"near\"].getValueAt(frame))\n    far = float(cameraNode\[\"far\"].getValueAt(frame))\n    projection_mode = int(cameraNode\[\"projection_mode\"].getValueAt(frame))\n    p = nuke.math.Matrix4()\n    p.projection(focal_length / h_aperture, near, far, projection_mode == 0)\n\n    # Matrix to translate the projected points into normalised pixel coords\n    imageAspect = float(imageformat.height()) / float(imageformat.width())\n\n    t = nuke.math.Matrix4()\n    t.makeIdentity()\n    t.translate(1.0, 1.0 - (1.0 - imageAspect / float(imageformat.pixelAspect())), 0.0)\n\n    # Matrix to scale normalised pixel coords into actual pixel coords.\n    x_scale = float(imageformat.width()) / 2.0\n    y_scale = x_scale * imageformat.pixelAspect()\n    s = nuke.math.Matrix4()\n    s.makeIdentity()\n    s.scale(x_scale, y_scale, 1.0)\n\n    # The projection matrix transforms points into camera coords, modifies based\n    # on the camera knob values, projects points into clip coords, translates the\n    # clip coords so that they lie in the range 0,0 - 2,2 instead of -1,-1 - 1,1,\n    # then scales the clip coords to proper pixel coords.\n    return s * t * p * m * camTransform\n\n\ndef projectPoints(frame, camera=None, point=None, imageformat=None):\n    # Modify projectpoint function in nukescripts.snap3d to add frame argument\n    if not imageformat:\n        imageformat = nuke.root()\[\"format\"].value()\n    camMatrix = cameraProjectionMatrix(camera, frame, imageformat)\n    if camMatrix == None:\n        raise RuntimeError(\"snap3d.cameraProjectionMatrix() returned None for camera.\")\n\n    if not (isinstance(point, list) or isinstance(point, tuple)):\n        raise ValueError(\"Argument point must be a list or tuple.\")\n\n    for point in point:\n        # Would be nice to not do this for every item but since lists/tuples can\n        # containg anything...\n        if isinstance(point, nuke.math.Vector3):\n            pt = point\n        elif isinstance(point, list) or isinstance(point, tuple):\n            pt = nuke.math.Vector3(point\[0], point\[1], point\[2])\n        else:\n            raise ValueError(\n                \"All items in point must be nuke.math.Vector3 or list/tuple of 3 floats.\"\n            )\n\n        tPos = camMatrix * nuke.math.Vector4(pt.x, pt.y, pt.z, 1.0)\n        # print tPos\n        try:\n            yield nuke.math.Vector2(tPos.x / tPos.w, tPos.y / tPos.w)\n        except ZeroDivisionError:\n            print(f\"Zero Division Error on frame \{frame\} with point data \{point\}\")\n            yield nuke.math.Vector2()\n\n\ndef calculate(node):\n    # Get the input Camera and verify it is right. (Assume camera is topnode of input to handle dots)\n    cam_input = node.input(1)\n    # Sanity check\n    if not (cam_input and isinstance(cam_input, nuke.Node)):\n        nuke.message(\"A Camera node must be connected.\")\n        return\n    if \"Camera\" in cam_input.Class():\n        cam = cam_input\n    else:\n        cam = nuke.toNode(nuke.tcl(\"full_name \[topnode %s]\" % cam_input.name()))\n\n    # AXIS INPUT\n    axis_input = node.input(2)\n    if not (axis_input and isinstance(axis_input, nuke.Node)):\n        nuke.message(\"A axis node must be connected.\")\n        return\n    if \"Axis\" in axis_input.Class():\n        axis = axis_input\n    else:\n        axis = nuke.toNode(nuke.tcl(\"full_name \[topnode %s]\" % axis_input.name()))\n\n    # BG INPUT\n    bg = node.input(0)\n    if not bg:\n        nuke.message(\n            \"BG not connected, so the root format will be used to reconcile the 3D point into screen space.\"\n        )\n\n    # Get framerange to operate on from camera animation curves\n    first = None\n    last = None\n    try:\n        if cam\[\"translate\"].isAnimated():\n            for curve in cam\[\"translate\"].animations():\n                if first == None:\n                    first = int(curve.keys()\[0].x)\n                else:\n                    first = min(first, int(curve.keys()\[0].x))\n            for curve in cam\[\"translate\"].animations():\n                if last == None:\n                    last = int(curve.keys()\[-1].x)\n                else:\n                    last = max(last, int(curve.keys()\[-1].x))\n        elif cam\[\"rotate\"].isAnimated():\n            for curve in cam\[\"rotate\"].animations():\n                if first == None:\n                    first = int(curve.keys()\[0].x)\n                else:\n\n                    first = min(first, int(curve.keys()\[0].x))\n            for curve in cam\[\"rotate\"].animations():\n                if last == None:\n                    last = int(curve.keys()\[-1].x)\n                else:\n                    last = max(last, int(curve.keys()\[-1].x))\n        else:\n            nuke.message(\"Input Camera is not animated.\")\n            return\n    except:\n        nuke.message(\n            \"Something went wrong getting the camera animation. Using Root framerange...\"\n        )\n        first = nuke.root().firstFrame()\n        last = nuke.root().lastFrame()\n\n    framerange = nuke.FrameRange(\"\{0\}-\{1\}\".format(first, last))\n\n    # Only run if the ip knob is not default\n    ipknob = axis\[\"translate\"]\n\n    opknob = node\[\"op1\"]\n    opknob.clearAnimated()\n    opknob.setAnimated()\n\n    # Building the data into a list of AnimationKey objects,\n    # and then applying that list to the knob using addKey is significantly faster than other methods.\n    # This makes the Calculate button instantaneous instead of taking forever.\n    point_animcurve = \[\[], \[]]\n    for frame in framerange:\n\n        # print \"values are \", ipknob.getValueAt(frame)\n        # Sample input point knob on every frame if it's animated or expression-linked\n        if ipknob.isAnimated() or ipknob.hasExpression():\n            point = next(projectPoints(\n                frame, cam, \[ipknob.getValueAt(frame)], node.format()\n            ))\n        else:\n            point = next(projectPoints(frame, cam, \[ipknob.value()], node.format()))\n        for index in range(2):\n            point_animcurve\[index].append(nuke.AnimationKey(frame, point\[index]))\n\n    for index, curve in enumerate(opknob.animations()):\n        curve.addKey(point_animcurve\[index])\n\n\nif __name__ == \"__main__\":\n    calculate(nuke.thisNode())\n" +STARTLINE}
 addUserKnob {22 clear_out l Clear t "Clear output knobs." -STARTLINE T "n = nuke.thisNode()\n\nkop = n\['op1']\nkop.clearAnimated()\nkop.setValue(kop.defaultValue())"}
 addUserKnob {12 op1 l "XY output"}
 addUserKnob {22 Transform T "# Create tracker node\nfrom __future__ import with_statement\n\ngrid_x = int(nuke.toNode('preferences').knob('GridWidth').value())\ngrid_y = int(nuke.toNode('preferences').knob('GridHeight').value())\npproj = nuke.thisNode()\n\nwith nuke.root():\n    transform_node = nuke.nodes.Transform()\n    transform_node.setXYpos(pproj.xpos() - grid_x * 0, pproj.ypos() + grid_y * 2)\n    \[n.setSelected(False) for n in nuke.allNodes()]\n    transform_node.setSelected(True)\n\n    if pproj\['op1'].isAnimated():\n        tknob = transform_node\['translate']\n        tknob.clearAnimated()\n        tknob.setAnimated()\n        for x, c in enumerate(tknob.animations()):\n            opknob = pproj\['op1']\n            if opknob.isAnimated():\n                c.addKey(opknob.animation(x).keys())\n            else:\n                tknob.clearAnimated()" +STARTLINE}
 addUserKnob {26 ""}
 addUserKnob {26 lbl l "" +STARTLINE T "Reconcile3DFast v1.1"}
 addUserKnob {22 btn l DerekVFX.ca T "nuke.tcl('start', 'https://derekvfx.ca')" +STARTLINE}
}
 Input {
  inputs 0
  name cam
  label "\[value number]"
  xpos 400
  ypos -301
  number 1
 }
 Input {
  inputs 0
  name axis
  label "\[value number]"
  xpos 197
  ypos -307
  number 2
 }
 Input {
  inputs 0
  name img
  label "\[value number]"
  xpos 620
  ypos -302
 }
 Output {
  name Output1
  selected true
  xpos 620
  ypos 158
 }
end_group