# Example: drag-drop import
URL: https://lsd.tools/docs/ex-import-template
Updated: 2026-07-17

> A @tool.template registers a file type (extensions=[".csv"]); its build function (ctx, files, name=None) returns a ready-to-run Entity by calling an @tool.entity builder's .build(...). Drop a file on the desktop and a whole pipeline appears, already wired.

The nicest way to start a pipeline is to drop a file on the canvas and have a whole, wired entity
appear. A [`@tool.template`](/docs/tools-commands-actions) maps a file extension to a **build
function** that returns a pre-wired [entity](/docs/tools-entities).

## The code

```python
# tool.py
from lsdtools import Tool, Context, Source, Entity, Table

tool = Tool("cloud", label="Point Cloud")


@tool.load(source_kinds=("file",))
def read_points(src: Source) -> Table:
    """Read a points file (needs x, y, z columns)."""
    return src.read()


@tool.shape
def near_origin(t: Table, radius: float = 100.0) -> Table:
    """Keep points within `radius` of the origin on the XY plane."""
    r2 = t["x"] * t["x"] + t["y"] * t["y"]
    return t.filter(r2 <= radius * radius)


@tool.entity(label="Point cloud", icon="lsd-folder-symbolic")
def point_cloud(path: str = "", radius: float = 100.0) -> Table:
    """An entity that reads a file, then trims it to points near the origin."""
    src = read_points(Source.file(path))
    return near_origin(src, radius=radius)


@tool.template("point-csv", label="Point cloud (CSV)", extensions=[".csv"])
def import_points(ctx: Context, files, name=None) -> Entity:
    """Dropped .csv → a pre-wired Point cloud entity."""
    return point_cloud.build(name=name or files.main.stem, path=str(files.main))
```

## What each line does

- **`@tool.entity` `point_cloud`** — a **builder function**: its signature (`path`, `radius`)
 becomes the entity's declarative parameters, and its body runs once at creation to wire the child
 steps. It returns the **terminal step**; LSD collects that step and everything upstream of it.
- **`@tool.template("point-csv", ..., extensions=[".csv"])`** — registers the drop target. The build
 function is `(ctx, files, name=None) -> Entity`.
- **`files.main`** — the dropped file as a `Path` (`files["role"]` addresses a specific role for
 multi-file templates). `files.main.stem` gives a sensible default entity name.
- **`point_cloud.build(name=..., path=...)`** — creates the entity of that type, passing the
 template's values as its parameters. Because the builder wires `read_points → near_origin`, the
 returned entity is ready to run.

## Run it in Python

The graph the template builds — run the same wiring directly to see the result:

```python
from lsdtools import Engine, Source, Table

Table({"x": [0.0, 200.0], "y": [0.0, 0.0], "z": [1.0, 2.0]}).write_csv("cloud.csv")

pts = near_origin(read_points(Source.file("cloud.csv")), radius=100.0)
Engine().run(pts).output.print()
# → x y z
# 0 0 1 (the point 200 m out is dropped)
```

And the template/entity path itself — what a drop triggers:

```python
entity = point_cloud.build(name="cloud", path="cloud.csv") # what import_points returns
Engine().add(entity).run("cloud") # runs the pre-wired pipeline
```

## Try changing it

- Add `roles=[FileRole("collar"), FileRole("survey")]` (from `lsdtools.advanced`) for a multi-file
 drop, and read them with `files["collar"]` / `files["survey"]`.
- Give the template a `sniff=` function so it only claims CSVs whose header has `x, y, z`.
- Set `priority=` so your template wins over another that also handles `.csv`.

## Related

**Learn** — [Entity types](/docs/tools-entities) · [Commands & actions](/docs/tools-commands-actions) · [Load steps](/docs/tools-load)

**API** — [Tool](/docs/api-tool) · [Context](/docs/api-context) · [Source](/docs/api-source)

**Examples** — [File loader](/docs/ex-file-loader) · [A complete package](/docs/ex-full-package)
