# Attaching transformcontrols to imported 3D object

**URL:** <https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192>\
**Category:** Questions\
**Created:** [June 23, 2024, 11:28pm UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192 "2024-06-23T23:28:09Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![a3dcrafter](https://avatars.discourse-cdn.com/v4/letter/a/ecae2f/32.png) [@a3dcrafter](https://discourse.threejs.org/u/a3dcrafter)\
**Post date:** [June 23, 2024, 11:28pm UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192/1 "2024-06-23T23:28:09Z")

</div>

Been able to figure out a bunch of stuff looking through examples and the forum, but I am stuck right now and looking for some help.

I have imported a 3d model of a skull into 3js. The skull has 2 objects that I modeled in blender, the skull and jaw.

What I am trying to do is attach transformcontrols to just the jaw part. I confirmed that the object’s name is jaw through the console.

What ends up happening right now is that the controls looks like its just attached to the scene instead. I can interact and manipulate the controls when it’s in rotate, but nothing happens. When it’s on scale mode, I can interact with it (all the axes highlight when I mouse over them), but I can’t scale the model as if it was locked. If I use transform, the code just breaks. These errors I get:

Uncaught TypeError: this.picker[this.mode] is undefined  
Uncaught TypeError: object is undefined

So I have no idea what the controls is even attached to even though I specified the jaw.

Here’s the code I’m using right now and doesn’t give me any errors, but it’s not working as intended:

```javascript
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { TransformControls } from 'three/addons/controls/TransformControls.js';
import WebGL from 'three/addons/capabilities/WebGL.js';
import GUI from 'lil-gui';

let skull;

const scene = new THREE.Scene( );
	scene.background = new THREE.Color( 0xdddddd );
	scene.add( new THREE.GridHelper( 1000, 1000, 0x888888, 0x444444 ) );

const camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 1, 1000 );

const hlight = new THREE.AmbientLight ( 0x404040, 10 );
	scene.add( hlight );

const directionalLight = new THREE.DirectionalLight( 0xffffff, 10 );
	directionalLight.position.set( 0, 1, 0 );
	directionalLight.castShadow = true;
	scene.add( directionalLight );

const renderer = new THREE.WebGLRenderer( );
	renderer.setSize( window.innerWidth, window.innerHeight );
	renderer.setAnimationLoop( animate );
	document.body.appendChild( renderer.domElement );

const loader = new GLTFLoader( );
	loader.load( 'skull.glb', function ( gltf ) {
		skull = gltf.scene;
		skull.traverse( function( object ) {

    			if ( object.isMesh ) console.log( object );

		} );
		scene.add( skull );
	}, undefined, function ( error ) {
		console.error( error );

	} );
		

const controls = new OrbitControls( camera, renderer.domElement );
	controls.update( );
	controls.addEventListener( 'change', animate );

const tcontrols = new TransformControls( camera, renderer.domElement );
	tcontrols.setMode( 'rotate' );
	tcontrols.addEventListener( 'change', animate );
	tcontrols.addEventListener( 'dragging-changed', function ( event ) {
		controls.enabled = ! event.value;
	} );

	tcontrols.size = 1;
	tcontrols.space = 'local';

const Jaw = new THREE.Mesh( skull );
	scene.add( Jaw );

	tcontrols.attach( Jaw );
	scene.add ( tcontrols );

camera.position.x = 157;
camera.position.y = -245;
camera.position.z = 127;

const gui = new GUI( );

	const myObject = {
		myBoolean: true,
		myFunction: function( ) { alert( 'hi') },
		myString: 'lil-gui',
		myNumber: 1
	};

	gui.add( myObject, 'myBoolean' ); // Checkbox
	gui.add( myObject, 'myFunction' ); // Button
	gui.add( myObject, 'myString' ); // Text Field
	gui.add( myObject, 'myNumber' ); // Number Field

	// Add sliders to number fields by passing min and max
	gui.add( myObject, 'myNumber', 0, 1 );
	gui.add( myObject, 'myNumber', 0, 100, 2 ); // snap to even numbers

	// Create dropdowns by passing an array or object of named values
	gui.add( myObject, 'myNumber', [0, 1, 2] );
	gui.add( myObject, 'myNumber', { Label1: 0, Label2: 1, Label3: 2 } );

	// Create color pickers for multiple color formats
	const colorFormats = {
		string: '#ffffff',
		int: 0xffffff,
		object: { r: 1, g: 1, b: 1 },
		array: [1, 1, 1]
	};

	gui.addColor( colorFormats, 'string' );

function animate( ) {
	renderer.render( scene, camera );
}

```

I am able to attach the controls to just a regular geometric shape when I use this based off the examples:

```javascript
	orbit = new OrbitControls( currentCamera, renderer.domElement );
	orbit.update();
	orbit.addEventListener( 'change', render );

	control = new TransformControls( currentCamera, renderer.domElement );
	control.setMode( 'rotate' );
	control.addEventListener( 'change', render );

	control.addEventListener( 'dragging-changed', function ( event ) {

		orbit.enabled = ! event.value;

	} );

	const mesh = new THREE.Mesh( geometry );
	scene.add( mesh );

	control.attach( mesh );
	scene.add( control );

```

But trying to get the controls on the imported object is confusing me. Maybe it’s the placement of some of the parts in the code?

Any help would be greatly appreciated.

---

<div class="post-metadata">

**Author:** ![AshhadDevLab](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/ashhaddevlab/32/44111_2.png) [@AshhadDevLab](https://discourse.threejs.org/u/AshhadDevLab)\
**Post date:** [June 24, 2024, 7:44am UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192/2 "2024-06-24T07:44:50Z")

</div>

Try using this code:

```javascript
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { TransformControls } from "three/addons/controls/TransformControls.js";
import WebGL from "three/addons/capabilities/WebGL.js";
import GUI from "lil-gui";

let skull;

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xdddddd);
scene.add(new THREE.GridHelper(1000, 1000, 0x888888, 0x444444));

const camera = new THREE.OrthographicCamera(
  window.innerWidth / -2,
  window.innerWidth / 2,
  window.innerHeight / 2,
  window.innerHeight / -2,
  1,
  1000
);

const hlight = new THREE.AmbientLight(0x404040, 10);
scene.add(hlight);

const directionalLight = new THREE.DirectionalLight(0xffffff, 10);
directionalLight.position.set(0, 1, 0);
directionalLight.castShadow = true;
scene.add(directionalLight);

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setAnimationLoop(animate);
document.body.appendChild(renderer.domElement);

const controls = new OrbitControls(camera, renderer.domElement);
controls.update();
controls.addEventListener("change", animate);

const tcontrols = new TransformControls(camera, renderer.domElement);
tcontrols.setMode("rotate");
tcontrols.addEventListener("change", animate);
tcontrols.addEventListener("dragging-changed", function (event) {
  controls.enabled = !event.value;
});

tcontrols.size = 1;
tcontrols.space = "local";

const loader = new GLTFLoader();
loader.load(
  "skull.glb",
  function (gltf) {
    skull = gltf.scene;
    skull.traverse(function (object) {
      if (object.isMesh) console.log(object);
    });
    tcontrols.attach(Skull);
    scene.add(tcontrols);
    scene.add(skull);
  },
  undefined,
  function (error) {
    console.error(error);
  }
);

camera.position.x = 157;
camera.position.y = -245;
camera.position.z = 127;

const gui = new GUI();

const myObject = {
  myBoolean: true,
  myFunction: function () {
    alert("hi");
  },
  myString: "lil-gui",
  myNumber: 1,
};

gui.add(myObject, "myBoolean"); // Checkbox
gui.add(myObject, "myFunction"); // Button
gui.add(myObject, "myString"); // Text Field
gui.add(myObject, "myNumber"); // Number Field

// Add sliders to number fields by passing min and max
gui.add(myObject, "myNumber", 0, 1);
gui.add(myObject, "myNumber", 0, 100, 2); // snap to even numbers

// Create dropdowns by passing an array or object of named values
gui.add(myObject, "myNumber", [0, 1, 2]);
gui.add(myObject, "myNumber", { Label1: 0, Label2: 1, Label3: 2 });

// Create color pickers for multiple color formats
const colorFormats = {
  string: "#ffffff",
  int: 0xffffff,
  object: { r: 1, g: 1, b: 1 },
  array: [1, 1, 1],
};

gui.addColor(colorFormats, "string");

function animate() {
  renderer.render(scene, camera);
}

```

---

<div class="post-metadata">

**Author:** ![a3dcrafter](https://avatars.discourse-cdn.com/v4/letter/a/ecae2f/32.png) [@a3dcrafter](https://discourse.threejs.org/u/a3dcrafter)\
**Post date:** [June 24, 2024, 4:01pm UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192/3 "2024-06-24T16:01:10Z")

</div>

Used that code and the controls are actually on the skull, thanks! Looks like I had to put all that together within the loader function to work, instead of outside/below it.

That got me halfway to where I wanted to be, with the controls specifically on the jaw part.

But I remembered one of the examples with IK stuff had some function to call the parts of a model. So I went off of that and put in this inside with the other loader stuff:

```javascript
skull.traverse( n => {
			if ( n.name === 'Jaw' ) part.Jaw = n;
		} );
		tcontrols.attach( part.Jaw );
		scene.add ( tcontrols );
		scene.add( skull );

```

Now it’s on the jaw and only the jaw rotates. Thanks again for the help!

Side note: I accidentally used scene.add( part.Jaw ) and just got only that part to load 😅. That might be useful to know later on, maybe. Probably after I start learning the IK stuff.

---

<div class="post-metadata">

**Author:** ![AshhadDevLab](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/ashhaddevlab/32/44111_2.png) [@AshhadDevLab](https://discourse.threejs.org/u/AshhadDevLab)\
**Post date:** [June 25, 2024, 10:40am UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192/4 "2024-06-25T10:40:19Z")

</div>

I wasn’t aware of the `Jaw` part! The problem here is that `GLTFLoader.loader()` is an asynchronous function and the model thats loaded inside the `loader()` can’t be accessed outside. Accessing it outside can be only possible if you assign the the `gltf.scene` to a global veraible.

I don’t know if you are familar with `three.js` file formatting pattern but that is very helpful in these cases!

---

<div class="post-metadata">

**Author:** ![a3dcrafter](https://avatars.discourse-cdn.com/v4/letter/a/ecae2f/32.png) [@a3dcrafter](https://discourse.threejs.org/u/a3dcrafter)\
**Post date:** [June 25, 2024, 4:43pm UTC](https://discourse.threejs.org/t/attaching-transformcontrols-to-imported-3d-object/67192/5 "2024-06-25T16:43:48Z")

</div>

Oh, I didn’t know about that. Now all the let variables I see at the top of all the example codes make more sense. Just read up a bit on async functions, and I see how they’re used and are better. I thought all code just ran in steps, waiting for the previous one to finish. Still very new to 3js (and programming in general) and picking up things as I go. Thanks!

![~WRD3578.jpg](https://canada1.discourse-cdn.com/flex035/uploads/threejs/original/3X/5/2/52d5f1c08649212fe6768d2cacdcf8df711810d9.jpeg)
