156 tris · 1 material · 0.21×0.01×0.09 m · GLB 18 KB
The fastest path in three.js: one import, zero loaders. The module returns a ready THREE.Group with named parts.
import { createAsset } from 'https://polyfork.dev/cdn/wrench-390c5f.mjs';
scene.add(createAsset());
Downloaded it instead? Import
'./wrench-390c5f.mjs' and add the importmap below. The zip
ships a working index.html with both already in place.
<!doctype html>
<script type="importmap">
{ "imports": { "three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/" } }
</script>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { createAsset } from 'https://polyfork.dev/cdn/wrench-390c5f.mjs';
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf3ecdc);
// Far plane at 5000: terrain blocks are 64 to 256m across and a 100m
// frustum slices them.
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 5000);
camera.position.set(3, 2, 4);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
document.body.style.margin = 0;
document.body.appendChild(renderer.domElement);
scene.add(new THREE.HemisphereLight(0xffffff, 0x998877, 1.2));
const sun = new THREE.DirectionalLight(0xfff2e0, 2);
sun.position.set(4, 7, 5);
scene.add(sun);
scene.add(createAsset());
new OrbitControls(camera, renderer.domElement);
renderer.setAnimationLoop(() => renderer.render(scene, camera));
</script>
The standard route: load the GLB with GLTFLoader. The same file works in any glTF pipeline (Babylon.js, <model-viewer>, custom engines).
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
new GLTFLoader().load('https://polyfork.dev/cdn/wrench-390c5f.glb', (gltf) => {
scene.add(gltf.scene); // real-world scale in meters, origin on the ground
});
Flat-shaded vertex colors on a single material: no textures to wire up, one draw call.
R3F takes any Object3D through <primitive>, and these modules
return a ready THREE.Group synchronously. No useGLTF, no loader, no
<Suspense>.
import { useMemo } from 'react';
import { Canvas } from '@react-three/fiber';
import { createAsset } from './wrench-390c5f.mjs';
function Wrench(props) {
const obj = useMemo(() => createAsset(props), [JSON.stringify(props)]);
return <primitive object={obj} />;
}
<Canvas camera={{ position: [3, 2, 4] }}>
<hemisphereLight args={[0xffffff, 0x998877, 1.2]} />
<directionalLight position={[4, 7, 5]} intensity={2} />
<Wrench />
</Canvas>
Drop the module into your project next to the component and import it as a local file:
a bare https:// import works in a plain script tag but not under Vite or Next.
More on using these in R3F.
Open this model in StackBlitz A running R3F project with this model in it, no install.
A model that animates itself exposes
userData.tick(seconds). Drive it from R3F's own loop:
useFrame((state) => obj.userData.tick?.(state.clock.elapsedTime));
To merge a static set into a single draw call, import
mergeAssets from https://polyfork.dev/cdn/merge.mjs and pass the merged
group to one <primitive>.
Using Claude Code, Cursor or another coding agent? This site is built for them: agent-readable docs at /llms.txt and a JSON API at /api. Paste this prompt:
Add the "Wrench" low-poly model from polyfork.dev
to my three.js scene. It is free and hotlinkable, nothing to download:
ES module: https://polyfork.dev/cdn/wrench-390c5f.mjs
(import { createAsset } - returns a THREE.Group, real-world scale)
GLB: https://polyfork.dev/cdn/wrench-390c5f.glb
Full metadata (size, palette, rigged parts): https://polyfork.dev/api/assets/wrench-390c5f
Site docs for agents: https://polyfork.dev/llms.txt
Every asset in this catalog shares one identical vertex-colored material, with no textures at all. That means whole scenes collapse into a single draw call: the same result Synty-style packs get from a texture atlas, without the atlas.
import { mergeAssets } from 'https://polyfork.dev/cdn/merge.mjs';
// position your assets first, then:
const { merged, dynamic } = mergeAssets([
terrain, tree, rock,
asset,
]);
scene.add(merged); // ONE draw call, static
dynamic.forEach(d => scene.add(d)); // rigged parts, still movable
Placing many copies of one model? Use THREE.InstancedMesh instead: one draw call and even less memory. The landing page's forest demo ends with exactly this merge.
Hotlink or download it. The copy-paste snippet just works.
An open-end and box-end combination wrench in metal. Real-world scale at 0.21 x 0.01 x 0.09 m, 156 triangles on one material.
Materials, not vertex colours: the palette comes across as named materials you can retint, and they carry this model's own knob names.
The zip holds the ES module, the GLB and a ready
index.html: serve the folder and it renders.
Free: create a free account to download. Hotlinking from the CDN needs no account.
One of 259 free 3D models for three.js, every one hotlinkable from the CDN with no account.