Paintings
Turn a photograph of a painting into a wall-mounted 3D model - the edge options, sizing, the inspection, and editing afterwards.
A painting is its own kind of product. Viewrium takes the most front-facing photograph of the artwork, straightens it into a rectangle and builds the canvas from it. The artwork is used as photographed and is never redrawn. You choose what goes round it:
edge | What it is |
|---|---|
frame | The frame in your photographs, built as a 3D model and fitted around the canvas. Only when the photographs show a frame. |
solid | A slab with a solid-colour edge, edge_color (a #RRGGBB hex, default #FFFFFF). |
stretch | A slab with the artwork's border stretched over the sides, like a gallery-wrapped canvas. |
Leave edge out and Viewrium decides from the photographs: frame when it finds a frame, solid
when it does not.
A painting is created through the same POST /v1/products as every product,
with three differences:
- It is a model. A painting produces one wall-mounted 3D model and no product images. Leave
orderout, or send{ "images": false, "model": true }. - It costs the normal model price - 10 credits, whatever the edge.
- It is always wall-mounted. The product's
formispaintingandwall_mountedistrue.
Create a painting
Put painting on the draft. Its presence is what selects the painting path. Sources are the
usual photographs, a video or a product page (image_urls, video_url, source_url); one
front-facing view of the whole artwork is enough. A 3D file (model_url) is not a painting source.
curl https://api.viewrium.com/v1/products \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"image_urls": ["https://cdn.example.com/harbour-at-dusk.jpg"],
"draft": {
"title": "Harbour at dusk",
"dimensions": { "width": 60, "height": 45 },
"painting": { "edge": "frame" }
}
}'from viewrium import (
ModelDimensions,
PaintingEdge,
PaintingOptions,
ProductCreateRequest,
ProductDraft,
Viewrium,
)
client = Viewrium("sk_live_...")
parked = client.products.create(
ProductCreateRequest(
image_urls=["https://cdn.example.com/harbour-at-dusk.jpg"],
draft=ProductDraft(
title="Harbour at dusk",
dimensions=ModelDimensions(width=60, height=45), # cm: the canvas
painting=PaintingOptions(edge=PaintingEdge.FRAME),
),
)
)
print(parked.painting.usable, parked.painting.framed, parked.cost.total_credits)import { Viewrium } from "@viewrium/sdk";
const client = new Viewrium({ apiKey: "sk_live_..." });
const parked = await client.products.create({
image_urls: ["https://cdn.example.com/harbour-at-dusk.jpg"],
draft: {
title: "Harbour at dusk",
dimensions: { width: 60, height: 45 }, // cm: the canvas
painting: { edge: "frame", edge_color: "#FFFFFF", dimensions_of: "canvas" },
},
});Like every create, this parks: nothing is written, and you commit the quote by posting
resume_token back. See Quote first, then commit.
product = client.products.create(ProductCreateRequest(resume_token=parked.resume_token))if (parked.status !== "parked") throw new Error("unexpected: already committed");
const product = await client.products.create({ resume_token: parked.resume_token });The resume may change draft.painting field by field, like any draft field - choose another
edge once you have read what the inspection found.
Sizes
Sizes ride in the draft's dimensions and unit, like every product's (cm unless you send
unit).
widthxheightdescribe the canvas - the artwork itself. Sendpainting.dimensions_of: "frame"to give the frame's outer size instead (only with theframeedge): the frame then meets that size and the canvas is fitted inside it.depthis the slab's depth, for asolidorstretchedge. A frame's depth follows the frame, sodepthwith a frame is refused.- Give one side and the other follows the artwork's proportions.
- Every size is optional. With none, the canvas's long side is 50 cm and a slab is 2 cm deep. A framed painting with no size gets the same canvas, with the frame fitted around it.
The product's height, width and depth are the finished outer size - the fitted frame or
the slab - so the viewer's size line and the AR scale are right. product.painting keeps the
settings themselves: edge, edge_color, dimensions_of, canvas_width_cm,
canvas_height_cm, depth_cm and has_frame.
When the photograph is not usable
The inspection asks one question: is there a photograph that shows the whole artwork front-on,
unobscured? The parked answer carries what it found in painting:
{
"status": "parked",
"form": "painting",
"wall_mounted": true,
"model": { "ordered": true, "allowed": false, "blocked_by": ["painting_unusable"] },
"cost": { "images": 0, "image_credits": 0, "model_credits": 0, "total_credits": 0 },
"painting": {
"usable": false,
"framed": true,
"reason": "A hand covers the lower left corner of the artwork.",
"suggested_dimensions_of": "canvas"
}
}usable- whether a photograph can be straightened into the canvas. When it isfalse, the model is blocked aspainting_unusable, nothing is priced, and the token cannot be committed. Send a clearer photograph in a new request.framed- whether the artwork is framed. Theframeedge needs it.reason- the inspection's own sentence. Show it as written.suggested_dimensions_of-framewhen the piece is framed and its product page's stated size reads as the framed size; a good default fordimensions_of.
Editing a painting
A painting's settings stay editable. products.update with painting (edge, edge_color,
dimensions_of, each optional) and/or new dimensions rebuilds its model in the background:
curl -X PATCH https://api.viewrium.com/v1/products/$PRODUCT_ID \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{ "painting": { "edge": "stretch" }, "dimensions": { "width": 70 } }'from viewrium import ModelDimensions, PaintingEdge, PaintingUpdate, ProductUpdateRequest
product = client.products.update(
product.id,
ProductUpdateRequest(
painting=PaintingUpdate(edge=PaintingEdge.STRETCH),
dimensions=ModelDimensions(width=70),
),
)
print(product.painting.rebuild.status) # -> "running"const updated = await client.products.update(product.id, {
painting: { edge: "stretch" },
dimensions: { width: 70 },
});
console.log(updated.painting?.rebuild?.status); // "running"The live model stays served while it rebuilds. The answer shows painting.rebuild with
status: "running" and the requested settings; the model stays ready, and the product's
settings and size stay the delivered ones until the rebuild finishes:
- It succeeds - the new model, the requested settings and the new outer size replace the old
ones, and
rebuildis gone. - It fails - the delivered model, settings and size stay, and
rebuildreadsstatus: "failed"with itserror. Send the edit again to retry.
Read the product with products.retrieve to follow it. A rebuild is not charged.
A painting ordered with a frame keeps it and can switch between frame, solid and
stretch as often as you like; the frame is always re-fitted to the current canvas. A painting
ordered without one can never get a frame. Resize a painting through products.update
with new dimensions - products.rescale does not apply to it.
Refusals
A refused painting request answers the error envelope with the
reason in details.reason.
| Status | details.reason | When |
|---|---|---|
400 | painting_images | The create's order asks for images. |
400 | painting_model_required | The create's order does not ask for a model. |
400 | painting_overrides | overrides.generate_unseen or overrides.force_model is sent - neither applies to a painting. |
400 | painting_wall_mounted | wall_mounted: false on a create or an edit. |
400 | painting_model_url | draft.painting with a model_url source. |
400 | depth_with_frame | depth with the frame edge. |
400 | dimensions_of_frame_without_frame | dimensions_of: "frame" with a solid or stretch edge. |
400 | no_frame_detected | The commit asks for the frame edge and the inspection found no frame (painting.framed: false). |
400 | painting_unusable | Committing a token whose inspection found no usable photograph. |
400 | not_a_painting | painting on a product, or a token, that is not a painting. |
400 | frame_not_ordered | An edit asks for the frame edge on a painting ordered without a frame. |
400 | frame_dimensions_required | An edit sets dimensions_of: "frame" with no frame size to describe. |
400 | frame_smaller_than_moulding | An edit gives the frame an outside width or height no larger than twice its moulding, which leaves no room for the canvas. The message gives the minimum in the request's unit. |
400 | painting_rescale | products.rescale on a painting. |
409 | generating | An edit while the painting's model is being made or rebuilt. |
409 | painting_not_built | An edit on a painting whose model never finished. |
A malformed value - an edge outside frame | solid | stretch, or an edge_color that is not a
#RRGGBB hex - is a 422 like any request-shape error.