MakeHuman but for cars. You write a config file and it builds the whole car, the outside and the inside.
You can try the editor in your browser here: https://hidude562.github.io/makecar/
It works the same way MakeHuman does for people. There is one base body mesh, and its topology never changes. A target is a set of offsets for each vertex on that mesh. A slider sets how much of a target gets added. Vertex j of every ring is always the same feature (the sill, the belt line, the roof rail), so a target made on one body still works on any other body.
The body has:
- 39 shape sliders, like
wheelbase,roof_heightanda_pillar_lean. Each one has an-incrand a-decrtarget. - 7 style macros: hatchback, wagon, suv, pickup, coupe, sports and van. Sedan is the base. They blend, so
{sedan: 0.5, wagon: 0.5}gives a shooting brake. - 4 sculpt targets:
haunches,side_crease,wedgeandroof_bubble.
The morphed body gives out connectors. These are like the joints in MakeHuman. A connector is a point, polygon, rectangle or circle, and it is placed from vertex groups on the morphed mesh. That way the connectors follow every slider.
Components attach to connectors. These are the wheels, seats, lamps, dashboard and so on. Some components use targets too. For instance, a wheel connector that has a bigger radius drives the radius slider on the wheel. Some components also give out connectors (the dashboard has a cluster, a screen and vents), and those get filled in the same way.
To add a component, register it with @register and set default_for to the tags it should take over.
pip install numpy pyyamlThat is all it needs. The tests also need pytest.
python -m makecar samples -o outputThis builds every config in configs/. Some other commands:
python -m makecar build configs/coupe.yaml -o output --set wheelbase=0.4 --seed 3
python -m makecar random -n 4 --style hatchback -o output
python -m makecar list-modifiers
python -m makecar list-components
python -m makecar connectors configs/suv.yaml
python -m pytest -qThe first run builds the target library and caches it in ~/.cache/makecar/ (it takes about 4 seconds). You can set MAKECAR_CACHE to put it somewhere else. After that a car builds in under a second. The PNG previews take a few more seconds because the renderer is plain numpy.
name: gt_coupe
description: Two-door GT with muscular haunches
seed: 6
body:
style: coupe # one style, or a blend like {suv: 0.7, coupe: 0.3}
modifiers: # sliders from -1 to 1
cowl_offset: 0.3 # long hood
roof_height: -0.15
width: 0.2
sculpt: {haunches: 0.8, wedge: 0.5}
hints: {door_count: 2, drive: left, transmission: manual, seat_rows: auto, fuel_side: left}
random: {amount: 0.0} # jitters the sliders using the seed
palette: {paint: "#151820", interior: "#3a1f1c", seat: "#6b2f28", rim: "#d0d3d8"}
components:
defaults: true # put the default component on every connector
disable: [antenna, roof_rail*] # connector name, tag or glob
assign:
wheel: {component: wheel.alloy, options: {spokes: 10, spoke_width: 0.4}}
exhaust: {component: exhaust.tip, options: {dual: true}}
roof_rail: roof.rails
output:
formats: [obj, svg, png, json] # "targets" also writes the used .target files
views: [three_quarter_front, three_quarter_rear, side, top, front, rear, interior_cutaway, interior_side, driver]
blueprint_theme: blueprint # or paper
variants: # more cars from the same file
- name: gt_coupe_wide
body: {modifiers: {width: 0.8}}body.base_params makes the base mesh again with different numbers. The targets still go on top of it. It is like editing the base mesh in MakeHuman.
configs/measured/ has a 2022 Civic, a Model 3 and an Aventador. I traced their side profiles from Wikimedia Commons photos (the photos and credits are in references/). The code for this is in makecar/reference/. It calibrates the photo off the wheel hubs, traces the edges and then puts the traced curves into base_params. The roofline is off by about 5 mm on all three. Only the side is measured, so the front, back and top views are still generic.
python -m makecar viewer configs/sedan.yamlThis opens http://127.0.0.1:8765. It is a local web app that uses the python http.server and a copy of three.js.
- Shape: every slider in groups. The body morphs while you drag.
- Connectors: move, rotate or scale any connector with
g,rands. The change gets written intoconnectors.overridesin the config, somakecar buildmakes the same car. - Sculpt: a mirrored brush that pushes or pulls the body. Save as target writes a
.targetfile and adds it as acustom/<name>slider. - Config: the live yaml. You can edit it and save it from here.
The viewer also runs as a static site on GitHub Pages. It runs the same Python code in the browser with Pyodide. The first load downloads around 10 MB (python, numpy and makecar), and then it is ready in 10 seconds or so. A slider change takes a second. Assembling the parts again takes 1 to 2 seconds, which is a little slower than running it locally. Some things work differently:
- Save and Export download the file instead of writing it.
- A sculpted target only lasts until you close the tab.
?config=suvopens a different config. It can be any file name fromconfigs/orconfigs/measured/.
To build the site yourself:
python -m scripts.build_web -o siteThen serve the site folder with python -m http.server. The build script also builds the target libraries and puts them in the site, so the browser does not have to build them.
Pushing to main deploys the site. .github/workflows/pages.yml runs a quick set of tests (around 200 of them), builds the site and publishes it. The full test suite runs separately in tests.yml. That way a slow test run does not hold up the site.
Each car goes in output/<name>/:
car.objandcar.mtl: the whole car, with one object group per componentbody.obj: only the bodyconnectors.json: every connector and what is attached to itassembly.json: slider values, measurements, the component tree and timingsblueprint.svg: side, front, plan and rear views with mm dimensionsconnector_map.svg: every connector drawn and labelledpreview.pngandinterior.png: the rendersspec.txt: a summary you can read
python -m makecar police -n 4 --tier marked -o outputThis makes police cars with random kit. There are three tiers: marked patrol, unmarked patrol and true undercover. Each piece of kit is rolled separately with the odds for that tier, so no two cars come out the same. --tier mixed is the default and picks a tier per car. --seed makes it repeatable. --configs DIR also writes the yaml for each car. The odds live in makecar/police.py:
| Kit | Marked | Unmarked | Undercover |
|---|---|---|---|
| Concealed emergency lights (grille, windshield, rear deck, mirrors) | 80% | 95% | 30% |
| Siren | 100% | 95% | 30% |
| A model fleets buy (sedan, suv or pickup) | 95% | 80% | 25% |
| Black, white, gray or silver paint | 85% | 80% | 75% |
| Dark rear windows | 80% | 75% | 50% |
| Laptop mount inside | 85% | 65% | 5% |
| Plain black steel wheels | 60% | 50% | 15% |
| Spotlight by the windshield | 70% | 40% | 5% |
| Extra antennas | 60% | 30% | 5% |
| Government plates | 85% | 30% | 5% |
| Push bumper | 60% | 20% | 5% |
The lettering changes from car to car too: the words, a city or county line, the font, italics, letter spacing, an outline and the colours are all rolled. Fleet plates get the unit number. The rest of a patrol car's kit is my guess. A roof light bar is on 90% of marked cars. All of them get agency lettering. 45% get a black and white two tone and 70% get a side stripe. 80% get a unit number on the quarter panels and the roof. A prisoner partition is in 85% of marked cars, 40% of unmarked ones and 5% of undercover ones. A raised ride height goes 50%, 25% and 5%.
The kit is all normal components, so you can put any of it on any car from a config. The body now emits mounts for it: roof_mount, bumper_front, spotlight_L/R, antenna_aux_L/R and cabin_partition. It also emits a panel_* connector for each door, fender and quarter panel, plus the hood, the roof and the deck. They have no default, so a plain car ignores them.
body:
livery: {doors: secondary, roof: secondary} # two tone from palette.paint_secondary
components:
assign:
roof_mount: light.bar
bumper_front: {component: bumper.push_bar, options: {siren: true}}
spotlight_L: light.spotlight
cabin_partition: {component: partition.cage, options: {style: bars}}
console: {options: {laptop_mount: true}}
grille: {options: {emergency_lights: true}}
panel_door_front: {component: decal.panel, options: {text: POLICE, stripe: true}}configs/police_marked.yaml, police_unmarked.yaml and police_undercover.yaml are three cars the generator made. They open in the web editor too.
components:
assign:
panel_door_front: {component: text.box, options: {text: "TAXI", font: serif-bold, size: 0.12}}
plate_front: {component: text.box, options: {text: "PD-400", size: 0.05, color: "#f4f4f0"}}text.box puts real text on any polygon connector. On a body panel it follows the curve of the skin. On a plain rectangle (a plate, the roof mount) it lies flat in the plane. size is the cap height in metres. x and y place the block on the panel, x along the car and y up the panel. There is align, line_spacing, letter_spacing, slant_deg (a fake italic), case, kerning and fit, which shrinks the text so it stays on the panel. outline (metres) draws a border in outline_color under the letters. Newlines in text make more lines. decal.panel uses the same engine and adds a stripe, and its texts list takes several blocks, each with a size and a place, which is how the police cars get a small city line over a big word.
Plates draw their characters the same way. plate.standard picks a random number per car, or you set text. Fleet plates (style: government) get a band that says so.
Six fonts ship in makecar/data/fonts: sans, sans-bold, sans-condensed-bold, serif-bold, serif-condensed-bold and mono-bold. They come from the DejaVu fonts. To add one, run this on any TrueType file (it needs fonttools):
python -m scripts.extract_font MyFont.ttf -o makecar/data/fonts/my-font.jsonThen use font: my-font, or give the path to the json. The letters are geometry, not a texture. The font json has the glyph outlines. Each glyph is cut into thin trapezoids with the nonzero winding rule, so the hole in an O comes out right. Then the trapezoids and the outline walls get lifted onto the panel through its surface grid, which is sampled at the loft stations so the letters sit on the skin.
pip install pillow
python -m scripts.turntable_gif -o output/turntable.gifThis is how I made the gif at the top. It morphs through all 8 body styles while the camera does one full orbit, and it loops back to sedan at the end. It uses the same style blending the config supports. The script only moves the blend a little each frame. --frames-per-style sets how many frames each style gets, which also sets how slow the morph looks. --size sets the resolution.
makecar/
geometry/ mesh, frames, curves, primitives
morph/ Target, Modifier, MorphableMesh
body/ params, the loft generator, styles, targets, connectors, mounts, panels, CarBody
components/ exterior, interior and fleet equipment parts, decals, text
reference/ photo tracing for the measured cars
viewer/ the viewer (session.py is the api, server.py serves it locally)
police.py the police car generator
data/fonts/ glyph outlines for the text engine
export/ obj, svg blueprint, png renderer
configs/ sample configs
references/ reference photos and credits
scripts/ helper scripts, including the web build
web/ the Pyodide worker for the web editor
tests/ pytest




