Experimental "ARCticulation Tool" for Preset Poses (Component Posing / Transition Animations / ChatGPT and Claude)

I wanted to get this into a semi-stable state before sharing it, so here it is: ARCticulation Tool, a SketchUp posing tool I built through a lot of experimenting, testing, and back-and-forth problem solving with ChatGPT.

See latest posts from me for version updates

I primarily model mecha/robots in SketchUp. A big part of my workflow is building models, posing them, taking screenshots, and sharing the results. Posing with the native Rotate and Move tools works, but it can get tedious when you are constantly adjusting arms, legs, heads, armor panels, cockpit parts, weapons, and all the little mechanical bits that need to line up just right. So I made this.

What ARCticulation Tool does

ARCticulation Tool lets you pose component-based models by saving and restoring pose snapshots. It is meant for mechanical models, mecha, robots, armor, limbs, joints, hinges, sliding parts, and pretty much anything else built from SketchUp components.

Basic idea

  1. Build your model using components.

  2. Set component origins where you want parts to rotate or move from (A convenient Origin management tool is included to set the center of an edge or face as an origin point via right click).

  3. Select the parts you want to pose.

  4. Use the tool to rotate, hinge, or arrange them.

  5. Save that arrangement as a pose preset.

  6. Later, click Snap Pose to restore that pose.

A pose is basically a saved snapshot of where the selected parts were. If you save a head looking left, an arm flexed, a leg bent, or a cockpit hatch slid open, the tool can snap those parts back to that saved position.

Main parts of the tool

Smart Rotate
A rotation tool for posing component parts. It has different modes depending on how you want parts to move together.

  • Default - simply rotate your selection around an origin point with an unobstructed view; no fancy gimmicks.

  • Follow - Not only rotate around an origin point, but set other components to rotate around additional origin points in the same selection flow!

  • Self - Designate one of the selected components as the origin point in a ā€œself chainā€ of rotating components.

Axis Limits
Lets you set which axes a part is allowed to rotate on, and how many degrees of rotation it’s permitted to rotate. Useful if you want a joint to behave more like a hinge instead of rotating freely in every direction.


Pose Presets
Lets you save poses and snap back to them later. This is the main feature I use for quickly switching between different model poses.


A few notes

This is still experimental, so I would not call it bulletproof. It works best with clean component structure and carefully placed component origins. If your model is deeply nested or mirrored, you may find strange edge cases.

If you test it, I would love to know:

  • What worked?

  • What broke?

  • What was confusing?

  • What felt useful?

  • What should be renamed, removed, or improved?

Thanks for checking it out.

looks one line above…

Capture d’écran 2026-06-23 aĢ€ 07.57.27

jeez, I’d hate to see you make a non basic workflow :sweat_smile:
(I’ll try it, but it’ll have to wait for the weekend. or the cold, whichever comes first)

ROFL, that wasn’t supposed to be 46, that was supposed to be 10.

For some reason the form doesn’t let that be its own numbered list, I’ll change it to bullet points.

An updated version of the tool. Main post updated.

  1. I abandoned pursuing composite presets for now… it was doing weird things.

  2. I removed transition animations. It now snaps in place between presets. Again, doing weird things. This means timing has been yoinked, FPS presets have been yoinked, etc.

  3. Undo states have been reworked, should work much better now.

  4. Nested components shouldn’t randomly jump coordinates in the root model space.

The tool has been upgraded!

  1. Smart Rotate is now Smart Motion! A sliding option has been added, allowing a part to slide along a length for an infinite or set distance. This has been integrated into the previous Smart Rotate tool, hence the new name. Trigger it through additional selections in the Smart Motion multi-tool, or if you set it in the new Joint Limits, it automatically detects the motion type!

  1. ā€œAttach Lockā€ added for Self-Chain action. If you are moving multiple parts in a chain, but want to create a new chained segment, you can now perform an ā€œAttach Lockā€ via Shift + Left Click on a selected rotation or slide point to spin off a new segment. This prevents the rotation math from one segment from interfering with another. Paired with the new gating system, you can make intentional delays for moving components! *Experimental

  1. Axis Limits is now Joint Limits! With the addition of the sliding function, the tool has been renamed. The Axis Limits UI has also had an overhaul to try and reduce clutter. Rotate and Slide can now be toggled per-component, with their own unique joint limit actions.

    • This includes an experimental feature called Gating. Gates allow you to designate at what length or what degree of rotation a chained component starts its rotation or slide. Experimental
    • It serves no function for snap playback poses, and is an advanced feature added for later attempts to re-introduce animation transitions. *Experimental
    • Gates allow certain parts to move before others fire off, to reduce part clipping and create more complex animated transitions, such as multiple parts of a cockpit hatch all opening at once, in sequence. *Experimental

Pose transitions are making a comeback! Soon.

Also UI overhaul. I went light mode this round.

Doing basic mechanical transitions for now, going to see if I can do more ā€œorganicā€ transitions for the human-like movements.

Implemented a chain system so each pose knows what pose comes before it, so it knows to execute that in the chain. This comes with the unfortunate ā€œbrief pauseā€ between transitions. I’m hoping I’ll be able to get it to code an organic approach that properly fires off everything in the chain in more human-like motions, we’ll see.

Already a lot better than the first attempt!

That’s a completely different story.
For that kind of stuff you should set-up a skinning system using skeletons which is (orders of magnitude) more complex than creating simple objects tranforms.

The basic idea is: you apply the transform (move/rotate/scale) to a bone and the bone drags the skinned mesh vertices. Each bone has an area of (gradual) influence with different ā€œweightsā€ (kinda what VertexTools soft selection does, but animated and spreading from multiple bone objects at the same time).

If you are talking about the jerky movements that you wish were smoother, that’s because apparently you are just lerping (linear interpolating) between target states. In that case it’s easier to fix: you just want to apply a curve instead of a linear function and then you can modify the tangents in order to control the ease-in/ease-out of movements.

Did you ever use some proper animation package such as Max, Maya or Blender?
If not, I recommend you have a look to how this kind of stuff works, before you try to create a similar system from scratch. Otherwise is unlikely that you end up with something useful.
That said, deforming vertices in Sketchup is not easy at all and you can quickly hit the limits of what Sketchup is able to do both in therms of API support and performances with hi-poly meshes.

That makes sense, but I should clarify what I meant by organic here.

My system isn’t a skinning/skeleton system. It’s more of a component-based kinematic posing system for hard surface models. Each part remains a rigid component, and the tool stores and plays transforms around existing origins, including chained/linked motion between parts.

So I’m not doing anything like forming vertices or building character rigging inside Sketchup. When I said organic, I meant making rigid mechanical motion feel more natural through the timing, offsets and chained movement, as well as smoother interpolation and easing rather than simple linear A to B to C motion.

Basically, not like blender or Maya skinning; more of a pose-driven mechanical articulation with better motion curves.

Yep, that’s what I meant with

So if you want to achieve something like this

You will need something like this were you can manipulate the tangents of the curves (manually or with presets) so that you can make object accelerating/decelerating and not always linearly interpolating.

Ahh, I gotcha. I’ll see what I can do there. I’ll get the basic system down first and look at something like this as refinement.

We got my playback recognizing gates and locks and introduced a functional reverse-playback system (toggleable, so it doesn’t interfere with simpler movements that don’t really require it).

Can finally have my cockpit open and close without clipping! (Well, might be SUPER minor clipping you can barely see, but that’s on me for timing/modeling the piece lol).

Kind of got smooth playback going… I didn’t do a curve editor, but I did have it create some presets and gave it an overlap slider.

There’s some ā€œbumpsā€ in the motion (I see you jumping there, upper arm into shoulder) that I got to work out, but I’m gettin’ there!

Still some quirks here and there (It’s not recognizing the gating on the cockpit after the rotation until I replay the open/close), but there’s now redundancies in place to make sure everything can fire off correctly and if it doesn’t, it’s easy to correct.

I can now fully rotate and move my models in the model space and it still knows to fire off the presets correctly. I can even export the pose presets and import them into a new model; I import the model first, then I import the pose presets, and it sets everything accordingly!

Slowly but surely hardening everything here and there. I’m also thinking of including a video exporter using sketchup’s built-in tools… will be interesting to see if that works with my tool.

But everything from posing to pose presets to joint limits is pretty solid now, 354 versions later from where I started (The basic articulation tool included in my small toolkit lol).

No more weird bump in the shoulder when moving poses. Can switch between organic and mechanical overlays for parts, slides working without issue…

… lookin’ good!

Much better and smoother.
The model is really nice as well.

Included below is the latest signed ā€œTestā€ build.

With regards to compatibility, this should work as far back as Sketchup 2021 (Mostly functional with a few minor bugs that will likely be unaddressed). It also takes into account compatibility with macOS, however, as I do not own a mac, this compatibility is unverified (This version 0.8.4.4 was specifically for adding macOS compatibility).

Everything should mostly work! I haven’t included frame/video exporting in this version.

ARCticulation_tool_v0_8_4_4_TEST_signed.rbz (337.1 KB)

Your extension is looking pretty cool. And your model suggests people are going to have to up their robot modeling game… big time!

I thought I’d send a scan of your extension from another extension I’m working on that tries to prepare them for EW review. The focus is in finding security issues, common reasons for rejection, and some basic code quality review. The results may point to some things that are low priority/false positives. But there are a few items that you might want to address. I hope you find it useful.

Extension Preflight Report

# Extension Preflight Report

Target: C:/Users/

Target type: folder

Signature metadata: detected (arcticulation_tool/arcticulation_tool.susig)

Package root: C:/Users/

Selection type: extension_folder

Detected extension name: ARCticulation Tool

Root loader: arcticulation_tool.rb

Extension folder: arcticulation_tool

Extension version: 0.8.4.4

Extension creator: Arcwalde

Extension description: Mechanical articulation tools for SketchUp: Smart Motion, Joint Limits, Pose Presets, Export Hub, and Origin Manager.

Extension copyright: Copyright (c) 2026 Arcwalde

Generated: 2026-07-04T17:22:12Z

Readiness: Needs attention

Scope note: Static preflight results are review candidates, not Extension Warehouse approval.

## Summary

- Files seen: 72

- Files scanned: 58

- Files skipped: 14

- Findings total: 19

## Findings

- [P2/medium] Interpolated execute_script call detected

Location: arcticulation_tool/tools/core/diagnostics_panel.rb:91

Rule: HTML002 (HtmlDialog Safety)

Evidence: dialog.execute_script(ā€œwindow.#{js_function} && window.#{js_function}(#{json_for_script(line.to_s)});ā€)

Why it matters: Interpolating Ruby values into JavaScript can create injection bugs.

Conservative fix: Serialize dynamic values with JSON.generate or to_json and keep receiver names fixed.

- [P2/medium] Destructive file operation needs review

Location: arcticulation_tool/tools/export_hub.rb:968

Rule: DATA004 (Data Loss)

Evidence: File.delete(tmp) if tmp && File.exist?(tmp)

Why it matters: Deleting, truncating, or recursively removing files can destroy user data when paths are broad, user-controlled, or not constrained.

Conservative fix: Constrain destructive file operations to extension-owned temp/cache paths, confirm user intent, and canonicalize paths before deletion.

- [P2/medium] Model#delete_attribute may rely on a monkey-patched API

Location: arcticulation_tool/tools/pose_presets/storage.rb:125

Rule: COMPAT002 (SketchUp API Compatibility)

Evidence: model.delete_attribute(DICTIONARY, ROUTES_KEY) rescue nil

Why it matters: Sketchup::Model#delete_attribute is not part of the official SketchUp API in RuboCop-SketchUp guidance and may only exist when another extension monkey-patches it.

Conservative fix: Avoid relying on Model#delete_attribute, or guard it and provide a fallback such as writing nil or rebuilding extension-owned model attributes safely.

- [P2/medium] Model#delete_attribute may rely on a monkey-patched API

Location: arcticulation_tool/tools/pose_presets/storage.rb:266

Rule: COMPAT002 (SketchUp API Compatibility)

Evidence: model.delete_attribute(DICTIONARY, CURRENT_POSE_KEY) rescue nil

Why it matters: Sketchup::Model#delete_attribute is not part of the official SketchUp API in RuboCop-SketchUp guidance and may only exist when another extension monkey-patches it.

Conservative fix: Avoid relying on Model#delete_attribute, or guard it and provide a fallback such as writing nil or rebuilding extension-owned model attributes safely.

- [P3/medium] Path construction uses _dir_ or _FILE_ directly

Location: arcticulation_tool/main.rb:8

Rule: LOAD007 (Loader)

Evidence: ROOT_DIR = File.expand_path(_dir_) unless const_defined?(:ROOT_DIR, false)

Why it matters: SketchUp Ruby has had path-encoding edge cases around _FILE_ and _dir_; RuboCop-SketchUp recommends duplicating and forcing encoding before path operations.

Conservative fix: Use a small path helper that duplicates _dir_ or _FILE_ and forces UTF-8 before File.expand_path, File.dirname, or File.join path construction.

- [P3/low] Helper model mutation should be verified inside an operation

Location: arcticulation_tool/tools/core/joint_limits/config.rb:28

Rule: UNDO001 (Operations)

Evidence: attribute mutation: source.set_attribute(DICTIONARY, keyer.call(ā€˜allowed’), allowed ? true : false)

Why it matters: This file mutates SketchUp model entities without a local operation wrapper, but another scanned file has visible start, commit, and abort operation handling. Static review cannot prove the call path.

Conservative fix: Verify user-facing commands reach this helper inside one reliable SketchUp operation. Keep operation ownership at the command/workflow layer rather than adding nested operations in low-level geometry helpers.

- [P3/medium] Model root entities access needs review

Location: arcticulation_tool/tools/export_hub.rb:442

Rule: SEL003 (Selection And Entity Assumptions)

Evidence: scan_portable_entities(model.entities, [], [], root_world, entries, 0, definition_cache) if model && model.respond_to?(:entities)

Why it matters: Using model.entities targets the model root, not the active editing context. Commands usually should respect the user context with model.active_entities.

Conservative fix: Prefer model.active_entities unless the command intentionally works at the model root and documents that behavior.

- [P3/medium] Model root entities access needs review

Location: arcticulation_tool/tools/pose_presets/context_resolution.rb:97

Rule: SEL003 (Selection And Entity Assumptions)

Evidence: scan_entities_for_pid(model.entities, target_pid, identity_transform, [], matches, 0, []) if model && model.respond_to?(:entities)

Why it matters: Using model.entities targets the model root, not the active editing context. Commands usually should respect the user context with model.active_entities.

Conservative fix: Prefer model.active_entities unless the command intentionally works at the model root and documents that behavior.

- [P3/medium] Model root entities access needs review

Location: arcticulation_tool/tools/pose_presets/context_resolution.rb:110

Rule: SEL003 (Selection And Entity Assumptions)

Evidence: scan_entities_for_context_index(model.entities, identity_transform, [], index, 0, [])

Why it matters: Using model.entities targets the model root, not the active editing context. Commands usually should respect the user context with model.active_entities.

Conservative fix: Prefer model.active_entities unless the command intentionally works at the model root and documents that behavior.

- [P3/low] Helper model mutation should be verified inside an operation

Location: arcticulation_tool/tools/pose_presets/placement_frame.rb:704

Rule: UNDO001 (Operations)

Evidence: attribute mutation: model.set_attribute(DICTIONARY, PLACEMENT_STATE_KEY, JSON.generate(state.is_a?(Hash) ? state : {}))

Why it matters: This file mutates SketchUp model entities without a local operation wrapper, but another scanned file has visible start, commit, and abort operation handling. Static review cannot prove the call path.

Conservative fix: Verify user-facing commands reach this helper inside one reliable SketchUp operation. Keep operation ownership at the command/workflow layer rather than adding nested operations in low-level geometry helpers.

- [P3/low] Drawing tool should invalidate the view when deactivated or suspended

Location: arcticulation_tool/tools/smart_motion_tool.rb

Rule: TOOL001 (Tools And View Drawing)

Evidence: Missing or incomplete: deactivate(view), suspend(view)

Why it matters: Tools that draw temporary viewport graphics should clear stale drawing when SketchUp deactivates or suspends the tool.

Conservative fix: Implement deactivate(view) and suspend(view), and call view.invalidate from those lifecycle methods.

- [P3/medium] Tool accepts VCB input without enableVCB?

Location: arcticulation_tool/tools/smart_motion_tool.rb

Rule: TOOL003 (Tools And View Drawing)

Why it matters: Tools that implement onUserText should also opt in to the value control box so typed input is available consistently.

Conservative fix: Define enableVCB? and return true for tools that accept onUserText input.

- [Info/high] Long undo operation names detected

Location: project

Rule: UNDO006 (Operations)

Evidence: 9 occurrences; examples: arcticulation_tool/tools/core/joint_limits/dialog.rb:517 model.start_operation(ā€˜ARCticulation Clear Instance Joint Settings’, true); arcticulation_tool/tools/core/joint_limits/dialog.rb:539 model.start_operation(ā€˜ARCticulation Clear Default Joint Settings’, true); arcticulat…

Why it matters: Operation names appear in SketchUp undo UI and should be concise enough for users to scan.

Review note: Use short, user-facing operation names while keeping detailed context in code comments or logs.

- [Info/high] SketchUp extension signature metadata detected

Location: arcticulation_tool/arcticulation_tool.susig

Rule: SIG001 (Archive Packaging)

Evidence: .susig file present

Why it matters: A .susig file is signature metadata for a SketchUp extension package. This is useful package context, but Preflight does not validate the signature.

Review note: For code review coverage, scan the development source package before signing or encrypting.

- [Info/low] Serialized execute_script payloads need review

Location: arcticulation_tool/tools/core/diagnostics_panel.rb

Rule: HTML002 (HtmlDialog Safety)

Evidence: 6 occurrences; examples: arcticulation_tool/tools/core/diagnostics_panel.rb:128 dialog.execute_script(ā€œarcDiagPanelEchoReply(#{json_for_script({ kind: ā€˜echo_reply’, received: data, ruby_time: Time.now.strftime(ā€˜%H:%M:%S’) })});ā€); arcticulation_tool/tools/core/diagnostics_panel.rb:135 dialog.execute…

Why it matters: This file has execute_script calls with values that appear to be JSON-serialized into fixed JavaScript calls. This is lower risk than raw interpolation, but static review cannot prove every helper escapes safely.

Review note: Keep JavaScript receiver names fixed and extension-owned, and route every dynamic value through JSON.generate, to_json, or a reviewed JSON helper.

- [Info/low] Serialized execute_script payloads need review

Location: arcticulation_tool/tools/core/joint_limits/dialog.rb

Rule: HTML002 (HtmlDialog Safety)

Evidence: 1 occurrence; examples: arcticulation_tool/tools/core/joint_limits/dialog.rb:562 @dialog.execute_script(ā€œarcSetRows(#{json(payload)});ā€)

Why it matters: This file has execute_script calls with values that appear to be JSON-serialized into fixed JavaScript calls. This is lower risk than raw interpolation, but static review cannot prove every helper escapes safely.

Review note: Keep JavaScript receiver names fixed and extension-owned, and route every dynamic value through JSON.generate, to_json, or a reviewed JSON helper.

- [Info/low] Serialized execute_script payloads need review

Location: arcticulation_tool/tools/export_hub.rb

Rule: HTML002 (HtmlDialog Safety)

Evidence: 3 occurrences; examples: arcticulation_tool/tools/export_hub.rb:312 dialog.execute_script(ā€œarcReceiveLibrary(#{JSON.generate(library_payload(model))});ā€); arcticulation_tool/tools/export_hub.rb:318 dialog.execute_script(ā€œarcStatus(#{JSON.generate(text.to_s)});ā€); arcticulation_tool/tools/export_hub…

Why it matters: This file has execute_script calls with values that appear to be JSON-serialized into fixed JavaScript calls. This is lower risk than raw interpolation, but static review cannot prove every helper escapes safely.

Review note: Keep JavaScript receiver names fixed and extension-owned, and route every dynamic value through JSON.generate, to_json, or a reviewed JSON helper.

- [Info/low] Serialized execute_script payloads need review

Location: arcticulation_tool/tools/pose_presets/pose_crud.rb

Rule: HTML002 (HtmlDialog Safety)

Evidence: 1 occurrence; examples: arcticulation_tool/tools/pose_presets/pose_crud.rb:39 @dialog.execute_script(ā€œwindow.arcPoseReceiveState && window.arcPoseReceiveState(#{js_json(payload)});ā€)

Why it matters: This file has execute_script calls with values that appear to be JSON-serialized into fixed JavaScript calls. This is lower risk than raw interpolation, but static review cannot prove every helper escapes safely.

Review note: Keep JavaScript receiver names fixed and extension-owned, and route every dynamic value through JSON.generate, to_json, or a reviewed JSON helper.

- [Info/low] Serialized execute_script payloads need review

Location: arcticulation_tool/tools/pose_presets/quick_save.rb

Rule: HTML002 (HtmlDialog Safety)

Evidence: 1 occurrence; examples: arcticulation_tool/tools/pose_presets/quick_save.rb:38 dialog.execute_script(ā€œwindow.arcQuickPoseStatus && window.arcQuickPoseStatus(#{js_json(status)});ā€) rescue nil

Why it matters: This file has execute_script calls with values that appear to be JSON-serialized into fixed JavaScript calls. This is lower risk than raw interpolation, but static review cannot prove every helper escapes safely.

Review note: Keep JavaScript receiver names fixed and extension-owned, and route every dynamic value through JSON.generate, to_json, or a reviewed JSON helper.

## Checks Run

- Package structure and root loader discovery

- Selected extension folder paired with sibling root loader

- Root loader metadata and SketchupExtension registration

- Built-in static text, path, security, platform, and release hygiene checks

- Platform compatibility checks scoped to Windows + Mac

- Namespace wrapper and top-level Ruby hygiene

- Required Ruby stdlib require coverage

- Mac toolbar icon compatibility

## Not Reviewed / Could Not Verify

- The reviewed extension code was not loaded or executed.

- Live SketchUp behavior was not run.

- HtmlDialog developer console behavior was not inspected.

- Mac behavior was not tested.

- Final RBZ install, enable/disable, command launch, and model workflow testing were not proven.

- Documentation, support contact, dependency instructions, and listing/support links were not verified.

- Usefulness, uniqueness, duplicate-feature fit, and listing accuracy are manual submission self-checks and are not scored.

- Archive packaging was not scanned because an unpacked folder was selected.

- SketchUp signature metadata (.susig) was detected; this scanner does not validate the signature.

- External Ruby, Node, RuboCop, and RuboCop-SketchUp checks are not run in this first scaffold.

## Skipped Files

- arcticulation_tool/arcticulation_tool.susig: SketchUp signature metadata file skipped

- arcticulation_tool/icons/articulation_rotate_icon.png: unsupported file type

- arcticulation_tool/icons/axis_limits_icon.png: unsupported file type

- arcticulation_tool/icons/cursor_pick.pdf: unsupported file type

- arcticulation_tool/icons/cursor_pick.png: unsupported file type

- arcticulation_tool/icons/cursor_pick.svg: unsupported file type

- arcticulation_tool/icons/cursor_rotate.pdf: unsupported file type

- arcticulation_tool/icons/cursor_rotate.png: unsupported file type

- arcticulation_tool/icons/cursor_rotate.svg: unsupported file type

- arcticulation_tool/icons/cursor_slide.pdf: unsupported file type

- arcticulation_tool/icons/cursor_slide.png: unsupported file type

- arcticulation_tool/icons/cursor_slide.svg: unsupported file type

- arcticulation_tool/icons/export_hub_icon.png: unsupported file type

- arcticulation_tool/icons/pose_presets_lab_icon.png: unsupported file type

Thanks! Once I’m done working on the frames/video exporter, I’ll certainly feed this review through and give it a thorough check!

A lot of them were false positives, but I was able to address four concerns it brought up. The rest it mostly refused to budge on because it was intentionally coded that way, or was deemed optional/low priority (Though it did bring in to mind I should probably harden this for localization in different languages once everything’s said and done).

Thanks for that feedback. The monkey-patching was the main one that seemed real. Would you use an extension that provides this kind of feedback if it were made available? Did it feel like a wild goose chase? The reason I’m asking is that I could silence some of the findings, but the scan would miss potential problems that it seems reasonable to review (even though you now have determined that some of the findings were ā€˜false alarms’).

Always good to keep Localization, Localisation, Localización, Lokalisierung, in mind!

Yes, I could see the merit in it.

In other news… inverse kinematics, maybe?

A work in progress. Not doing things like weight, collision, etc. Just wanted to see if I could do IK since I use FK. Super basic rig for now, just testing it through ruby console.