HoopLang Docs
/
Playground
El libro · Capítulo 02

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:

HoopLang
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#

HoopLang
rect(at: (5mm, 5mm), size: (70mm, 40mm), radius: 6mm)
  • at: la esquina superior izquierda. Es opcional: sin at, 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#

HoopLang
circle(center: (20mm, 25mm), r: 11mm)

Centro y radio. Los argumentos van con nombre: circle((20mm, 25mm), 11mm) no compila.

polygon#

HoopLang
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:

HoopLang
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 hasta p.
  • curve c1 c2 p: una curva de Bézier cúbica hasta p, con dos puntos de control c1 y c2 (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:

Salida del compilador
02-gota-abierta.hoop:5:14: error E004: a closed shape is required: the path is missing close

gota 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ónEscribesResultado
unióna + blo que está en a o en b
diferenciaa - ba con b recortado: un hueco o un mordisco
interseccióna & bsó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:

HoopLang
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:

HoopLang
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:

HoopLang
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étodoHace
.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:

HoopLang
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:

Salida del compilador
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):

MiembroEs
.centerel centro (Point)
.top_left, .top_right, .bottom_left, .bottom_rightlas 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, .heightel ancho y el alto (Length)

Con aritmética de puntos (Point + Point) y de longitudes, ubicas cosas respecto de otras sin calcular a mano:

HoopLang
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.center vale, y también canvas.inset(5mm).center o rect(size: (10mm, 5mm)).width.

  • El nombre del miembro se revisa:

    Salida del compilador
    02-centre.hoop:2:11: error E007: a shape has no `.centre`; did you mean `.center`?

Todo junto: un llavero#

HoopLang
// 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) }
}
Salida del compilador
formas.hoop: ok — 3 threads, 2 layers, 6 objects

Para 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, …) y path { from … line … curve … close }.
  • + une, - resta (huecos), & intersecta.
  • .move, .rotate, .scale, .inset, .outset devuelven formas nuevas; con y hacia 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