rewrite of tag handling

This commit is contained in:
Adrian Mariano 2022-05-15 10:51:45 -04:00
parent 494b6a40db
commit 174fb02e2b
7 changed files with 540 additions and 251 deletions

File diff suppressed because it is too large Load diff

View file

@ -227,12 +227,12 @@ module half_joiner(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0
{ {
vnf = half_joiner(l=l, w=w, base=base, ang=ang, screwsize=screwsize); vnf = half_joiner(l=l, w=w, base=base, ang=ang, screwsize=screwsize);
if (in_list("remove",$tags_shown)) { if (in_list("remove",$tags_shown)) {
attachable(anchor,spin,orient, size=[w,l,base*2], $tags="remove") { attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
half_joiner_clear(l=l, w=w, ang=ang, clearance=1); half_joiner_clear(l=l, w=w, ang=ang, clearance=1);
union(); union();
} }
} else { } else {
attachable(anchor,spin,orient, size=[w,base*2,l], $tags="keep") { attachable(anchor,spin,orient, size=[w,base*2,l], $tag="keep") {
vnf_polyhedron(vnf, convexity=12); vnf_polyhedron(vnf, convexity=12);
children(); children();
} }
@ -426,12 +426,12 @@ module half_joiner2(l=20, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=
{ {
vnf = half_joiner2(l=l, w=w, base=base, ang=ang, screwsize=screwsize); vnf = half_joiner2(l=l, w=w, base=base, ang=ang, screwsize=screwsize);
if (in_list("remove",$tags_shown)) { if (in_list("remove",$tags_shown)) {
attachable(anchor,spin,orient, size=[w,l,base*2], $tags="remove") { attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
half_joiner_clear(l=l, w=w, ang=ang, clearance=1); half_joiner_clear(l=l, w=w, ang=ang, clearance=1);
union(); union();
} }
} else { } else {
attachable(anchor,spin,orient, size=[w,base*2,l], $tags="keep") { attachable(anchor,spin,orient, size=[w,base*2,l], $tag="keep") {
vnf_polyhedron(vnf, convexity=12); vnf_polyhedron(vnf, convexity=12);
children(); children();
} }
@ -512,12 +512,12 @@ function joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, o
module joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP) module joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, orient=UP)
{ {
if (in_list("remove",$tags_shown)) { if (in_list("remove",$tags_shown)) {
attachable(anchor,spin,orient, size=[w,l,base*2], $tags="remove") { attachable(anchor,spin,orient, size=[w,l,base*2], $tag="remove") {
joiner_clear(w=w, l=l, ang=ang, clearance=1); joiner_clear(w=w, l=l, ang=ang, clearance=1);
union(); union();
} }
} else { } else {
attachable(anchor,spin,orient, size=[w,l,base*2], $tags="keep") { attachable(anchor,spin,orient, size=[w,l,base*2], $tag="keep") {
union() { union() {
back(l/4) half_joiner(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize); back(l/4) half_joiner(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize);
fwd(l/4) half_joiner2(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize); fwd(l/4) half_joiner2(l=l/2, w=w, base=base, ang=ang, screwsize=screwsize);
@ -569,35 +569,35 @@ module joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, ori
// dovetail("male", w=15, h=8, slide=30, taper=6); // dovetail("male", w=15, h=8, slide=30, taper=6);
// right(20) dovetail("female", 15, 8, 30, taper=6); // Same as above // right(20) dovetail("female", 15, 8, 30, taper=6); // Same as above
// Example: A block that can link to itself // Example: A block that can link to itself
// diff("remove") // diff()
// cuboid([50,30,10]){ // cuboid([50,30,10]){
// attach(BACK) dovetail("male", slide=10, width=15, height=8); // attach(BACK) dovetail("male", slide=10, width=15, height=8);
// attach(FRONT) dovetail("female", slide=10, width=15, height=8,$tags="remove"); // tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8);
// } // }
// Example: Setting the dovetail angle. This is too extreme to be useful. // Example: Setting the dovetail angle. This is too extreme to be useful.
// diff("remove") // diff()
// cuboid([50,30,10]){ // cuboid([50,30,10]){
// attach(BACK) dovetail("male", slide=10, width=15, height=8, angle=30); // attach(BACK) dovetail("male", slide=10, width=15, height=8, angle=30);
// attach(FRONT) dovetail("female", slide=10, width=15, height=8, angle=30,$tags="remove"); // tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8, angle=30);
// } // }
// Example: Adding a chamfer helps printed parts fit together without problems at the corners // Example: Adding a chamfer helps printed parts fit together without problems at the corners
// diff("remove") // diff("remove")
// cuboid([50,30,10]){ // cuboid([50,30,10]){
// attach(BACK) dovetail("male", slide=10, width=15, height=8, chamfer=1); // attach(BACK) dovetail("male", slide=10, width=15, height=8, chamfer=1);
// attach(FRONT) dovetail("female", slide=10, width=15, height=8,chamfer=1,$tags="remove"); // tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8,chamfer=1);
// } // }
// Example: Rounding the outside corners is another option // Example: Rounding the outside corners is another option
// diff("remove") // diff("remove")
// cuboid([50,30,10]) { // cuboid([50,30,10]) {
// attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1, $fn=32); // attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1, $fn=32);
// attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1, $tags="remove", $fn=32); // tag("remove") attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1, $fn=32);
// } // }
// Example: Or you can make a fully rounded joint // Example: Or you can make a fully rounded joint
// $fn=32; // $fn=32;
// diff("remove") // diff("remove")
// cuboid([50,30,10]){ // cuboid([50,30,10]){
// attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1.5, round=true); // attach(BACK) dovetail("male", slide=10, width=15, height=8, radius=1.5, round=true);
// attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1.5, round=true, $tags="remove"); // tag("remove")attach(FRONT) dovetail("female", slide=10, width=15, height=8, radius=1.5, round=true);
// } // }
// Example: With a long joint like this, a taper makes the joint easy to assemble. It will go together easily and wedge tightly if you get the tolerances right. Specifying the taper with `back_width` may be easier than using a taper angle. // Example: With a long joint like this, a taper makes the joint easy to assemble. It will go together easily and wedge tightly if you get the tolerances right. Specifying the taper with `back_width` may be easier than using a taper angle.
// cuboid([50,30,10]) // cuboid([50,30,10])
@ -605,14 +605,14 @@ module joiner(l=40, w=10, base=10, ang=30, screwsize, anchor=CENTER, spin=0, ori
// fwd(35) // fwd(35)
// diff("remove") // diff("remove")
// cuboid([50,30,10]) // cuboid([50,30,10])
// attach(TOP) dovetail("female", slide=50, width=18, height=4, back_width=15, spin=90, $tags="remove"); // tag("remove") attach(TOP) dovetail("female", slide=50, width=18, height=4, back_width=15, spin=90);
// Example: A series of dovetails forming a tail board, with the inside of the joint up. A standard wood joint would have a zero taper. // Example: A series of dovetails forming a tail board, with the inside of the joint up. A standard wood joint would have a zero taper.
// cuboid([50,30,10]) // cuboid([50,30,10])
// attach(BACK) xcopies(10,5) dovetail("male", slide=10, width=7, taper=4, height=4); // attach(BACK) xcopies(10,5) dovetail("male", slide=10, width=7, taper=4, height=4);
// Example: Mating pin board for a half-blind right angle joint, where the joint only shows on the side but not the front. Note that the anchor method and use of `spin` ensures that the joint works even with a taper. // Example: Mating pin board for a half-blind right angle joint, where the joint only shows on the side but not the front. Note that the anchor method and use of `spin` ensures that the joint works even with a taper.
// diff("remove") // diff("remove")
// cuboid([50,30,10]) // cuboid([50,30,10])
// position(TOP+BACK) xcopies(10,5) dovetail("female", slide=10, width=7, taper=4, height=4, $tags="remove",anchor=BOTTOM+FRONT,spin=180); // tag("remove")position(TOP+BACK) xcopies(10,5) dovetail("female", slide=10, width=7, taper=4, height=4, anchor=BOTTOM+FRONT,spin=180);
function dovetail(gender, width, height, slide, h, w, angle, slope, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient) = no_function("dovetail"); function dovetail(gender, width, height, slide, h, w, angle, slope, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient) = no_function("dovetail");
module dovetail(gender, width, height, slide, h, w, angle, slope, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient) module dovetail(gender, width, height, slide, h, w, angle, slope, taper, back_width, chamfer, extra=0.01, r, radius, round=false, anchor=BOTTOM, spin=0, orient)
{ {
@ -871,10 +871,11 @@ module snap_pin(size,r,radius,d,diameter, l,length, nub_depth, snap, thickness,
// snap_pin_socket("standard", anchor=CENTER, orient=UP, fins=true, fixed=false, $fn=40); // snap_pin_socket("standard", anchor=CENTER, orient=UP, fins=true, fixed=false, $fn=40);
// Example: A cube with a socket in the middle and one half-way off the front edge so you can see inside: // Example: A cube with a socket in the middle and one half-way off the front edge so you can see inside:
// $fn=40; // $fn=40;
// diff("socket") cuboid([20,20,20]) { // diff("socket") cuboid([20,20,20])
// attach(TOP) snap_pin_socket("standard", $tags="socket"); // tag("socket"){
// position(TOP+FRONT)snap_pin_socket("standard", $tags="socket"); // attach(TOP) snap_pin_socket("standard");
// } // position(TOP+FRONT)snap_pin_socket("standard");
// }
function snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) = no_function("snap_pin_socket"); function snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) = no_function("snap_pin_socket");
module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) { module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fixed=true, pointed=true, fins=false, anchor=BOTTOM, spin=0, orient=DOWN) {
sizedat = _pin_size(size); sizedat = _pin_size(size);
@ -1011,9 +1012,10 @@ module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fi
// right(width+13) // right(width+13)
// diff("remove") // diff("remove")
// cuboid([width+8,max(12,length+2),depth+3], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM) // cuboid([width+8,max(12,length+2),depth+3], chamfer=.5, edges=[FRONT,"Y"], anchor=BOTTOM)
// tag("remove")
// attach(BACK) // attach(BACK)
// rabbit_clip(type="socket",length=length, width=width,snap=snap,thickness=thickness,depth=depth+extra_depth, // rabbit_clip(type="socket",length=length, width=width,snap=snap,thickness=thickness,
// lock=lock,compression=0,$tags="remove"); // depth=depth+extra_depth, lock=lock,compression=0);
// } // }
// left(37)ydistribute(spacing=28){ // left(37)ydistribute(spacing=28){
// test_pair(length=6, width=7, snap=0.25, thickness=0.8, compression=0.1); // test_pair(length=6, width=7, snap=0.25, thickness=0.8, compression=0.1);
@ -1033,11 +1035,11 @@ module snap_pin_socket(size, r, radius, l,length, d,diameter,nub_depth, snap, fi
// left(32) // left(32)
// diff("remove") // diff("remove")
// cuboid([30,30,11],orient=BACK,anchor=BACK){ // cuboid([30,30,11],orient=BACK,anchor=BACK){
// attach(BACK)rabbit_clip("socket", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5.5, lock_clearance=3,$tags="remove"); // tag("remove")attach(BACK)rabbit_clip("socket", length=25, width=25, thickness=1.5, snap=2, compression=0, lock=true, depth=5.5, lock_clearance=3);
// xflip_copy() // xflip_copy()
// position(FRONT+LEFT) // position(FRONT+LEFT)
// xscale(0.8) // xscale(0.8)
// zcyl(l=20,r=13.5, $tags="remove",$fn=64); // tag("remove")zcyl(l=20,r=13.5, $fn=64);
// } // }
function rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1, clearance=.1, lock=false, lock_clearance=0, function rabbit_clip(type, length, width, snap, thickness, depth, compression=0.1, clearance=.1, lock=false, lock_clearance=0,

View file

@ -399,7 +399,7 @@ module rounding_angled_corner_mask(r, ang=90, d, anchor=CENTER, spin=0, orient=U
// diff("mask") // diff("mask")
// cyl(h=30, d=30) { // cyl(h=30, d=30) {
// attach(TOP) // attach(TOP)
// #rounding_cylinder_mask(d=30, rounding=5, $tags="mask"); // #tag("mask")rounding_cylinder_mask(d=30, rounding=5);
// } // }
function rounding_cylinder_mask(r, rounding, d) = no_function("rounding_cylinder_mask"); function rounding_cylinder_mask(r, rounding, d) = no_function("rounding_cylinder_mask");
module rounding_cylinder_mask(r, rounding, d) module rounding_cylinder_mask(r, rounding, d)

View file

@ -1616,8 +1616,7 @@ module text(text, size=10, font="Helvetica", halign, valign, spacing=1.0, direct
$parent_geom = geom; $parent_geom = geom;
$parent_size = _attach_geom_size(geom); $parent_size = _attach_geom_size(geom);
$attach_to = undef; $attach_to = undef;
do_show = _attachment_is_shown($tags); if (_is_shown()){
if (do_show) {
_color($color) { _color($color) {
_text( _text(
text=text, size=size, font=font, text=text, size=size, font=font,

View file

@ -2479,8 +2479,7 @@ module text3d(text, h=1, size=10, font="Helvetica", halign, valign, spacing=1.0,
$parent_geom = geom; $parent_geom = geom;
$parent_size = _attach_geom_size(geom); $parent_size = _attach_geom_size(geom);
$attach_to = undef; $attach_to = undef;
do_show = _attachment_is_shown($tags); if (_is_shown()) {
if (do_show) {
_color($color) { _color($color) {
linear_extrude(height=h, center=true) linear_extrude(height=h, center=true)
_text( _text(

View file

@ -72,32 +72,33 @@ module manfrotto_rc2_plate(chamfer="all",anchor,orient,spin)
left(10,back(-flat_height,select(pts,-3))) left(10,back(-flat_height,select(pts,-3)))
]; ];
tag_scope()
attachable(anchor,spin,orient,size=[botwid,length,thickness],size2=[topwid,length],shift=[.64115/2,0]){ attachable(anchor,spin,orient,size=[botwid,length,thickness],size2=[topwid,length],shift=[.64115/2,0]){
down(thickness/2) down(thickness/2)
diff() diff()
linear_sweep(pts,h=length,convexity=4,orient=FWD,anchor=FWD){ linear_sweep(pts,h=length,convexity=4,orient=FWD,anchor=FWD){
zflip_copy() zflip_copy()
down(.01)fwd(.01)left(.01)position(LEFT+FRONT+BOT) down(.01)fwd(.01)left(.01)position(LEFT+FRONT+BOT)
cuboid([corner_space,(length-innerlen)/2,thickness+.02], chamfer=-chsize, $tags="remove", cuboid([corner_space,(length-innerlen)/2,thickness+.02], chamfer=-chsize, $tag="remove",
orient=FWD,anchor=TOP+LEFT+FWD,edges=chamf_top?"ALL":TOP); orient=FWD,anchor=TOP+LEFT+FWD,edges=chamf_top?"ALL":TOP);
fwd(left_top)position(LEFT+BACK)linear_sweep(h=cutout_len,facet,convexity=4,$tags="remove",anchor=RIGHT+BACK); fwd(left_top)position(LEFT+BACK)linear_sweep(h=cutout_len,facet,convexity=4,$tag="remove",anchor=RIGHT+BACK);
if (chamf_bot){ if (chamf_bot){
edge_mask(FRONT+LEFT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(FRONT+LEFT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(FRONT+RIGHT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(FRONT+RIGHT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(FRONT+TOP)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(FRONT+TOP)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(FRONT+BOT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(FRONT+BOT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(TOP+RIGHT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(TOP+RIGHT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(BOT+RIGHT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(BOT+RIGHT)chamfer_edge_mask(length,chsize,$tag="remove");
zflip_copy(){ zflip_copy(){
right(corner_space)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize,$tags="remove"); right(corner_space)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize,$tag="remove");
down((length-innerlen)/2)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize,$tags="remove"); down((length-innerlen)/2)edge_mask(TOP+LEFT) chamfer_edge_mask(length,chsize,$tag="remove");
} }
} }
if (chamf_top){ if (chamf_top){
edge_mask(BACK+LEFT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(BACK+LEFT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(BACK+RIGHT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(BACK+RIGHT)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(BACK+TOP)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(BACK+TOP)chamfer_edge_mask(length,chsize,$tag="remove");
edge_mask(BACK+BOT)chamfer_edge_mask(length,chsize,$tags="remove"); edge_mask(BACK+BOT)chamfer_edge_mask(length,chsize,$tag="remove");
} }
} }
children(); children();

View file

@ -415,7 +415,7 @@ do this:
include <BOSL2/std.scad> include <BOSL2/std.scad>
diff("hole") diff("hole")
cube(100, center=true) cube(100, center=true)
cylinder(h=101, d=50, center=true, $tags="hole"); tag("hole")cylinder(h=101, d=50, center=true);
``` ```
The `keep=` argument takes tags for shapes that you want to keep in the output. The `keep=` argument takes tags for shapes that you want to keep in the output.
@ -425,8 +425,8 @@ include <BOSL2/std.scad>
diff("dish", keep="antenna") diff("dish", keep="antenna")
cube(100, center=true) cube(100, center=true)
attach([FRONT,TOP], overlap=33) { attach([FRONT,TOP], overlap=33) {
cylinder(h=33.1, d1=0, d2=95, $tags="dish"); tag("dish") cylinder(h=33.1, d1=0, d2=95);
cylinder(h=33.1, d=10, $tags="antenna"); tag("antenna") cylinder(h=33.1, d=10);
} }
``` ```
@ -437,10 +437,10 @@ will inherit the "keep" tag and get kept.
```openscad-3D ```openscad-3D
include <BOSL2/std.scad> include <BOSL2/std.scad>
diff("hole", "keep") diff("hole", "keep")
cube(100, center=true, $tags="keep") tag("keep")cube(100, center=true)
attach([RIGHT,TOP]) { attach([RIGHT,TOP]) {
cylinder(d=95, h=5, $tags=""); tag("") cylinder(d=95, h=5);
cylinder(d=50, h=11, anchor=CTR, $tags="hole"); tag("hole") cylinder(d=50, h=11, anchor=CTR);
} }
``` ```
@ -464,8 +464,8 @@ though, so you will need to set the tags of the children as well as the parent.
```openscad-3D ```openscad-3D
include <BOSL2/std.scad> include <BOSL2/std.scad>
diff("hole") diff("hole")
cube([20,11,45], center=true, $tags="hole") cube([20,11,45], center=true, $tag="hole")
cube([40,10,90], center=true, $tags="body"); cube([40,10,90], center=true, $tag="body");
``` ```
Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable Tags (and therefore tag-based operations like `diff()`) only work correctly with attachable
@ -498,7 +498,7 @@ everything that *is* tagged with it.
include <BOSL2/std.scad> include <BOSL2/std.scad>
intersect("bounds") intersect("bounds")
cube(100, center=true) cube(100, center=true)
cylinder(h=100, d1=120, d2=95, center=true, $fn=72, $tags="bounds"); cylinder(h=100, d1=120, d2=95, center=true, $fn=72, $tag="bounds");
``` ```
If given both the `a=` and `b=` arguments, then shapes marked with tags given to `a=` will be If given both the `a=` and `b=` arguments, then shapes marked with tags given to `a=` will be
@ -509,8 +509,8 @@ include <BOSL2/std.scad>
intersect("pole", "cap") intersect("pole", "cap")
cube(100, center=true) cube(100, center=true)
attach([TOP,RIGHT]) { attach([TOP,RIGHT]) {
cube([40,40,80],center=true, $tags="pole"); cube([40,40,80],center=true, $tag="pole");
sphere(d=40*sqrt(2), $tags="cap"); sphere(d=40*sqrt(2), $tag="cap");
} }
``` ```
@ -521,8 +521,8 @@ the result of the union:
include <BOSL2/std.scad> include <BOSL2/std.scad>
intersect("bounds", keep="pole") intersect("bounds", keep="pole")
cube(100, center=true) { cube(100, center=true) {
cylinder(h=100, d1=120, d2=95, center=true, $fn=72, $tags="bounds"); cylinder(h=100, d1=120, d2=95, center=true, $fn=72, $tag="bounds");
zrot(45) xcyl(h=140, d=20, $fn=36, $tags="pole"); zrot(45) xcyl(h=140, d=20, $fn=36, $tag="pole");
} }
``` ```
@ -533,9 +533,9 @@ unioning the result with every other shape.
```openscad-3D ```openscad-3D
include <BOSL2/std.scad> include <BOSL2/std.scad>
hulling("hull") hulling("hull")
cube(50, center=true, $tags="hull") { cube(50, center=true, $tag="hull") {
cyl(h=100, d=20); cyl(h=100, d=20);
xcyl(h=100, d=20, $tags="pole"); xcyl(h=100, d=20, $tag="pole");
} }
``` ```