RAIMAD

Editing methods overview

This page aims to offer a complete overview of all editing operations in RAIMAD.

Editing operations are methods of Transform, Proxy, and Boundpoint. Complete documentation generated from Python docstrings and method signatures is available in the pages linked above.

These two automatic test files in the RAIMAD repo may provide additional insight:

Briefly:

Rotation

Method Transform Proxy Boundpoint
protate
crotate
orotate
rotate

Rotation takes two inputs: the angle, and the reference point (the point "around" -- or, in British English, "about" -- which the rotation is happening).

The angle is given in radians in the counterclockwise orientation (mathematicians call this "positive orientation").

The reference point can be given as two separate x and y coordinates with the crotate method, or as a 2-tuple holding both coordinates in one object using the protate method:

import raimad as rai

# annular sector 1/8 of a circle wide facing towards positive y
ansec = rai.AnSec.from_auto(
    r1=40, r2=50,
    thetamid=rai.quartercircle, 
    dtheta=rai.eigthcircle,
    )

# original
show(ansec)

# rotate around origin with
# coords given as two separate arguments
r1 = ansec.proxy().crotate(rai.quartercircle, 0, 0)
show(r1)

# rotate around ansec's middle with
# coords given as a tuple
r2 = ansec.proxy().protate(rai.quartercircle, (0, 45))
show(r2)

# The difference between these two invocations
# won't be visible in the preview because of autocrop,
# but it does change where they end up:
print(f"{r1.bbox.mid = }")
print(f"{r2.bbox.mid = }")
r1.bbox.mid = <(-43.477590650225736, 7.105427357601002e-15) bound to <
	Manual Proxy at 8  with <Transform Rotate 90.00) > of
	AnSec at /  >
r2.bbox.mid = <(1.5224093497742643, 45.00000000000001) bound to <
	Manual Proxy at 8  with <Transform Move (+1.00, +1.00) Rotate 90.00) > of
	AnSec at /  >

rotate takes either:

# original
show(ansec)

# tuple
r1 = ansec.proxy().rotate(rai.quartercircle, (0, 0))
show(r1)

# or separate coords
r2 = ansec.proxy().rotate(rai.quartercircle, 10, 10)
show(r2)

print(f"{r1.bbox.mid = }")
print(f"{r2.bbox.mid = }")
r1.bbox.mid = <(-43.477590650225736, 7.105427357601002e-15) bound to <
	Manual Proxy at o  with <Transform Rotate 90.00) > of
	AnSec at /  >
r2.bbox.mid = <(-23.477590650225736, 7.105427357601002e-15) bound to <
	Manual Proxy at /  with <Transform Move (+1.00, +1.00) Rotate 90.00) > of
	AnSec at /  >

Or none at all (in which case, the origin of the proxy's coordinate grid is used as the reference point):

# explicitly specify origin
r1 = ansec.proxy().rotate(rai.quartercircle, 0, 0)
show(r1)

# don't specify a reference point
r2 = ansec.proxy().rotate(rai.quartercircle)
show(r2)

# The two transformations are identical
print(f"{r1.bbox.mid = }")
print(f"{r2.bbox.mid = }")
r1.bbox.mid = <(-43.477590650225736, 7.105427357601002e-15) bound to <
	Manual Proxy at  with <Transform Rotate 90.00) > of
	AnSec at /  >
r2.bbox.mid = <(-43.477590650225736, 7.105427357601002e-15) bound to <
	Manual Proxy at  with <Transform Rotate 90.00) > of
	AnSec at /  >

The orotate method allows rotating by a multiple of 90 degrees around the origin. This is useful for avoiding floating point noise -- see Coordinates and Transformations.

rect = rai.RectLW(20, 10)
rect_orot = rect.proxy().orotate(1)
show(rect)
show(rect_orot)
print(f"{rect.bbox.as_list() = }")
print(f"{rect_orot.bbox.as_list() = }")
rect.bbox.as_list() = [-10.0, -5.0, 10.0, 5.0]
rect_orot.bbox.as_list() = [-5.0, -10.0, 5.0, 10.0]

Rotating around a BoundPoint can be done with the rotate method. Since the BoundPoint itself is the reference point, protate, crotate, and orotate methods are not defined for it.

# original
show(ansec)

# rotate around ansec's middle (explicit coordinates)
r1 = ansec.proxy().rotate(rai.quartercircle, 0, 45)
show(r1)

# Same thing but using bbox.mid
r2 = ansec.proxy().bbox.mid.rotate(rai.quartercircle)
show(r2)

print(f"{r1.bbox.mid = }")
print(f"{r2.bbox.mid = }")
r1.bbox.mid = <(1.5224093497742643, 45.00000000000001) bound to <
	Manual Proxy at 8  with <Transform Move (+1.00, +1.00) Rotate 90.00) > of
	AnSec at /  >
r2.bbox.mid = <(0.0, 43.477590650225736) bound to <
	Manual Proxy at  with <Transform Move (+1.00, +1.00) Rotate 90.00) > of
	AnSec at /  >

Translation (movement)

Method Transform Proxy Boundpoint
pmove
cmove
movex
movey
move

Translation takes two arguments: x and y offset. As with rotation, we have cmove and pmove that take two numbers and one tuple respectively, and move, which takes either. We also define movex and movey methods that take only one coordinate.

All movement methods are defined for all editing classes. Since there is no "reference point" for translation, moving a BoundPoint is the same as moving its Proxy.

Reflection (flipping)

Method Transform Proxy Boundpoint
pflip
cflip
hflip
vflip
flip

hflip flips horizontally (mirrors along the vertical axis). vflip flips vertically (mirrors along the horizontal axis). A custom vertical or horizontal line can be specified. cflip and pflip can mirror along two axes at once, with the former taking the x and y coordinates separately, and the latter taking them as a 2-tuple. flip can take either, defaulting to mirroring along the X and Y axes.

Mirroring can be done in reference to a BoundPoint, in which case the position of the boundpoint are used as the axes of mirroring -- either separately using hflip and vflip, or both at once with flip. pflip and cflip are not defined for BoundPoint, since the BoundPoint itself is the reference.

Scaling

Method Transform Proxy Boundpoint
apscale
acscale
ppscale
ccscale
cpscale
pcscale
ascale
pscale
cscale
scale

Scaling is the most complicated. It takes an X scale factor, a Y scale factor, and a reference point. Either can be given as separate coords or a tuple. Also, a single number can be used as both the X and Y factor. There are methods for each combination:

Method Scale factor Reference point
apscale single number tuple
acscale single number two args
ppscale tuple tuple
ccscale two args two args
cpscale two args tuple
pcscale tuple two args
sn = rai.Snowman()
show(sn)
show(sn.proxy().acscale(0.2, 0, 0))
show(sn.proxy().apscale(0.1, (0, 0)))
show(sn.proxy().ppscale((0.1, 0.2), (0, 0)))
show(sn.proxy().ccscale(0.2, 0.1, 0, 0))

BoundPoint's scaling methods take only the factors, and use the BoundPoint itself as the reference point. Again, the factors can be given as two separate numbers, a tuple, or a single number for both the X and Y scale.

Method Reference point
ascale single number
pscale tuple
cscale two args
show(sn)
s1 = sn.proxy().bbox.top_right.pscale((0.2, 0.1))
s2 = sn.proxy().bbox.bot_left.cscale(0.2, 0.1)

# Same scale factors...
show(s1)
show(s2)

# But different locations due to different
# reference point
print(f"{s1.bbox.mid = }")
print(f"{s2.bbox.mid = }")
s1.bbox.mid = <(40.0, 159.0) bound to <
	Manual Proxy at o  with <Transform Move (+0.20, +0.10) Scale (0.20, 0.10)> of
	Snowman at \  >
s2.bbox.mid = <(-40.0, -39.0) bound to <
	Manual Proxy at  with <Transform Move (+0.20, +0.10) Scale (0.20, 0.10)> of
	Snowman at \  >

Finally, scale just works with whatever you throw at it:

# All of these scale by a factor of 0.2
# on X and Y around the origin

show(sn.proxy().scale(0.2))
show(sn.proxy().scale(0.2, 0, 0))
show(sn.proxy().scale(0.2, (0, 0)))
show(sn.proxy().scale(0.2, 0.2, 0, 0))
show(sn.proxy().scale((0.2, 0.2), (0, 0)))

Aligning Points

BoindPoints have special a special to method exclusive to them, which transforms the underlying Proxy such that the BoundPoint ends up at specific coordinates. This is basically a more ergonomic version of move, useful in operations that have a sense of "connecting" or "overlapping" things together. pto and cto variants are available.

Method Transform Proxy Boundpoint
to
pto
cto
class Foo(rai.Compo):
    def _make(self):
        center = rai.RectLW(40, 40).proxy()
        left = rai.RectLW(20, 20).proxy()
        right = left.proxy()

        left.bbox.mid.to(center.bbox.bot_left)
        right.bbox.mid.to(center.bbox.bot_right)

        self.subcompos.append(center)
        self.subcompos.append(left)
        self.subcompos.append(right)

show(Foo())

Snapping

Method Transform Proxy Boundpoint
snap_left
snap_right
snap_above
snap_below

Proxies have snapping methods that let you connect them to other proxies as if they had magnets. You can snap things above, below, to the left, and to the right of each other. See Coordinates and Transformations and Class: Proxy for more info.

class Foo(rai.Compo):
    def _make(self):
        center = rai.RectLW(40, 40).proxy()
        left = rai.RectLW(20, 20).proxy()
        right = left.proxy()

        left.snap_left(center)
        right.snap_right(center)

        self.subcompos.append(center)
        self.subcompos.append(left)
        self.subcompos.append(right)

show(Foo())