2. Formas
Un bordado es, antes que nada, geometría: áreas que se rellenan, bordes que se cubren, líneas que se recorren. En este capítulo aprendes a construir formas en HoopLang, a combinarlas y a transformarlas, hasta armar un llavero completo.
Dar nombre a una forma: shape#
Puedes escribir una forma directamente en un objeto
(fill azul { shape: circle(…) }), pero casi siempre conviene darle nombre:
shape placa = rect(at: (5mm, 5mm), size: (70mm, 40mm), radius: 6mm)shape declara un valor de tipo Shape: una figura cerrada, que puede
tener huecos. Con nombre, la forma se reutiliza (el relleno y el borde de
satín usan la misma) y se lee mejor.
Las cuatro formas básicas#
rect#
rect(at: (5mm, 5mm), size: (70mm, 40mm), radius: 6mm)at: la esquina superior izquierda. Es opcional: sinat, la esquina va en(0mm, 0mm).size:(ancho, alto).radius: opcional, redondea las esquinas.
Las esquinas redondeadas no son sólo estética: una esquina viva de 90° en un relleno o en un borde de satín acumula puntadas en el vértice y tiende a abultarse. Un radio de 2 a 6 mm se borda más limpio.
circle#
circle(center: (20mm, 25mm), r: 11mm)Centro y radio. Los argumentos van con nombre: circle((20mm, 25mm), 11mm)
no compila.
polygon#
polygon((0mm, 0mm), (8mm, 0mm), (4mm, -7mm))Una lista de puntos (al menos tres), sin nombre. El último se une con el primero solo.
path#
Para curvas, path { … } describe un camino segmento a segmento:
shape gota = path {
from (15mm, 2mm)
curve (22mm, 14mm) (28mm, 22mm) (28mm, 27mm)
curve (28mm, 34mm) (22mm, 38mm) (15mm, 38mm)
curve (8mm, 38mm) (2mm, 34mm) (2mm, 27mm)
curve (2mm, 22mm) (8mm, 14mm) (15mm, 2mm)
close
}from p: el punto de partida (sólo al principio).line p: una recta hastap.curve c1 c2 p: una curva de Bézier cúbica hastap, con dos puntos de controlc1yc2(como en cualquier programa de vectores).close: cierra el camino. Nada puede ir después.
Un path sin close es un camino abierto (tipo Path): sirve para una
línea de corrido o un satín a lo largo, pero no para rellenar. Si olvidas el
close en una forma:
02-gota-abierta.hoop:5:14: error E004: a closed shape is required: the path is missing closegota no llegó a declararse, pero el compilador no repite el error en cada
lugar que la usa: el único error es el que importa. Cuando veas varios
errores, de todos modos, arregla primero el de más arriba y vuelve a
verificar.
Operaciones entre formas#
Las formas se combinan como conjuntos:
| Operación | Escribes | Resultado |
|---|---|---|
| unión | a + b | lo que está en a o en b |
| diferencia | a - b | a con b recortado: un hueco o un mordisco |
| intersección | a & b | sólo lo que a y b tienen en común |
Los huecos se hacen restando. Un aro es un círculo menos otro círculo:
shape aro = circle(center: (20mm, 25mm), r: 11mm) - circle(center: (20mm, 25mm), r: 7mm)Un relleno con un hueco deja la tela a la vista en el hueco: así se borda la "O" de un logo o la ventana de un escudo, sin poner un hilo del color de la tela encima.
La intersección recorta una forma con otra. Esta franja es la parte de una banda girada que cae dentro de la placa:
shape franja = placa & rect(at: (30mm, 0mm), size: (12mm, 60mm)).rotate(20deg)& se aplica antes que + y - (como * antes que +). Si combinas
varias, usa paréntesis para que se lea lo que quieres:
shape rayos = (barra + barra.rotate(45deg) + barra.rotate(90deg)) - circle(center: c, r: 17mm)Transformar: métodos de una forma#
Una forma tiene métodos, que devuelven una forma nueva (la original no cambia):
| Método | Hace |
|---|---|
.move(dx, dy) | la desplaza |
.rotate(a) | la gira a grados alrededor de su centro |
.rotate(a, p) | la gira alrededor del punto p |
.scale(r) | la escala (80%, 150%) desde su centro |
.scale(r, p) | la escala desde el punto p |
.inset(d) | la achica d hacia adentro, por todo el borde |
.outset(d) | la agranda d hacia afuera |
Se encadenan, y se aplican de izquierda a derecha:
fill oro { shape: punta.move(55mm, 36mm).rotate(180deg) }Un detalle del sistema de coordenadas: como y crece hacia abajo, un ángulo
positivo se ve girar en sentido horario en la pantalla y en la tela.
inset y outset son las más usadas en bordado:
escudo.inset(3mm)es el área del relleno cuando el escudo lleva un borde de satín de 3 mm: el relleno no tiene que meterse bajo todo el satín.aro.outset(1mm)recorta el fondo un poco más grande que el aro que va encima: el aro se borda sobre tela, no sobre otro relleno, y el bordado no se engruesa.
Si llamas a un método que no existe, el mensaje lista los que hay:
02-grow.hoop:2:17: error E001: unknown method or invalid arguments: .grow(…) (inset(Length), outset(Length), move(Length, Length), rotate(Angle[, center]), scale(Ratio[, center]))Medidas y puntos de una forma: los miembros#
Una forma con nombre te da puntos y medidas de su caja (el rectángulo que la contiene):
| Miembro | Es |
|---|---|
.center | el centro (Point) |
.top_left, .top_right, .bottom_left, .bottom_right | las esquinas |
.top (o .top_center), .bottom (o .bottom_center) | el centro de los lados de arriba y de abajo |
.left (o .center_left), .right (o .center_right) | el centro de los lados |
.width, .height | el ancho y el alto (Length) |
Con aritmética de puntos (Point + Point) y de longitudes, ubicas cosas
respecto de otras sin calcular a mano:
design "Miembros" { canvas: rect(size: (60mm, 40mm)) }
thread azul = #1f3a8a "Azul"
thread oro = #d8a21b "Oro"
shape placa = rect(at: (5mm, 5mm), size: (50mm, 30mm), radius: 4mm)
const ancho = placa.width // 50mm
const arriba = placa.top_center // (30mm, 5mm)
layer a {
fill azul { shape: placa }
}
layer b {
fill oro { shape: circle(center: placa.center, r: placa.height / 4) }
fill oro { shape: circle(center: arriba + (0mm, 5mm), r: 2mm) }
fill oro { shape: rect(at: placa.bottom_left + (5mm, -6mm), size: (ancho - 10mm, 2mm)) }
}Si cambias el tamaño de placa, todo lo demás se acomoda solo. El lienzo
también es una forma con nombre: canvas.center, canvas.width y
canvas.inset(5mm) valen.
Dos cosas que conviene saber desde ya:
Los miembros se piden a cualquier forma, no sólo a un nombre:
placa.centervale, y tambiéncanvas.inset(5mm).centerorect(size: (10mm, 5mm)).width.El nombre del miembro se revisa:
Salida del compilador02-centre.hoop:2:11: error E007: a shape has no `.centre`; did you mean `.center`?
Todo junto: un llavero#
// formas.hoop: un llavero hecho con formas y operaciones.
design "Llavero" { canvas: rect(size: (80mm, 50mm)) }
thread azul = #1f3a8a "Azul"
thread rojo = #cc3f24 "Rojo"
thread oro = #d8a21b "Oro"
// La placa: un rectángulo de esquinas redondeadas.
shape placa = rect(at: (5mm, 5mm), size: (70mm, 40mm), radius: 6mm)
// Un aro: un círculo menos otro círculo (un hueco).
shape aro = circle(center: (20mm, 25mm), r: 11mm) - circle(center: (20mm, 25mm), r: 7mm)
// Una franja: lo que la placa y una banda girada tienen en común.
shape franja = placa & rect(at: (30mm, 0mm), size: (12mm, 60mm)).rotate(20deg)
// Un triángulo, copiado, movido y girado.
shape punta = polygon((0mm, 0mm), (8mm, 0mm), (4mm, -7mm))
layer fondo {
// La placa sin el aro: el aro va encima, en otro color.
fill azul { shape: placa - aro.outset(1mm) }
}
layer detalle {
fill oro { shape: aro }
fill rojo { shape: franja.inset(1mm) }
fill oro { shape: punta.move(55mm, 20mm) }
fill oro { shape: punta.move(55mm, 36mm).rotate(180deg) }
fill oro { shape: circle(center: placa.center + (24mm, 0mm), r: 2mm) }
}formas.hoop: ok — 3 threads, 2 layers, 6 objectsPara más ideas, mira docs/lenguaje/galeria/sol.hoop: ocho rayos con cuatro
barras giradas y unidas, un círculo restado, y unas montañas recortadas por un
disco con &.
Resumen#
shape nombre = …da nombre a una forma cerrada.- Básicas:
rect(at:, size:, radius:),circle(center:, r:),polygon(p1, p2, p3, …)ypath { from … line … curve … close }. +une,-resta (huecos),&intersecta..move,.rotate,.scale,.inset,.outsetdevuelven formas nuevas; conyhacia abajo, un ángulo positivo gira en sentido horario..center,.top_left,.width… dan puntos y medidas de la caja de una forma con nombre.
Referencia: primitivas · expresiones · tipos