Read this model as text
A round grid that lies on the soil of a plant pot so a cat cannot dig in it. It has a hole in the middle for the stem, is split into 2, 3 or 4 segments that lock together with dovetail tabs, and carries spikes on the underside that pin it into the soil.
The image shows the solid OpenSCAD renders at diameter 200 · hole_d 40 · thickness 3 · bar_w 3 · rings 4 · ring_spacing square · spokes_per_segment 4 · outer_spokes_from 2 · swirl 30 · segments 4 · gap 1 · edge_mode parallel · joint true · tab_w 4 · tab_depth 4 · clearance 0.3 · part all · spike_style blade · spike_len 25 · spike_base 10 · spike_w 4 · spike_ring_step 2 · spike_spoke_step 2 · round_r 0.5 · smoothness 96 · color_grid ForestGreen.
Cat guard for plant pots
mick/plant-cat-guard@1.2.0
Report
For a legal or policy problem: a license violation, personal data, somebody else’s work republished as their own. A model that prints badly is a comment, not a report.
Parameters
from sourceDescription
What is this
A round grid that lies on the soil of a plant pot so a cat cannot dig in it. It has a hole in the middle for the stem, is split into 2, 3 or 4 segments that lock together with dovetail tabs, and carries spikes on the underside that pin it into the soil.
What you use it for
Keeping cats (and their toilet habits) out of plant pots. Set the diameter to the pot, the hole to the stem, pick the number of segments, print each segment upside down with the spikes pointing up, click the segments together around the plant and press them into the soil.
Design notes
The grid is rings crossed by spokes, all drawn in 2D and extruded once. Spokes are strokes along a path, so swirl turns the straight spokes into a spiral around the stem without changing anything else. ring_spacing = "square" places the rings geometrically so every cell keeps the same proportions from hole to rim, and outer_spokes_from adds a spoke between every pair of spokes from that ring outward, dartboard style, so the outer cells do not grow wide.
The split runs along spokes: a cut spoke is drawn twice, one full bar on each side of the gap, so every segment is a closed frame. edge_mode = "parallel" shifts the two bars sideways and keeps the gap constant; "radial" rotates them so they point at the centre, which makes the gap a wedge that opens toward the rim. On each cut one segment carries a dovetail tab and the other a socket in a small boss, sized with tab_w, tab_depth and clearance, so the segments click together.
Spikes sit on the crossings of rings and spokes on every n-th ring and n-th spoke. "blade" spikes are flat knives as wide as the bar, running along the spoke, so they read as part of the frame and cut into the soil; "pyramid" gives the plain four-sided pin. Rounding is one offset pass over the finished 2D drawing, as a final polish. part = "one" gives a single segment flipped onto its plate side, the print orientation, with its own tab and socket.
Parameters
diameter and hole_d size the plate, thickness and bar_w the bars. rings, ring_spacing, spokes_per_segment, outer_spokes_from and swirl set the grid. segments, gap, edge_mode, joint, tab_w, tab_depth and clearance control the split. part picks the whole guard or one segment. spike_style, spike_len, spike_base, spike_w, spike_ring_step and spike_spoke_step shape and place the spikes. round_r is the polish, color_grid the display colour.
Use with your agent
{
"mcpServers": {
"remixcad": {
"type": "http",
"url": "https://remix-cad.vercel.app/api/mcp"
}
}
}get_model("mick/plant-cat-guard@1.2.0")Returns the manifest, the source, the import closure and the license obligations. Full MCP setup.
Parameter table
Parsed from the Customizer annotations in main.scad| Name | Default | Range | Unit | Description |
|---|---|---|---|---|
| diameter | 200 | 80 to 400 step 5 | ||
| hole_d | 40 | 10 to 200 step 2 | ||
| thickness | 3 | 2 to 6 step 0.5 | ||
| bar_w | 3 | 2 to 8 step 0.5 | ||
| rings | 4 | 2 to 10 step 1 | ||
| ring_spacing | "square" | even, square | ||
| spokes_per_segment | 4 | 1 to 10 step 1 | ||
| outer_spokes_from | 2 | 0 to 9 step 1 | ||
| swirl | 30 | -120 to 120 step 5 | ||
| segments | 4 | 2, 3, 4 | ||
| gap | 1 | 0.5 to 3 step 0.1 | ||
| edge_mode | "parallel" | parallel, radial | ||
| joint | true | |||
| tab_w | 4 | 3 to 8 step 0.5 | ||
| tab_depth | 4 | 2 to 8 step 0.5 | ||
| clearance | 0.3 | 0.1 to 0.6 step 0.05 | ||
| part | "all" | all, one | ||
| spike_style | "blade" | blade, pyramid | ||
| spike_len | 25 | 0 to 60 step 1 | ||
| spike_base | 10 | 4 to 20 step 1 | ||
| spike_w | 4 | 2 to 10 step 0.5 | ||
| spike_ring_step | 2 | 1 to 5 step 1 | ||
| spike_spoke_step | 2 | 1 to 6 step 1 | ||
| round_r | 0.5 | 0 to 2 step 0.25 | ||
| smoothness | 96 | 32 to 192 step 16 | ||
| color_grid | "ForestGreen" | ForestGreen, Silver, Gainsboro, White, DimGray, Black, SlateGray, FireBrick, SteelBlue, Goldenrod |
Lineage
0 models depend on this oneOnly what this model came from and what came from it.
License
Whoever downloads this takes these obligations along- mick/plant-cat-guard@1.2.0 is MIT
Source
main.scad is the entrypointmain.scad
// A round grid that lies on the soil of a plant pot to keep cats out. Split into segments
// that click together with dovetail tabs, with spikes underneath to pin it into the soil.
// Everything is drawn in 2D from circles and strokes and extruded once; rounding is one
// offset pass over the finished drawing, as a final polish.
/* [Size] */
// Outer diameter of the guard. Match it to the inside of the pot.
diameter = 200; // [80:5:400]
// Diameter of the hole in the middle, around the stem.
hole_d = 40; // [10:2:200]
// Thickness of the plate.
thickness = 3; // [2:0.5:6]
// Width of the bars.
bar_w = 3; // [2:0.5:8]
/* [Grid] */
// Concentric rings, counting the rim around the hole and the outer rim.
rings = 4; // [2:1:10]
// even: rings at equal distance. square: geometric, so every cell keeps the same shape.
ring_spacing = "square"; // [even, square]
// Spokes per segment: one edge spoke plus the spokes inside the segment.
spokes_per_segment = 4; // [1:1:10]
// Add a spoke between every pair from this ring outward (0 = outer rim). 0 turns it off.
outer_spokes_from = 2; // [0:1:9]
// Degrees a spoke turns between hole and rim. Zero gives straight spokes.
swirl = 30; // [-120:5:120]
/* [Split] */
// Number of segments the guard is cut into.
segments = 4; // [2, 3, 4]
// Width of the cut between the two edge spokes of neighbouring segments.
gap = 1; // [0.5:0.1:3]
// parallel: edge spokes shifted beside the cut, constant gap. radial: pointing at the centre.
edge_mode = "parallel"; // [parallel, radial]
// Dovetail tabs that click the segments together.
joint = true;
// Width of the tab neck.
tab_w = 4; // [3:0.5:8]
// How deep the tab head sits in the neighbouring segment.
tab_depth = 4; // [2:0.5:8]
// Play around the tab, taken from the socket.
clearance = 0.3; // [0.1:0.05:0.6]
// The whole guard, or one segment flipped into print orientation.
part = "all"; // [all, one]
/* [Spikes] */
// blade: flat knife in the width of the bar, along the spoke. pyramid: plain pin.
spike_style = "blade"; // [blade, pyramid]
// Length of the spikes below the plate. Zero removes them.
spike_len = 25; // [0:1:60]
// Length of a blade along the bar, at its base.
spike_base = 10; // [4:1:20]
// Width of a pyramid at its base.
spike_w = 4; // [2:0.5:10]
// A spike on every n-th ring, counted from the outer rim inward.
spike_ring_step = 2; // [1:1:5]
// A spike on every n-th spoke, counted from the edge spoke of each segment.
spike_spoke_step = 2; // [1:1:6]
/* [Polish] */
// Radius the bar corners are rounded to. Zero keeps them sharp.
round_r = 0.5; // [0:0.25:2]
/* [Quality] */
// Facets on the rings. More is smoother and slower.
smoothness = 96; // [32:16:192]
/* [Colour] */
// Display only, the STL carries no colour.
color_grid = "ForestGreen"; // [ForestGreen, Silver, Gainsboro, White, DimGray, Black, SlateGray, FireBrick, SteelBlue, Goldenrod]
/* [Hidden] */
hole = min(hole_d, diameter - 4 * bar_w - 10);
r_out = diameter / 2 - bar_w / 2; // centre line of the outer rim
r_in = hole / 2 + bar_w / 2; // centre line of the rim around the hole
r_mid = (r_in + r_out) / 2;
spokes = segments * spokes_per_segment;
seg_a = 360 / segments;
edge_off = (gap + bar_w) / 2; // sideways shift of a parallel edge spoke
delta = asin(min(0.9, (gap + bar_w) / (2 * r_in))); // rotation of a radial edge spoke
split_ring = min(outer_spokes_from, rings - 1);
steps = swirl == 0 ? 1 : 24;
r_polish = min(round_r, bar_w / 2 - 0.3, gap / 2 - 0.1);
wall = 1.5;
head_w = tab_w + 3;
// Ring i, with 0 the outer rim and rings-1 the rim around the hole.
function ring_r(i) = ring_spacing == "square"
? r_out * pow(r_in / r_out, i / (rings - 1))
: r_out - i * (r_out - r_in) / (rings - 1);
// Spoke j: its base angle, and whether it lies on a cut.
function spoke_a(j) = j * 360 / spokes;
function on_cut(j) = j % spokes_per_segment == 0;
// Sides a spoke is drawn on: two bars beside a cut, one bar elsewhere.
function spoke_sides(j) = on_cut(j) ? [-1, 1] : [0];
// ---- Paths ----
// A spoke of base angle a follows theta(a, r): straight, or turning by swirl from hole to rim.
function tt(r) = max(0, min(1, (r - r_in) / (r_out - r_in)));
function theta(a, r) = a + swirl * tt(r);
function dtheta(r) = (r > r_in && r < r_out) ? swirl * PI / 180 / (r_out - r_in) : 0;
function tangent_a(a, r) = theta(a, r) + atan(r * dtheta(r));
function polar(r, a) = r * [cos(a), sin(a)];
function nrm(a, r) = let(f = tangent_a(a, r)) [-sin(f), cos(f)];
// Point on spoke a, side s (-1, 0, 1), at radius r.
function pt(a, s, r) = edge_mode == "radial"
? polar(r, theta(a + s * delta, r))
: polar(r, theta(a, r)) + s * edge_off * nrm(a, r);
// Direction of that spoke at radius r.
function dir_a(a, s, r) = edge_mode == "radial" ? tangent_a(a + s * delta, r) : tangent_a(a, r);
function path(a, s, r0, r1, n) = [for (k = [0 : n]) pt(a, s, r0 + (r1 - r0) * k / n)];
function reverse(v) = [for (i = [len(v) - 1 : -1 : 0]) v[i]];
// A stroke of width w along a list of points.
module stroke(pts, w) {
for (k = [0 : len(pts) - 2])
hull() {
translate(pts[k]) circle(d = w, $fn = 16);
translate(pts[k + 1]) circle(d = w, $fn = 16);
}
}
// The polish: a closing pass rounds the concave corners, an opening pass the convex ones.
module polish2d(r) {
if (r > 0)
offset(r = r, $fn = 24) offset(r = -2 * r, $fn = 24) offset(r = r, $fn = 24) children();
else
children();
}
// ---- Grid pattern ----
// Rings, the spokes inside the segments and the extra outer spokes. The edge spokes are
// added after the cuts, see plate2d.
module grid_core2d() {
for (i = [0 : rings - 1])
difference() {
circle(r = ring_r(i) + bar_w / 2, $fn = smoothness);
circle(r = ring_r(i) - bar_w / 2, $fn = smoothness);
}
for (j = [0 : spokes - 1])
if (!on_cut(j))
stroke(path(spoke_a(j), 0, r_in, r_out, steps), bar_w);
if (outer_spokes_from > 0)
for (j = [0 : spokes - 1])
stroke(path(spoke_a(j) + 180 / spokes, 0, ring_r(split_ring), r_out, steps), bar_w);
}
module edge_spokes2d() {
for (j = [0 : spokes - 1], s = spoke_sides(j))
if (s != 0)
stroke(path(spoke_a(j), s, r_in, r_out, steps), bar_w);
}
// Width of the cut at radius r: the gap, or the wedge between two radial edge spokes.
function cut_w(r) = (edge_mode == "radial" ? max(gap, 2 * r * sin(delta) - bar_w * cos(delta)) : gap) + 0.4;
module cuts2d() {
R = diameter / 2 + 5;
n = 24;
for (k = [0 : segments - 1]) {
a = spoke_a(k * spokes_per_segment);
for (m = [0 : n - 1])
hull() {
translate(pt(a, 0, R * m / n)) circle(d = cut_w(R * m / n), $fn = 16);
translate(pt(a, 0, R * (m + 1) / n)) circle(d = cut_w(R * (m + 1) / n), $fn = 16);
}
}
}
// ---- Joint ----
// In the local frame of a cut at r_mid: x along the cut outward, +y toward the segment
// that owns the tab, -y toward the segment with the socket.
function off_mid() = edge_mode == "radial" ? r_mid * sin(delta) : edge_off;
function y0() = -off_mid() + bar_w / 2; // gap-facing edge of the socket spoke
module at_cut(k) {
a = spoke_a(k * spokes_per_segment);
translate(pt(a, 0, r_mid)) rotate(dir_a(a, 0, r_mid)) children();
}
module tab2d() {
polygon([[-tab_w / 2, off_mid()], [tab_w / 2, off_mid()], [tab_w / 2, y0()],
[head_w / 2, y0() - tab_depth], [-head_w / 2, y0() - tab_depth], [-tab_w / 2, y0()]]);
}
module boss2d() {
translate([-(head_w / 2 + clearance + wall), y0() - tab_depth - wall])
square([head_w + 2 * (clearance + wall), tab_depth + wall + 0.01]);
}
module socket2d() {
offset(r = clearance)
intersection() {
tab2d();
translate([-50, y0() - 50]) square([100, 50 + 0.6]);
}
}
// ---- Plate ----
module body2d() {
difference() {
polish2d(r_polish)
union() {
difference() { grid_core2d(); cuts2d(); }
edge_spokes2d();
if (joint) for (k = [0 : segments - 1]) at_cut(k) boss2d();
}
if (joint) for (k = [0 : segments - 1]) at_cut(k) socket2d();
}
}
module tabs2d(ks) {
if (joint) for (k = ks) at_cut(k) polish2d(min(r_polish, 0.8)) tab2d();
}
// One segment: the wedge between cut 0 and cut 1, following the cut paths.
module wedge2d() {
R = diameter / 2 + 8;
a0 = spoke_a(0);
a1 = spoke_a(spokes_per_segment);
polygon(concat(
[[0, 0]],
path(a0, 0, 0.5, R, 24),
[for (a = [theta(a0, R) : 2 : theta(a1, R)]) polar(R, a)],
reverse(path(a1, 0, 0.5, R, 24))));
}
module plate2d() {
if (part == "one") {
intersection() { body2d(); wedge2d(); }
tabs2d([0]);
} else {
body2d();
tabs2d([0 : segments - 1]);
}
}
module plate() {
linear_extrude(height = thickness) plate2d();
}
// ---- Spikes ----
module spike(phi) {
if (spike_style == "blade")
rotate(phi) rotate([90, 0, 0])
linear_extrude(height = bar_w, center = true)
polygon([[-spike_base / 2, 0.5], [spike_base / 2, 0.5], [0, -spike_len]]);
else
rotate(phi + 45) translate([0, 0, 0.5]) mirror([0, 0, 1])
cylinder(h = spike_len + 0.5, d1 = spike_w * sqrt(2), d2 = 0.8, $fn = 4);
}
module spikes() {
if (spike_len > 0)
for (i = [0 : rings - 1], j = [0 : spokes - 1], s = spoke_sides(j))
if (i % spike_ring_step == 0 && j % spike_spoke_step == 0)
translate(pt(spoke_a(j), s, ring_r(i))) spike(dir_a(spoke_a(j), s, ring_r(i)));
}
module spikes_one() {
intersection() {
spikes();
translate([0, 0, -spike_len - 1]) linear_extrude(height = spike_len + 2) wedge2d();
}
}
// ---- Layout ----
if (part == "one") {
// Flipped onto its plate side, spikes up: the print orientation.
color(color_grid) rotate(-seg_a / 2) translate([0, 0, thickness]) mirror([0, 0, 1]) {
plate();
spikes_one();
}
} else {
color(color_grid) { plate(); spikes(); }
}
Comments
Context, not a score. Comments never move a model up or down the results.No comments yet
Printed it? A note about your material and the clearance you landed on saves the next person a failed print.
