- Brand
- HZplan
- Category
- Content & Design
- Primary Subcategory
- CAD & 3D Design Tool Control
Integration details
Description
HZplan turns floor-plan images or written dimensions into structured, editable home plans your assistant can reason about. Users can create and refine multi-storey layouts with floor-specific rooms, walls, doors, windows, furniture, and surface finishes; import and calibrate floor-plan images; inspect dimensions and topology; add notes and custom attributes; make targeted geometry edits; create materials from reference images; generate 3D object models; and export storeys as PNG or SVG.
- Integration type
- Plugin
- Verification status
- Not applicable
- Platform
- ChatGPT
- Primary Subcategory
- CAD & 3D Design Tool Control
- Secondary Subcategories
- None listed
- Brand
- HZplan
- Access
- Account required
- First tracked
- 2026-06-12
- Tool count
- 46
- Market
- US
The Primary Subcategory used for this profile’s headline score.
Other Subcategories where the Integration is listed.
Your score is coming
ChatGPT now suggests Plugins on its own when they match a user's request.Your Plugin Discovery Score measures how often yours appears, and it will show here as soon as it’s ready.
What discovery looks like

Get alerts for HZplan
Get updates when HZplan’s Discoverability Score or category rank changes.
Competing in ChatGPT CAD & 3D Design Tool Control
View Category46 tools agents can invoke
Dress a whole plan in an interior style (scandinavian, classic or modern): every room's floor, walls and ceiling get the style's finishes, chosen by room type. scope "finishes" leaves furniture and its 3D models alone. scope "finishes_and_furniture" first furnishes every empty room it can, as furnish_room does (rooms_furnished and rooms_skipped say which), then also sets the style's 3D models on all furniture. WARNING: with existing_finishes "replace" (the default) every existing floor, wall and ceiling finish on the plan is DELETED first, including materials the user made from their own photos. Confirm with the user before replacing finishes they chose; use existing_finishes "keep" to dress only bare surfaces. Finishes are visible only in the 3D walk; give the user the walk link from the result. Only rooms are dressed. unnamed_areas lists enclosed areas with no room (storey_id, area_m2, point_mm): ask the user what each is, name it with name_space_at_point at point_mm, then style again. ERROR CODES plan_not_found: the plan does not exist for this user. plan_has_no_floors: the plan has no floors to style. Add a storey first. no_named_rooms: no floor has a room; nothing was changed. Name the areas in unnamed_areas with name_space_at_point, then retry. partial_apply: some floors could not be styled (see storeys[].error); the others were. style_not_applied: no floor could be styled (see storeys[].error); no finishes were written, and with scope finishes nothing is changed. Usually the floor does not solve; fix it and retry. feature_disabled: scope finishes_and_furniture is not available on this server. Use scope finishes, and tell the user. generator_busy: (finishes_and_furniture) the furniture generator is busy; no finishes were written. Try again in a minute. furnish_invalid_request, generator_failed: (finishes_and_furniture) the furniture generator failed; no finishes were written. Retry later, or use scope finishes. generator_not_configured: (finishes_and_furniture) the furniture generator is not set up on this server. Use scope finishes. plan_write_conflict: (finishes_and_furniture) the plan changed while rooms were being furnished; no finishes were written. Re-read the plan and retry. With these generator codes, rooms_furnished lists the rooms furnished on earlier floors; that furniture stays. A floor that could not be furnished for another reason is listed in furnish_errors and does not stop the style.
apply_interior_style
Step 2 of 2 in the image → hzplan pipeline. Runs full inference + conversion on ONE floor (picked by floor_id from a prior split_floor_plan response) and either mints a new hzplan (plan_id omitted) or appends the floor as a new storey to an existing plan (plan_id given). Burns 1 unit of `plan_from_image_monthly_*` quota per call. WHEN TO CALL After split_floor_plan returned successfully, the user has seen the previews, and they've told you which floor(s) to import. Call this ONCE per chosen floor. For a 3-floor plan the user wants fully imported, that's 1 split_floor_plan call + 3 convert_floor_to_plan calls. DO NOT retry a successful call. DO NOT call this before split_floor_plan. WHAT TO PASS image: the SAME image used in split_floor_plan (ChatGPT auto-fills again from the user's upload). floor_id: the floor_id from the split_floor_plan response. storey_name: human label for this storey, e.g. 'ground', 'first', 'kitchen floor'. Default to the suggested_name from split_floor_plan if the user doesn't say otherwise. plan_id: OMIT on the first conversion (mints a brand-new plan). PASS the previous call's plan_id on all subsequent conversions so all floors land on ONE plan as separate storeys. plan_title: only relevant when plan_id is omitted — title for the new plan. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, scale_source, scale_calibrated, quota, summary } - plan_id: pass this to the NEXT convert_floor_to_plan call if there are more floors to import. - storey_id: the storey id assigned inside the plan ('s1', 's2', ...). - scale_source / scale_calibrated: how the scale was estimated. A converted storey is never calibrated (scale_calibrated: false); its dimensions are an estimate until rescale_storey runs. - quota: current usage info. - summary: diagnostic counts (walls, openings, mm_per_px). NEXT STEPS - BEFORE telling the user the plan is ready, ask for one real measurement they know (a wall length, a room's width or a door's width). Compare it with the same dimension from get_storey_plan and call rescale_storey once with factor = real / current. Skip this only if the user declines. - Then render it with render_storey_widget(plan_id, storey_id) to show the result. - The user can then refine the plan with upsert_entity / delete_entity / set_object_feature_side etc. — those operate on walls / openings / objects inside the storey. ERROR CODES feature_not_enabled: account lacks plan_from_image entitlement. Tell the user; don't retry. quota_exceeded: monthly limit reached. Tell the user. floor_id_not_found: floor_id doesn't match the image you passed. Did the image change? Call split_floor_plan again with the current image. hzplan_validation_failed: the converted plan didn't pass our solver. Report to the user. service_*: transient pipeline error. Safe to retry ONCE. image_url_fetch_failed: user's image URL expired. Ask them to re-upload, then start over with split_floor_plan.
convert_floor_to_plan
Generate a random demo flat as a reference to explore: rooms, doors, windows, furniture and an interior style. It is saved as a new plan in the user's account. It is NOT a design of the user's home; say so when you show it. Pass settings (or {} for the defaults). get_authoring_guide lists the supported settings under demo_plan_settings; an unsupported setting is refused with the list. Generation runs in the background (typically 15-80 s). Poll get_demo_plan_status with the returned job_id every few seconds until status is succeeded or failed. ERROR CODES invalid_settings: a setting is unknown or has a bad value; the message lists the supported ones. Fix and retry. too_many_in_flight: the user already has the maximum number of demo plans generating. Wait for one to finish. rate_limited: too many generations in a short window. Wait a minute, then retry. feature_disabled: demo plans are not available on this server. Tell the user. queue_not_configured, queue_unavailable: the server cannot start a demo plan right now. Retry later.
create_demo_plan
Turn an image into a reusable custom floor, wall or ceiling material in the user's private library, then apply it with a surface_finish material_asset(...) fact. NEVER TINTABLE: materials made this way carry the image's own colour, so the finish must use material_asset(...) alone — adding color_hex to it is rejected as finish_material_not_tintable. WHAT THIS DOES: it tiles the image you give it, unchanged. There is no enhancement, no seam repair, no perspective correction and no quality check. HOW THE IMAGE IS CONSUMED: it is tiled edge-to-edge across the WHOLE surface at the real-world size you declare, then viewed at eye level in a 3D walk-through. Only its base colour is used — surfaces are flat-shaded, with no relief or bump. So: - Opposite edges must meet seamlessly: left against right, top against bottom. - MIRRORING IS A TRAP: don't mirror an image to force its edges to match. Every tile then becomes symmetric about that axis, which at room scale reads as an obvious repeating band — usually more visible than the seam it removed. - One-off features repeat on EVERY tile: a single knot, stain, logo or baked-in shadow becomes a grid of them. BEST INPUT: an image YOU generate — a flat, straight-on, evenly lit, seamlessly tileable square texture with no room, furniture, shadow, border or text. Generate it, show the user, let them approve it, then call this tool with it. A photo of a real floor or wall usually looks bad tiled, because its perspective, shadows and distinctive marks repeat on every tile. If the user insists on one, warn them first and let the result speak. PASSING THE IMAGE (do NOT base64-encode it yourself — image bytes won't fit in a tool argument): - Preferred: pass the image you just generated (or the user's uploaded one) as the `image` FILE REFERENCE — ChatGPT fills download_url + file_id automatically. - FALLBACK whenever `image` doesn't populate — and an image you generated that won't attach is exactly that case: call request_image_upload, have the user open the link and save/drop the image there, then pass image_ref:{ upload_id }. This is the reliable path when attaching the file directly fails. - Non-ChatGPT clients only: `raw_url` (public http(s) URL or data:...;base64,... URI). SIZE IS REQUIRED: texture_width_mm/texture_height_mm are the real-world size of ONE tile. Choose deliberately — 4-6 plank widths for wood (typically 1000-1500mm), the tile pitch for tiles, about 1000mm for carpet — and tell the user the number you chose. The millimetres must match what the image ACTUALLY depicts, not the pixel canvas: if you drew planks laid out for a 1050mm tile on a 1200px canvas, declare 1050. Nothing downstream can detect a mismatch — every feature simply renders at the wrong real-world size. LIMITS: PNG/JPEG/WebP up to 10MB, 50 custom materials per account. Search search_material_assets first — the curated catalog is higher quality than most generated tiles. IMMUTABLE ONCE CREATED: a material has exactly one revision and tintable is false, so nothing can adjust it afterwards — there is no edit, no re-tint and no new revision. A follow-up like 'make it a bit darker' means generating a NEW image and calling this tool again, which burns another slot against the per-account cap. Settle the image with the user BEFORE calling. ON SUCCESS: structuredContent.advisories[] may carry one warning (currently aspect_mismatch, when the image's pixel aspect disagrees with the declared repeat by more than 1.2x). It is advisory, never a rejection — the material is created either way. It is also the only quality signal this tool produces, so relay it to the user. The same text is appended to this tool's text result. ERROR CODES unsupported_image_format: not PNG/JPEG/WebP. Convert and retry. image_too_large: over 10MB. Use a smaller image. material_limit_reached: delete one with delete_surface_material first. storage_failed: transient. Safe to retry ONCE. storage_unavailable: server misconfiguration. Don't retry; tell the user.
create_surface_material_from_image
Create or update a private SVG illustration in the current user's library. Omit illustration_id to create a new illustration (name and svg_markup are then required); pass illustration_id to update an existing one (only the fields you include change, the rest are preserved). render_svg_preview only previews — this tool persists.
upsert_illustration
Use this when the user wants to start a brand-new house plan from scratch. Creates an empty private plan owned by the current user and returns its plan_id; follow up with create_storey, then add walls/openings/objects via upsert_entity. To build a plan from a floor-plan image instead, use split_floor_plan + convert_floor_to_plan.
create_plan
Create a storey. Optionally follow with upsert_entity(type=storey, assertions=[size_mm(width_mm, depth_mm)]) to pin a fixed rectangle (image-converted plans always author one); without it the storey extent is derived from the walls.
create_storey
Permanently delete one of the user's own custom materials (a mat.user.* ID) and its stored image. Frees a slot against the per-account limit. IMPORTANT: plans that already use this material KEEP their authored material_asset fact and will render the missing-material fallback. This never edits a plan. Tell the user how many of their plans referenced it (returned as referencing_plan_count) before they rely on the result. Only the user's own custom materials can be deleted. Catalog materials cannot. ERROR CODES material_not_found: not a custom material in this user's library (already deleted, a catalog ID, or someone else's). Don't retry. storage_unavailable: server misconfiguration. Don't retry; tell the user.
delete_surface_material
Permanently delete one of the user's own generated 3D models (the asset_id from get_object_3d_status, or an object's model_3d) and its stored .glb. This cannot be undone. Refused while any of the user's plans still uses the model: detach it with set_object_3d_asset (asset_id: null) or delete that plan first. This never edits a plan. Only the user's own generated models can be deleted. System library models cannot. ERROR CODES asset_in_use: referencing_plan_count of the user's plans still use it. Detach it, then retry. asset_not_found: not a generated model in this user's library (already deleted, a system model, or someone else's). Don't retry.
delete_object_3d_asset
Delete one wall, opening, object, or auxiliary height level from the current plan head by its entity id. Structural floor/ceiling levels are removed only with delete_storey. Deletion fails if another entity still depends on this one through host, offset, or attached_to; the response lists blocking entity ids so they can be re-hosted or re-bound first. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
delete_entity
Archive one private illustration from the current user's library.
delete_illustration
Use this when the user wants to permanently delete an entire plan. Removes the plan and all of its storeys, along with any uploaded floor-plan images and public snapshots tied to it. This is destructive and cannot be undone. Prefer this over deleting storeys one by one when the whole plan should go; confirm with the user first because there is no recovery.
delete_plan
Use this when the user wants to remove an entire floor. Deletes one storey and everything on it — its rooms, spaces, objects, walls, and openings — from the current plan head. This is destructive and is not reversible through the API. It fails only if an entity on another storey still references something on this one; the response then lists the blocking entity ids so they can be re-anchored or deleted first, then retry.
delete_storey
Render one storey to a downloadable PNG (default) or SVG and return a link (valid 1 hour). Toggle room names (show_names), furniture (draw_objects), and measurements (show_measurements, default off). When show_measurements is true the dimensions are AI-estimated — tell the user to verify, and suggest rescale_storey to calibrate. The exported image always carries an 'AI-generated' mark.
export_storey
Furnish one empty room: the generator picks and places furniture for its type. Kitchens get units and appliances; bathrooms, shower rooms and WCs get their fixtures. Works only when the room has no furniture. Fixed fittings (such as radiators and stairs) and decor don't count; otherwise it fails with room_not_empty and lists what is there. Rooms that share one open-plan area are furnished together. Rooms whose shape cannot be furnished are refused with room_shape_unsupported. ERROR CODES feature_disabled: furnishing is not available on this server. Tell the user. plan_not_found: the plan does not exist for this user. Check plan_id. storey_required: the room id exists on more than one floor. Pass storey_id. storey_not_found: no floor with that storey_id. Use one from get_plan. storey_unsolvable: the floor's geometry does not solve, so its rooms cannot be found. Fix the floor, then retry. room_not_found: no room with that space id and a floor area on the floor. Use a space id from get_storey_plan. room_not_empty: the room has furniture (objects lists it). Delete those objects first, or pick an empty room. room_type_unsupported: the room's type is not one that can be furnished. Pass room_type if the room is one of its values. room_shape_unsupported: the room's shape cannot be furnished. Tell the user; furnish it with upsert_entity instead. nothing_placed: no furniture fitted in the room (missing lists what was tried). Tell the user; the room may be too small. generator_busy: the furniture generator is busy. Try again in a minute. furnish_invalid_request, generator_failed: the furniture generator could not furnish this room. Tell the user and try again later. generator_not_configured: the furniture generator is not set up on this server. Tell the user. plan_write_conflict: the plan changed while the room was being furnished. Re-read the plan and retry.
furnish_room
Generate a 3D model (.glb) for an existing object from a reference image and attach it to that object. The object must already exist with solved width/depth, persisted height_mm, and a physical attached_to fact (create/place it via upsert_entity first). Plan facts are authoritative: the legacy optional height_mm input is accepted but never changes the object's authored height. Async: returns a job_id; poll get_object_3d_status until succeeded — on success the model is auto-attached (confirm with get_object, or use set_object_3d_asset if attached is false). To permanently remove a generated model the user no longer wants, use delete_object_3d_asset. PASSING THE IMAGE (do NOT base64-encode it yourself — image bytes won't fit in a tool argument): - Preferred: pass the user's uploaded or in-chat generated image as the `image` FILE REFERENCE — ChatGPT fills download_url + file_id automatically. - FALLBACK whenever `image` doesn't populate (a generated image that won't attach, a mobile upload, or 'file too large'): call request_image_upload, have the user open the link and save/drop the image there, then pass image_ref:{ upload_id }. - Non-ChatGPT clients only: `raw_url` (public http(s) URL or data:...;base64,... URI). ERROR CODES feature_not_enabled: account lacks 3D generation entitlement. Tell the user; don't retry. too_many_in_flight: concurrent job cap reached. Wait for a running job to finish, then retry. quota_exceeded: monthly 3D generation limit reached. Tell the user. rate_limited: too many requests in a short window. Wait a moment, then retry. object_not_found: plan_id or object_id doesn't exist. Verify and correct. storey_required: the object id exists on more than one floor. Pass storey_id (details.storeys lists them). footprint_unavailable: object has no solved width/depth yet. Ensure the object is fully constrained via upsert_entity first. vertical_placement_unavailable: object lacks authored height_mm or attached_to. Place it physically before generating an asset. image_too_large: image exceeds the size limit. Use a smaller or compressed image. unsupported_image_format: image isn't PNG/JPEG/WEBP. Convert and retry.
generate_object_3d
Poll a 3D-generation job started by generate_object_3d. Returns status (queued/running/succeeded/failed/cancelled), the asset_id once succeeded, and `attached` (whether the model_3d link landed on the object — if false after success, call set_object_3d_asset).
get_object_3d_status
Poll a demo plan job started by create_demo_plan, every few seconds while status is queued or running (typically 15-80 s in total). When succeeded: plan_id, view_url (2D plan), walk_url (3D walk) and stats (net area m², rooms, bedrooms, furniture pieces, style). Show the user both links and say the plan is a random reference, not their home. When failed: tell the user error (error_code says why) and offer to try again with create_demo_plan. ERROR CODES job_not_found: no demo plan job with that id for this user. Check the job_id from create_demo_plan. demo_plan_incomplete: the job finished without a usable plan. Start a new one with create_demo_plan.
get_demo_plan_status
Get one illustration, including its stored SVG markup. Use this tool for illustration data; MCP resources are widget HTML only.
get_illustration
Get one object with its current constraints and solved placement metadata. Use set_object_illustration for illustration-only changes; use this before upsert_entity when you need to preserve existing assertions during broader object edits. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
get_object
Get the stored top-level summary for one plan. This returns summary data, not the full plan document.
get_plan
Get the plan's spatial room topology. Returns one node per derived free-space region (the rooms shown in the UI) with its storey, an interior `center` point, area, and bound `name`/`kind`/`room_id` (null until named); and edges for the openings/doorways connecting rooms. Room tools such as furnish_room take `room_id`, never the area's `space_id`. To name a room, pass that node's `center` as the (x_mm, y_mm) to name_space_at_point.
get_plan_topology
Get bounded structured detail for one storey. Use storey_id from get_plan when possible. Pass include_surface_context=true only when decorating: it adds current space-by-space wall-face intervals and floor/ceiling targets.
get_storey_plan
List accessible system illustrations and the current user's private illustrations. Use this tool for illustration discovery; do not use MCP resources for library data.
list_illustrations
Use this when you need to find a plan_id or show the user what plans they have. Lists the current user's private plans with their ids and titles. Read-only.
list_plans
List current objects in one room.
list_room_objects
Patch-merge geometry assertions into an EXISTING entity by (predicate, anchor) key: each provided assertion replaces the one with the same key and ALL other assertions are preserved. Use this to change one field (e.g. thickness_mm) without resending the whole set — unlike upsert_entity, which replaces the entire assertion list. Does not create entities (use upsert_entity for that). For `offset`, the anchor (the first arg, e.g. "left", "top", "center_x") is part of the key, so different anchors merge independently. Custom attributes (set via `set_entity_attributes`) are unaffected. The result echoes the entity's full current assertion set. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
set_entity_assertions
Create or update a semantic space name by dropping an interior pin on a solved storey. Use this for named floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings already define the enclosure. This does not author new geometry. Instead it stores a semantic pin and binds the name onto the derived free-space region that contains that point. The point must fall inside exactly one derived free-space region. Provide space_id to rename or move an existing named space; omit it to create a new one.
name_space_at_point
Remove geometry assertions from an EXISTING entity by (predicate, anchor) key; all other assertions are preserved. Provide the same {predicate, args} shape — only the key is used (values ignored). For `offset`, pass the anchor in args[0] (e.g. {predicate:"offset", args:["left"]}). Removing a key the entity does not have is a no-op. Removing a load-bearing constraint may leave the entity under-constrained — the solver result will say so. The result echoes the entity's full current assertion set. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
remove_entity_assertions
Render one illustration SVG preview in the ChatGPT widget. Preview only; this does not save anything. Use upsert_illustration to persist SVG markup.
render_svg_preview
Render one named room from a storey in the ChatGPT widget.
render_named_room_widget
Render the whole-plan topology graph in the ChatGPT widget.
render_plan_topology_widget
Render one storey in the ChatGPT widget.
render_storey_widget
Get a one-time upload link for a floor-plan image when you can't attach it directly or the file is too large to pass inline. Works for ANY client (Claude, ChatGPT, etc.). Quota-free. FLOW 1. Call this (no args) → { upload_id, upload_url }. 2. Show the user the upload_url and ask them to open it and drop their floor-plan image (one tap). 3. Then call split_floor_plan with image_ref: { upload_id }. If they haven't finished you'll get code 'upload_pending' — wait a moment and retry. 4. Reuse the SAME upload_id for convert_floor_to_plan — one upload covers split + convert. ChatGPT: prefer attaching the file (pass `image`). But if that keeps failing or the file is too large, FALL BACK to this upload link — the user just opens it and drops the image, and you only ever pass back image_ref:{ upload_id } (no base64). LAST-RESORT FALLBACK: if the user can't or won't use the link, tell them they can create the plan directly in the web app: https://hzplan.uk/plans/new/from-image — never leave them with no way forward.
request_image_upload
Sets the SCALE of `storey_id` losslessly: it records a per-storey scale factor instead of rewriting geometry. real_mm = abstract × factor, so reads report the rescaled dimensions while the stored geometry is untouched — the operation is fully reversible (×2 then ×0.5 returns to the original). Repeated calls COMPOSE (the effective factor multiplies). Wall thicknesses, image bboxes, and angles are unaffected. Runs the geometry engine at the rescaled (real-mm) size and refuses to commit if the storey becomes infeasible. WHEN TO CALL After convert_floor_to_plan, as soon as the user gives one real measurement: a converted storey's scale is an estimate (scale_calibrated: false) and can be far off. Also usable on hand-authored plans where the user wants to scale a storey up/down. Call ONCE per scale change — if the call succeeds, the plan is updated; if it fails (solver_infeasible / solver_ambiguous), the plan is unchanged. WHAT TO PASS plan_id: the plan owning the storey. storey_id: the storey to rescale (e.g. 's1', 's2'). factor: number applied to the CURRENT scale; the resulting effective factor must stay in [0.1, 10]. Compute as `target_real_world_length / current_plan_length`. Example: AI sees a wall on the image and thinks it's 4000 mm in real life; the plan currently reports that wall as 3500 mm; factor = 4000 / 3500 ≈ 1.143. WHAT YOU GET BACK ON SUCCESS { ok: true, plan_id, storey_id, content_version, factor, storey_size_mm: [w, h] } - factor is the new EFFECTIVE scale factor for the storey. - storey_size_mm is the new outer dimensions in mm. ERROR CODES plan_not_found / storey_not_found: bad ids. invalid_factor: the resulting effective factor is out of [0.1, 10]. solver_infeasible: rescaled storey is overconstrained (e.g. a fixed-size object no longer fits). Plan was NOT modified. Try a different factor or fix the object first. solver_ambiguous: rescaled storey is underconstrained. Add missing offsets and retry, or revert. solver_error: the geometry engine itself failed; surface to user. Do NOT retry on success. Compose multiple rescales by calling once per storey.
rescale_storey
Search the accessible surface-material catalog before creating a finish with material_asset(...). Returns stable logical IDs, compatible surface kinds, tintability, base colour, physical texture repeat, style tags, and optional preview URL. A user may also choose color_hex alone without calling this tool.
search_material_assets
Send the user's feedback to the HZplan developers. Use when a tool failed, behaved unexpectedly, was confusing, or the user has a complaint, suggestion, or praise. Quota-free. CONSENT — REQUIRED: first draft the message, show the user the EXACT text you will send, and get their explicit agreement. Never send feedback the user hasn't seen and approved, and never include anything they didn't agree to share.
send_feedback
Attach free-form key→value metadata (e.g. material=brick, condition=poor, note="window is broken") to ANY entity (wall, opening, object, storey, room, house). Passive: stored and shown read-only in the UI, never affects geometry, the solver, or rendering. Merge semantics — `set` adds/overwrites the named keys, `unset` removes them, all other attributes are preserved. Prefer this over upsert_entity for metadata: upsert_entity replaces the whole assertion list. Values are strings (≤2048 chars); keys ≤64 chars; ≤32 keys per entity. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
set_entity_attributes
Attach, replace, or clear a generated 3D model (asset_id) on an existing object while preserving its other assertions. Use after generate_object_3d if get_object_3d_status reports attached:false, to re-attach a previously generated asset, or pass asset_id:null to remove the model (this only detaches it; delete_object_3d_asset deletes it for good). Prefer this over upsert_entity, which replaces the whole assertion list and silently drops attachments. storey_id: the floor this object is on — required only when the same object id exists on more than one floor.
set_object_3d_asset
Update only the directional `feature_side` on an existing object (top/bottom/left/right, or null to clear) while preserving every other assertion — kind, size_mm, offsets, illustration_id, model_3d, etc. ALWAYS prefer this over upsert_entity when changing only the facing direction; upsert_entity replaces the whole assertion list and will silently drop attachments like model_3d. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
set_object_feature_side
Update only the persisted `height_mm` on an existing object (positive millimetres, or legacy null which now materializes the global 1000 mm default) while preserving every other assertion — kind, size_mm, offsets, physical attachments, illustration_id, model_3d, feature_side, etc. Prefer this over upsert_entity when changing only height. Generated 3D assets are scaled to this authored dimension and never decide it. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
set_object_height
Attach, replace, or clear an illustration on an existing object while preserving the object's other assertions automatically. Prefer this over upsert_entity when you only want to change illustration_id. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
set_object_illustration
Step 1 of 2 in the image → hzplan pipeline. Detects how many distinct floor plans are present on a single uploaded image (residential plans frequently show ground + first + sometimes basement on one page) and returns one bounding box + preview crop per detected floor. Quota-free. WHEN TO CALL Call this ONCE per image the user wants to convert into an hzplan. If it returns ok:true, USE the floors[] result — show previews to the user, ask which to import, then call convert_floor_to_plan for each chosen floor. DO NOT retry a successful call. DO NOT issue duplicate calls back-to-back. WHAT TO PASS image: the user's uploaded (or in-chat generated) image as a FILE REFERENCE — ChatGPT fills download_url + file_id automatically. Never base64-encode the image yourself; do not paste image bytes into any argument. FALLBACK (use this whenever `image` doesn't populate — e.g. a generated image, a mobile upload, or 'file too large'): call request_image_upload, have the user open the link and drop/save the image there, then pass image_ref:{ upload_id }. This is the reliable path when attaching the file directly fails. Non-ChatGPT clients only: `raw_url` accepts a public http(s) URL or a data:...;base64,... URI. Last resort: point the user to the web app: https://hzplan.uk/plans/new/from-image WHAT YOU GET BACK ON SUCCESS { ok: true, floors: [ { floor_id, suggested_name, bbox_px, source } ] } PLUS one preview image per floor attached as image content (in the same order as floors[]). - floor_id: opaque string. PASS THIS to convert_floor_to_plan to convert that specific floor. - suggested_name: 'ground' / 'first' / 'second' / 'third' / 'fourth' or 'floor N' for >5 floors. Use as the default storey_name unless the user overrides. - bbox_px: [x1, y1, x2, y2] location of the floor in the source image's pixel space. - the attached preview images let the user confirm which floor is which; floors[][i] corresponds to attached image i. NEXT STEPS AFTER A SUCCESSFUL CALL 1. Show the user the attached floor preview images (one per entry in floors[], same order). 2. Ask which floors to import and confirm storey names. 3. For each chosen floor, call convert_floor_to_plan with: same image, the floor_id, the storey_name, and plan_id (omit on the first conversion to mint a new plan; pass on subsequent ones to keep adding storeys to the same plan). ERROR CODES service_misconfigured: pipeline isn't deployed. Don't retry; tell the user. not_a_floor_plan: the floor-plan check refused the page — it read as a photo, elevation, section, map, diagram or historical survey rather than a modern floor plan. Tell the user what came back and why. If they look at it and confirm it really is a floor plan they want converted, call again with skip_plan_check: true — only after the user says so explicitly, never on your own and never as an automatic retry. splitter_no_floors: image probably isn't a residential floor plan. Ask the user for a clearer image. image_url_fetch_failed: the user's image URL expired (signed URLs are short-lived). Ask the user to re-upload. unsupported_image_format: image isn't PNG/JPEG/WEBP. service_http_error / service_timeout: transient pipeline error — safe to retry ONCE.
split_floor_plan
Call this first when authoring or editing plans if you need the supported predicates, recommended sequencing, or the MCP usage rule that resources are widget shells only while tools handle all reads and writes.
get_authoring_guide
Create or update one authored entity in the current plan head. Supported entity types are storey, wall, opening, object, height_level, and surface_finish. Assertions are entity-local: do not repeat entity_id in args. Each assertion is an object {"predicate": <name>, "args": [...]}, e.g. assertions: [{"predicate":"on_storey","args":["s1"]}, {"predicate":"kind","args":["outer"]}, {"predicate":"offset","args":["left","s1.left",0]}]. The canonical-tuple form that get_storey_plan shows (["on_storey","s1"], ["offset","left","s1.left",0]) is also accepted, and a leading subject that repeats THIS entity (e.g. ["offset","w1.left","s1.left",0] or ["kind","w1","outer"] while authoring w1) is reduced to entity-local form automatically — so you can replay what you read. Use tools for all data reads and writes; MCP resources are widget HTML only and are never required for CRUD. When changing only an existing object's illustration link, prefer set_object_illustration instead of rebuilding the full assertion list manually. To give an object a 3D model from a reference image, call generate_object_3d only after it has solved width/depth, persisted height_mm, and a physical attached_to fact; then poll get_object_3d_status and optionally call set_object_3d_asset. Storey workflow: (optional) after create_storey, upsert the storey with size_mm(width_mm, depth_mm) to pin a fixed rectangle; omit it and the storey extent derives from the walls, which leaves only s1.left and s1.top anchorable — anchor everything else to walls you have already placed. Only walls and openings create or divide rooms. Use name_space_at_point(...) to name enclosed floor areas such as bathroom, bedroom, hall, kitchen, or garage after walls and openings define them. Do not create those spaces as objects. Objects are furniture, fixtures, and equipment such as cupboards, sofas, beds, tables, toilets, sinks, and tubs; they may align to walls or surrounding spaces but they do not replace partition walls. Some furniture is directional: feature_side(top|bottom|left|right) is the direction the object FACES — a sofa, armchair, or toilet whose feature_side is left faces (opens toward) the left. To face a window on the left wall, set feature_side=left. Exceptions: a bed's feature_side is its headboard side, and a sink/bath's is its plumbing (wall/tap) side. When updating an existing entity, fetch it first and resend every assertion you want to keep. To change or drop a single field on an existing entity, prefer set_entity_assertions / remove_entity_assertions (merge by key) over resending the entire assertion set. Storey predicates: ceiling_height_mm(number_mm), eye_height_mm(number_mm), size_mm(width_mm, depth_mm). Wall predicates: on_storey(storey_id), kind(value), axis(horizontal|vertical), angle_deg(number), thickness_mm(number_mm), length_mm(number_mm), height_mm(number_mm), attached_to(child_face, target_id, target_face), offset(anchor, reference_anchor, delta_mm). Offsets constrain solved rectangle edges, not wall centerlines, so think in left, right, top, and bottom edges. When you want a gap between two entities, offset the closest pair of edges that represents that gap. Outer walls should usually be positioned relative to storey anchors: offset(left, s1.left, 0) works on every storey, while offset(bottom, s1.bottom, 0) needs an authored size_mm — s1.right, s1.bottom, s1.center_x and s1.center_y are derived edges that a size-less storey does not expose, so anchor to a wall there instead. For interior wall corners, place the first wall from storey anchors, then place the closing wall by sharing a corner with the existing wall instead of pinning both walls absolutely again. Prefer left, right, top, and bottom for wall placement; use start or end only when you explicitly need along-wall anchors, and center for a wall's or opening's along-axis midpoint. A wall keeps its fixed length_mm and will NOT stretch when the storey is resized; to make an outer wall grow or shrink with the storey — which presumes an authored size_mm, since a size-less storey has no fixed edges to follow — anchor BOTH ends to the moving storey edges (for example offset(left, s1.left, 0)+offset(right, s1.right, 0) on a horizontal wall) and omit length_mm. A wall is either axis-aligned (use axis(horizontal|vertical)) or angled (use angle_deg(number) where 0 is east and 90 is north). Never combine axis and angle_deg on one wall. length_mm is always measured along the wall's own direction, and offsets still anchor the wall's axis-aligned bounding box. Opening predicates: on_storey(storey_id), kind(value), length_mm(number_mm), height_mm(number_mm), sill_height_mm(number_mm), head_height_mm(number_mm), host(target_id), hinge(start|end), opens_to(left|right), elevation_mm(number_mm), attached_to(child_face, target_id, target_face), offset(anchor, reference_anchor, delta_mm). An opening's anchors run along its host wall: start, end and center. On a horizontal wall left/right/center_x are stored as start/end/center, on a vertical wall top/bottom/center_y; the result's normalized_anchors lists any such rewrite. Use arch as a friendly alias for archway. Examples (every s1.right/s1.bottom below presumes an authored size_mm on the storey; on a size-less one substitute a wall anchor): a west outer wall uses kind(outer)+axis(vertical)+length_mm(...)+offset(left, s1.left, 0)+offset(top, s1.top, 0); a south outer wall uses kind(outer)+axis(horizontal)+length_mm(...)+offset(left, s1.left, 0)+offset(bottom, s1.bottom, 0); a horizontal partition that closes against an existing vertical wall can use kind(inner)+axis(horizontal)+length_mm(...)+offset(right, w_existing.left, 0)+offset(top, w_existing.top, 0); a 45° diagonal wall of 1 m bounding box uses kind(outer)+angle_deg(45)+length_mm(1414)+thickness_mm(200)+offset(left, w_west.left, 0)+offset(top, w_north.bottom, 0); a single hinged door uses kind(door)+hinge+opens_to; a garden door uses kind(garden_door)+opens_to; a built-in wardrobe / cupboard door uses kind(cupboard_door)+opens_to (no hinge); a garage door uses kind(garage_door) (panel style) or kind(roller_door) (corrugated roll-up) and must not include hinge or opens_to; an archway uses kind(archway) and must not include hinge or opens_to. Object predicates: on_storey(storey_id), kind(value), category(value), movable(boolean), height_mm(number_mm), size_mm(width_mm, depth_mm), connects(target_id), feature_side(top|bottom|left|right), elevation_mm(number_mm), model_3d(value), illustration_id(illustration_id), attached_to(child_face, target_id, target_face), offset(anchor, reference_anchor, delta_mm). Height-level predicates: on_storey(storey_id), kind(value), elevation_mm(number_mm). A storey already owns floor and ceiling levels; create an auxiliary level only to coordinate several wall/opening bounds. Objects attach to physical storey/object/wall hosts, never directly to levels. Surface-finish predicates: on_storey(storey_id), kind(value), thickness_mm(number_mm), pin_mm(x_mm, y_mm), material_asset(material_id), color_hex(#RRGGBB), attached_to(child_face, target_id, target_face), offset(anchor, reference_anchor, delta_mm). A finish requires color_hex or material_asset; colour alone is valid. Search material IDs with search_material_assets. A colour may accompany only a tintable material and then modulates its base colour while physical maps remain unchanged. Wall finishes attach their inward face to one axial wall face and span with offsets; floor/ceiling finishes own pin_mm and attach to storey.floor/storey.ceiling. Vertical placement uses attached_to(child_face,target_id,target_face). Floor objects use attached_to(base,storey_id,floor), stacks use attached_to(base,support_object_id,cap), and ceiling objects use attached_to(cap,storey_id,ceiling). Never attach an object to a height level (e.g. s1_floor): levels bound only wall and opening base/cap. Every object has persisted height_mm; omission materializes the global 1000 mm default. A pendant's height includes its complete canopy/cable/body assembly. Objects must have enough offsets to solve both horizontal and vertical placement: give size_mm(width_mm, depth_mm) plus exactly one horizontal and one vertical anchor — for example offset(center_x, s1.center_x, 0)+offset(center_y, s1.center_y, 0) to center it (s1.center_x/center_y anchors require an authored size_mm; on derived storeys anchor to walls), or offset(left, w1.right, 100)+offset(top, w2.bottom, 100) to seat it off two walls. Do NOT anchor both faces of one axis (left and right, or center_x and left together): size_mm already fixes the width, so a second same-axis anchor is redundant and overconstrains the solve (it then reports infeasible). Objects may share a 2D footprint when vertical attachments place them in a valid stack or at separated heights; actual 3D solid intersections remain invalid. To create or change several entities, send them as items of one upsert_entities call rather than repeated upsert_entity calls. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
upsert_entity
Apply many entity creations/updates to one plan in a single call. Intended for blast-then-repair authoring when importing from a floor plan image: emit the whole plan (storey size + all walls + openings + objects) as one batch, read the per-item and global solver report, then resubmit a follow-up batch that only contains the corrections. Items are applied in order against the same in-memory document; if any item fails to apply OR the solver reports infeasible/ambiguous/overlapping storeys, nothing is persisted and the full per-item + per-storey report is returned so you can fix and retry. Prefer create_storey first (so s1 exists) and pass entity_id on items you want to update instead of create. storey_id: the floor this entity is on — required only when the same id exists on more than one floor, or when creating on a plan with several floors.
upsert_entities
HZplan ChatGPT Plugin FAQ
How the directory, categories and Discoverability Score work.
Read the methodologyHow do I improve HZplan's ChatGPT Plugin discoverability?
The levers are the listing surface agents actually read: names, descriptions, keywords, tool metadata, and registry health. Which lever matters depends on where discovery breaks, which is what continuous measurement shows.
What are HZplan alternatives on ChatGPT?
As of 2026-11-02, HZplan competes with Altifigence, ArchLang, Aspicio, BimWired Elektroplanung Revit, Bits & Bolts Remote, convert3d, CWS Architecture, EasyGerber and 10 more in ChatGPT CAD & 3D Design Tool Control, ranked by public Discoverability Score.
Where is this profile measured?
This profile uses the geography attached to the latest public registry snapshot: US. Locale tags are intentionally omitted.