Product Photo to 3D Model with Real-World Scale
Pricing
from $400.00 / 1,000 delivered models
Product Photo to 3D Model with Real-World Scale
Turns product photos into web-ready GLB models scaled to the real dimensions you give, oriented front-forward, with the pivot on the floor. Ready to drop into Shopify, WooCommerce, Wix or any model-viewer page.
Pricing
from $400.00 / 1,000 delivered models
Rating
0.0
(0)
Developer
Kostas Skutulas
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
23 days ago
Last modified
Categories
Share
Product Photos to 3D Models at Real-World Scale
Turn product photos into GLB models scaled to your exact millimetre dimensions, with the pivot on the floor, ready for Shopify, WooCommerce and Wix.
Introduction
AI image-to-3D generators turn a product photo into a 3D mesh in about a minute, but they do not get the size right. They normalise their output to roughly a one cubic metre volume and ignore the object's real proportions, so a dining chair comes back the same size as a three-seat sofa. They also put the origin at the centre of the object instead of at its base.
Both problems break augmented reality (AR). A model with the origin at its centre sinks halfway into the shopper's floor. A model at the wrong scale looks plainly wrong next to a real door frame. "See it in your room" is the reason to put 3D on a product page, so a model at the wrong scale is worse than no model at all.
This actor fixes both. You already know your products' dimensions, because they are printed on the product page. The actor treats those dimensions as ground truth and makes the generated mesh match them: exact width, height and depth in millimetres, origin on the floor, centred horizontally, upright.
The result is a GLB 3D model you can upload straight to Shopify's 3D media slot, where the built-in viewer and AR work without any app. The same file works with the free viewer plugins on WooCommerce and Wix, and with model-viewer on any custom site. iOS AR Quick Look is served from the same corrected file, so iPhone shoppers see the model you approved.
The output follows the conventions the platforms document: glTF's metre unit and +Y up, Shopify's requirement that the origin sits at the product's base, and Google's Scene Viewer guidance on triangle count and file size. The defaults favour AR that works everywhere over the smallest possible file.
Tutorial
1. Get a provider key
You bring your own Meshy or Tripo API key. The provider then licenses the generated models to you directly, with nobody in between, and you pay generation costs at the provider's list price instead of a marked-up rate.
A paid provider tier is normally required if you intend to sell or redistribute the models. Meshy's free tier licenses output under CC BY 4.0, which requires attribution; its paid tiers grant full ownership. Tripo grants paid users broad commercial rights, and free accounts assign the rights to Tripo. Read your provider's terms before you put the models on a live store.
2. Collect dimensions
For each product you need the real width, height and depth in millimetres. This is the only input the actor cannot guess, and it is what gives the model its correct scale.
3. Give it two or three photos
With one photo, the generator has to invent the back of the object. Two or three angles give a noticeably better mesh. The first URL you give is treated as the front view.
3b. Upholstery: list the fabrics
For a sofa or armchair sold in several fabrics, add fabrics to the item with each fabric's name and the swatch photo the shop already shows. The model gets one variant per fabric, switchable in the viewer and in AR, and the weave is on the model at its real size. The sharpest patch of the swatch is flattened, made seamless, given relief and repeated over the fabric surfaces through a second UV set, and the model's own folds and seams stay underneath. This costs no provider credits and adds about a megabyte per fabric. Colour-only tinting was tested first and looked like a colour change, so the actor tiles the actual weave instead.
A sofa sold in one cloth needs no swatch. Set clothFromGallery: true and, with a Claude key, Claude finds the cloth in the item's own imageUrls: the close-up most shops show, and lit faces of the whole sofa for its colour. The close-up is trimmed to plain cloth and widened around Claude's box to take in all of it. Its colour is set to what the lit faces show, because a close-up is shot nearer the lamp and comes out paler and warmer, and then raised by what the viewer's lighting takes off. The model wears this cloth as its only material. Passing the row's clothReading back on the item as clothReading dresses the model the same way without a Claude call.
3c. Simple shapes: build instead of generate
Generators do worst on shapes that a few numbers describe fully. They add lumps and stains to plain surfaces and smear the pattern or the print, which is the detail that matters on these products. For those, give build instead: { "kind": ..., "params": { ... } }. The product is built from the dimensions, exactly to size, and uses no provider credits. There are five kinds, and all params are in millimetres:
boxy-sofa:widthMm,heightMm,depthMm, and optionallyseatHeightMm,armWidthMm,armHeightMm,backHeightMm,backDepthMm,chaiseDepthMm,chaiseWidthMm(the chaise module, its arm included),chaiseSide,seatSectionMm,quiltMm,backCushionMm,sideCushion,armInsetMm(the free arm stops this far short of the seat front),chaiseOpenMm(the chaise's open end in front of its arm, snapped to a tufting seam),pillows,pillowMm. The sofa is built as modules side by side: a seat module between the arms, and a chaise module that runs the chaise's full depth. Each seat is one block from the feet up, split into sections with real seams ifseatSectionMmis smaller than the seat. WithquiltMmthe seats are tufted in cells on top and in channels at the front, and the arms get the same, with channels along the depth on their sides. The back is upholstered blocks, or withbackCushionMma low frame with loose cushions standing against it. A side cushion leans on the arm over the chaise, and scatter cushions go in front. The sofa is dressed in thefabricsgiven.imageUrlsare only for reference unless you giveanthropicApiKey(oropenrouterApiKey, which routes the same calls through OpenRouter). With a key, Claude reads every param you left out from the photos and the dimensions before building: which end the chaise is on, how wide the arms are, how many back cushions and tufting squares there are. If the finished model differs grossly from the photo, Claude corrects its reading once and the better of the two builds is kept. Params you give always win, and the row'sbuildParamSourcessays where each one came from.upholstered-bed:mattressWidthMm,mattressLengthMmandheadboardHeightMm(from the floor), which are the sizes a shop lists, and optionallybaseHeightMm(the upholstered box the mattress lies in, default 350),frameMm(the frame round the mattress at the sides and the foot, default 50),headboardDepthMm(default 120),channels(vertical rolls across the headboard, default one about every 150 mm) andbedding(defaulttrue). Generated from a photo of a made bed, the sheets came out as shards of glass and the velvet as grey plastic, so a bed like this is built instead. The headboard is a row of separate rolls with real gaps between them, and withbeddingthe bed is made up as in a shop's photo, with a duvet, a throw and pillows, because a bare bed looks like a crate. It is dressed in thefabricsgiven, so a bed sold in four fabrics becomes four variants.imageUrlsare only for reference.drum-shade:diameterMm,heightMm, and optionallycolour(#rrggbb, otherwise read from the photo),cordMm,canopyDiameterMm,canopyHeightMm,cordColour.imageUrls: the front photo, then optionally a photo from below, which becomes a glowing bottom disc. The wall is the front photo unwrapped from the cylinder, with the photographer's light taken out. The photo only shows the front, so the sides and back are mirrored from it.flat:widthMm,heightMm,depthMm, for a record sleeve, book or picture.imageUrls: the front image, then optionally the back. Without a back image, the back and edges take the front's border colour.slatted-armchair, all params optional:widthMm,depthMm,heightMm,seatHeightMm,armHeightMm,sideSlats,backSlats,backDepthMm,woodLengthMmandfabricWidthMm(the real size the crops cover, so the grain and weave come out at true scale),badge.imageUrls: a crop of the wood and a crop of the fabric, cut from the product's own photos.
4. Run and check the preview
Each row of the output table shows the size the model measures after processing, next to the size you asked for. If they match, the model is right.
Every model is also compared with its first photo. The product is cut out of the photo against its plain backdrop, the model's outline is drawn from the same view, and photoMatchUrl links to a picture of the two with their difference: red where the photo has product and the model has none, blue the other way round. Check it before a model goes live. It is the quickest way to spot a wrong arm or a badly joined chaise.
Measured on approved models and on models a person rejected, outline agreement (photoMatchPct) does not separate good models from wrong ones: a generated model's soft edges score lower than a built model with a structural mistake, and a different sofa with the same proportions scores as well as the right one. The largest single difference (photoLargestDifferencePct) did not flag any approved model and does flag the gross mistakes (a missing part, the wrong configuration, the wrong photo), so it is the only number that raises a warning. Whether an arm is the right length or a seat is one piece is for the person looking at the picture to judge. A photo taken in a room is not compared at all, and a white part on a white backdrop (a mattress) cannot be cut out cleanly.
5. Upload
Download the GLB from the link in the output and add it to the product's media. On Shopify nothing else is needed: the 3D viewer and mobile AR switch on by themselves.
Pricing
This actor charges per event, and only for work that succeeded. Compute is included in the price.
| Event | Price | When it fires |
|---|---|---|
model-delivered | $0.40 | Once per usable model, never for a failure |
apify-actor-start | $0.00005 per GB of memory | Once per run, charged by Apify: $0.0002 at the default 4 GB |
Who pays for what:
- This actor charges
model-deliveredfor each usable model. A product that fails to generate costs you nothing here. - Apify charges
apify-actor-startonce per run. - Meshy or Tripo bill their generation credits separately, against your own key. The output table reports how many credits each model consumed, so you can see the real cost per model.
- Claude calls, when you give an Anthropic or OpenRouter key, are billed to that key.
Input and output
Input
{"provider": "meshy","providerApiKey": "your-provider-key","scaleMode": "exact","items": [{"id": "sofa-oslo-3s","imageUrls": ["https://example.com/sofa-front.jpg","https://example.com/sofa-side.jpg"],"widthMm": 2100,"heightMm": 850,"depthMm": 950,"photographedFabricName": "Grey, as photographed","fabrics": [{ "name": "Anthology Beige", "swatchUrl": "https://example.com/fabrics/anthology-beige.jpg" },{ "name": "Anthology Terra", "swatchUrl": "https://example.com/fabrics/anthology-terra.jpg" }]}]}
fabrics is optional and only for upholstered products. weaveMm (default 90) sets how much of the product the smallest tile cut from a swatch covers. A fabric may carry "finish": "leather" or "velvet" (default "weave"), because a photograph cannot say what the material is and each one is shaded differently: leather is glossier with a firmer grain, and velvet is the most matte, with a strong sheen and no bumps.
Each swatch becomes three maps: the colour tile, a relief map at two scales (the edges of the yarn, and the loops or crackle as a whole), and a roughness map that follows the coarse pattern. The gloss therefore breaks up on the tops of a boucle's loops and goes matte along a pull-up leather's crackle. At arm's length, that variation in gloss is most of what tells one material from another, and the colour tells the least. The tile is cut from the sharpest flat part of the photograph, as large as the photograph allows: from a draped swatch a third of it, from a flat one up to 80%. The scale on the model follows, so a coarse pattern repeats every few hand widths instead of every hand width. The finish also decides how coarse a pattern is kept when the photographer's light is taken out: leather keeps its crackle, a weave keeps its loops.
A product built from its dimensions (3c) has no photograph to take its shading from, so the builder bakes shading into the vertices: the seam between two cushions and the seat under a scatter cushion are darker than the open top, as they are in a room.
Output
{"id": "sofa-oslo-3s","status": "ok","glbUrl": "https://api.apify.com/v2/key-value-stores/.../records/sofa-oslo-3s.glb","requestedSizeMm": [2100, 850, 950],"measuredSizeMm": [2100, 850, 950],"generatedSizeMm": [1980, 812, 902],"triangles": 38412,"fileSizeBytes": 2841200,"yawApplied": 0,"distortion": 1.06,"dimensionMismatch": 1.06,"creditsConsumed": 30,"fabricVariants": ["Grey, as photographed", "Anthology Beige", "Anthology Terra"],"fabricCoveragePct": 73.2}
generatedSizeMmis the mesh as the generator made it, before any scaling. When a model comes out squashed, this number shows why.measuredSizeMmis measured on the finished file.dimensionMismatchis how far the generated mesh was from your dimensions before any correction. 1.0 means they agreed. A high mismatch usually means the model came out facing the wrong way.distortionis how much stretching was actually applied. It is always 1.0 in uniform mode.
Actor recommendations
If a product comes back as mush, try quad remeshing before anything else. Set a target polycount on the product and remeshTopology to quads, and the generator rebuilds its reconstruction on a regular grid instead of returning the raw surface. On an LED flood light, the hundred lenses on its face went from blobs of random size, which no post-processing can turn into an array, to round lenses of even size in rows. On hard-surface goods, no other setting in this actor comes close to that. It does not suit every product: on a round garden light the same setting improved the lenses just as much, but the housing lost its rim and its cooling fins. Try one product both ways before you run a whole catalogue.
Three defects make a generated product look generated, and this actor has a setting for each. They are different errors at different scales, and each needs its own tool.
The largest in scale is a surface that should be one plane but wanders gently in and out across its width, or a straight bracket arm that comes back bowed. Smoothing cannot fix it, because the averaging that flattens the wander also rounds off the corner it runs into. planarSnap, on by default, fits one plane to each region that is already almost flat and puts its vertices on that plane, or on the line or corner where planes meet. What is still wavy after that is for normalPasses, which filters the field of face normals instead of the vertices, weighting each neighbour by how nearly it faces the same way, so flat regions settle onto one plane while creases stay sharp. It is off by default, because it also divides gentle curves into planes with creases between them, and on a matte surface those look like crushed foil.
The other two defects are the ones a shopper notices next, and both can be fixed here. The first is bright crystalline chips scattered over a surface that should be one flat colour: about two per cent of generated vertices stand more than a couple of edge lengths off the surface their neighbours describe. spikeThreshold pulls them back. It is on by default, because a vertex that far off the surface is never a real feature. The second is a matte product that looks wrapped in cling film, because generators paint patches of near-mirror gloss onto plain painted metal. minRoughness raises the roughness floor on everything the material does not mark as metal, so screws and clips keep their shine. It is off by default, because on lacquer, glaze or polished stone the gloss belongs to the product. On a black powder-coated flood light, a floor of 0.45 turned a surface with reflections sliding over it into solid black.
Text and logos on a symmetrical product usually come back mirrored, and nothing in this actor can fix that. Generators texture a symmetrical object by painting one half and reflecting it onto the other. That is why a seam runs down the middle of the finished model, and why the two halves of a regular pattern come back as mirror images of each other instead of one continuous grid. Anything written on the product is mirrored too: an IP rating on the right of a flood light housing arrives reversed. Post-processing cannot repair it, because the pixels for the correct letters are not in the file. If a marking matters, either accept it at the size it is actually seen, or retouch it out of the source photo before generating. A product with no logo looks better than one with a reversed logo. Meshy's symmetry_mode used to control this, and Meshy now documents that it no longer affects the output.
Keep lossy formats away from the normal and metallic-roughness maps. This actor keeps them separate from the colour map for you. If you build your own pipeline, the reason is that these two maps do not store colour: one stores a direction in RGB, and the other stores two material properties in two channels. JPEG's chroma subsampling averages exactly those channels between neighbouring pixels, so the artefacts appear as local changes in metalness and surface direction. On a dark product that shows as bright metallic speckle.
Keep surface smoothing on for anything machined. Generators reconstruct a surface from the photograph instead of modelling it, so a lamp housing, a bracket or an appliance arrives with its flat faces gently creased and its round edges slightly wavy. It looks modelled in clay, and shoppers notice it first, because a real product does not look like that. smoothingPasses takes the noise out without changing the shape or the size. Measured on a garden light with smoothing alone, two passes removed 40% of the surface noise, four removed 52% with the lens detail still crisp, and eight removed 61% but began to soften small features. The default is eight all the same, because planarSnap puts the flat faces and the edges back afterwards, so the passes can spend themselves on noise. The two only work at these values together: if you turn planarSnap off, bring smoothing down to about four. Lower it, or set it to 0, for sofas, cushions and rugs, where the unevenness is part of the product. Smoothing improves a good mesh with a rough finish. It does not repair a bad mesh.
A squashed model usually means the mesh contains something your dimensions do not. Generators model what they see, including the power cable, the mounting spike or the strap. A catalogue depth covers none of those, so exact mode stretches the product to make room for them. When the stretch is large, the run reports suggestedSizeMm: the size the mesh's own proportions give at the width you asked for. A garden light listed at 170 x 180 x 51 mm needed 90 mm of depth in the mesh, because the drawing's 51 mm is the housing alone and the mesh includes the bracket behind it.
Turn on reuseGeneratedMeshes while you are getting a catalogue right. Generation is the only part that costs provider credits. Dimensions, rotation, triangle budget, smoothing and texture settings are all applied afterwards. With reuse on, the mesh is kept, and a rerun with corrected numbers takes seconds and uses no provider credits. Each model it delivers is still charged as model-delivered. Turn it off when you are done, because the stored meshes are tens of megabytes each and stay in your Apify storage.
Pick the Meshy model to suit the goods. The default model reconstructs the surface, which works well for soft goods. The smart topology models build clean geometry at a chosen face count and split the object into its real parts. That looks like the right choice for hard-surface products, but they also reinterpret the object instead of reproducing it. Given the same garden light, one of them returned a bulging ring and twice as many LEDs as the photograph shows. Try both on one product before you run a catalogue through either.
Set yawDegrees when a model faces the wrong way. The front of a sofa cannot be worked out from geometry alone. The actor turns a model a quarter turn when that clearly fits your width and depth better, but a front-to-back flip needs one number from you. Check the preview, add "yawDegrees": 180 and run that product again.
Leave Draco compression and WebP textures off unless the model is for a web viewer only. Both make the file considerably smaller, and both put AR on phones at risk. Google's documentation says two different things. The Scene Viewer page lists exactly two supported extensions, KHR_materials_unlit and KHR_texture_transform. The newer Android XR page lists Draco, KTX2 and WebP among others and says they work "through both GltfModelEntity and Scene Viewer", but that page describes Android XR. On the phones your customers use, the older page is the accurate one: Draco-compressed files are reported to fail in ARCore and in ARKit, where they do not open at all. Shopify's own web viewer decodes Draco without trouble, so if the model is for the product page and not for AR, turning Draco on halves the file. The defaults choose working AR over a smaller download.
Use scaleMode: "uniform" when your dimensions include something the mesh does not. Exact mode stretches the mesh to match your numbers, which is right when the numbers describe the same object the photos show. If your listed depth includes a headrest that the photos do not show, uniform mode keeps the generated proportions and fits the model inside your box instead.
Show the model with the right tone mapping, or your own viewer will wash it out. This costs nothing and matters more than any setting in this actor. Measured against the original photographs in CIELAB, model-viewer keeps 66% of a sofa's chroma with tone-mapping="neutral" and only 29% with agx, because AgX is a filmic mapper and desaturates on purpose. Neutral is already the default in model-viewer 4.x, so the usual mistake is overriding it. Two things to watch: values other than aces, agx and neutral fall back to Neutral without an error, and exposure is multiplied by 1.3 when Neutral runs with the neutral or legacy environment, so exposure values are not comparable between tone mappers.
Turn off dynamic render scaling if you use auto-rotate. model-viewer drops to a quarter of the pixels when a frame takes over 60 ms, and only restores full resolution once the scene stops moving. A rotating model never stops, so the page stays blurry the whole time instead of only while dragging. Add this line after the element is defined:
customElements.get('model-viewer').minimumRenderScale = 1;
Its getter returns undefined, so check the render-scale event instead of reading the property back.
Watch the mismatch column. A value above about 1.3 means something is wrong with that product: bad dimensions, a bad mesh or a wrong yaw. It is reported in both scale modes, because it describes the disagreement itself, before any correction.
FAQ and support
Do I need a Shopify app to display these? No. Shopify supports GLB natively: upload the file to the product's media, and the 3D viewer and mobile AR switch on without an app. WooCommerce and Wix need a viewer plugin, and free ones exist. Without one, WordPress refuses to upload a GLB at all.
Why is there no USDZ file for iOS?
The USDZ the generators return is not the model you approved. It has the generator's own arbitrary scale and origin, and none of this actor's corrections are applied to it. Shipping it next to a correct GLB would show a wrongly sized model to every iPhone shopper. Shopify and model-viewer both build iOS AR from the GLB, so the corrected file is the one that gets used.
Why do I have to supply dimensions? The model's real-world scale comes from them. Both providers offer an "auto size" feature that estimates real-world height with AI vision. An estimate makes AR look plausible on its own and wrong next to a real object.
What if a photo produces a bad mesh? Shiny, transparent and very intricate products fail most often. You are not charged for a model that fails, and the output row explains what went wrong.
Where are my files stored, and for how long? In a named Apify key-value store, which is kept indefinitely. Use the same storage name across runs to build one library. The download links stay valid as long as the record exists.
Can I sell products using these models? That depends on your provider's terms and your tier with that provider. This actor does not change it. See step 1 of the tutorial above.
Report problems through the Issues tab on this actor.
Related tools
- Shop Intel: finds online shops whose catalogues lack photos, dimensions or 3D.
- Website Lead Qualifier: what to pitch to any business website or Google Maps list.
- Made by ITneeds: examples of finished models.


