Viewrium Docs

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:

edgeWhat it is
frameThe frame in your photographs, built as a 3D model and fitted around the canvas. Only when the photographs show a frame.
solidA slab with a solid-colour edge, edge_color (a #RRGGBB hex, default #FFFFFF).
stretchA 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 order out, 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 form is painting and wall_mounted is true.

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
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" }
    }
  }'
Python
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)
TypeScript
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.

Python
product = client.products.create(ProductCreateRequest(resume_token=parked.resume_token))
TypeScript
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).

  • width x height describe the canvas - the artwork itself. Send painting.dimensions_of: "frame" to give the frame's outer size instead (only with the frame edge): the frame then meets that size and the canvas is fitted inside it.
  • depth is the slab's depth, for a solid or stretch edge. A frame's depth follows the frame, so depth with 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 is false, the model is blocked as painting_unusable, nothing is priced, and the token cannot be committed. Send a clearer photograph in a new request.
  • framed - whether the artwork is framed. The frame edge needs it.
  • reason - the inspection's own sentence. Show it as written.
  • suggested_dimensions_of - frame when the piece is framed and its product page's stated size reads as the framed size; a good default for dimensions_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
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 } }'
Python
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"
TypeScript
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 rebuild is gone.
  • It fails - the delivered model, settings and size stay, and rebuild reads status: "failed" with its error. 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.

Statusdetails.reasonWhen
400painting_imagesThe create's order asks for images.
400painting_model_requiredThe create's order does not ask for a model.
400painting_overridesoverrides.generate_unseen or overrides.force_model is sent - neither applies to a painting.
400painting_wall_mountedwall_mounted: false on a create or an edit.
400painting_model_urldraft.painting with a model_url source.
400depth_with_framedepth with the frame edge.
400dimensions_of_frame_without_framedimensions_of: "frame" with a solid or stretch edge.
400no_frame_detectedThe commit asks for the frame edge and the inspection found no frame (painting.framed: false).
400painting_unusableCommitting a token whose inspection found no usable photograph.
400not_a_paintingpainting on a product, or a token, that is not a painting.
400frame_not_orderedAn edit asks for the frame edge on a painting ordered without a frame.
400frame_dimensions_requiredAn edit sets dimensions_of: "frame" with no frame size to describe.
400frame_smaller_than_mouldingAn 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.
400painting_rescaleproducts.rescale on a painting.
409generatingAn edit while the painting's model is being made or rebuilt.
409painting_not_builtAn 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.

On this page