Metadata-Version: 2.4
Name: gmd-editor
Version: 0.1.4
Summary: Decode, encode, and edit Geometry Dash .gmd level files
Project-URL: Source, https://github.com/cool101wool/gmd_editor
License: MIT
Keywords: gamefiles,geometry-dash,gmd,level
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# gmd-editor

A Python library for decoding, encoding, and editing Geometry Dash `.gmd` level files.

## Installation

```bash
pip install gmd_editor
```

## Quick Start

```python
from gmd_editor import load, save

# Load a .gmd file
level = load("MyLevel.gmd")

print(f"Object count: {level.object_count}")
print(f"Bounding box: {level.bounding_box()}")

# Iterate over objects
for obj in level:
    print(f"id={obj.id}  x={obj.x}  y={obj.y}")

# Move all objects up by 30 units
level.transform(lambda o: setattr(o, "y", (o.y or 0) + 30))

# Save the modified level
save(level, "MyLevel_modified.gmd")
```

## API Reference

### `load(path)` / `save(level, path)`

```python
from gmd_editor import load, save

level = load("level.gmd")
save(level, "level_out.gmd")
```

### `GMDLevel`

| Attribute / Method | Description |
|--------------------|-------------|
| `level.objects` | `list[GDObject]` — all objects |
| `level.header` | The level settings string |
| `level.object_count` | Number of objects |
| `level.bounding_box()` | `(min_x, min_y, max_x, max_y)` |
| `level.unique_object_ids()` | Set of GD object IDs used |
| `level.filter(fn)` | Filter objects by predicate |
| `level.filter_by_id(id)` | Filter objects by GD object ID |
| `level.transform(fn)` | Apply a function to every object in-place |
| `level.add_object(obj)` | Append a new object |
| `level.remove_objects(fn)` | Remove matching objects; returns count removed |
| `level.to_level_string()` | Serialise to raw level string |
| `level.to_gmd_string()` | Serialise back to full .gmd XML |

### `GDObject`

Named property shortcuts (all gettable and settable):

| Property | Key | Type |
|----------|-----|------|
| `obj.id` | `"1"` | `int` |
| `obj.x` | `"2"` | `float` |
| `obj.y` | `"3"` | `float` |
| `obj.h_flip` | `"4"` | `bool` |
| `obj.v_flip` | `"5"` | `bool` |
| `obj.rotation` | `"6"` | `float` |
| `obj.scale` | `"36"` | `float` |
| `obj.groups` | `"57"` | `list[int]` |

Raw access for any property:

```python
obj["21"]          # get by numeric key string
obj["21"] = "3"    # set
obj.get("21", "0") # with default
obj.remove("21")   # delete
```

### Low-level codec

```python
from gmd_editor.codec import (
    decode_level_string,  # base64url+gzip → plain text
    encode_level_string,  # plain text → base64url+gzip
    extract_b64,          # pull encoded data from .gmd XML
    inject_b64,           # put encoded data back into .gmd XML
    fix_padding,          # fix GD's broken base64 padding
)
```

## Examples

### Teleport all objects to a fixed X position

```python
from gmd_editor import load, save

level = load("level.gmd")
level.transform(lambda o: setattr(o, "x", 0.0))
save(level, "level_out.gmd")
```

### Remove all objects of a specific type

```python
from gmd_editor import load, save

level = load("level.gmd")
removed = level.remove_objects(lambda o: o.id == 8)  # remove all spike objects
print(f"Removed {removed} spikes")
save(level, "level_out.gmd")
```

### Create a new object and add it

```python
from gmd_editor import load, save
from gmd_editor import GDObject

level = load("level.gmd")

new_obj = GDObject()
new_obj.id = 1        # block
new_obj.x = 300.0
new_obj.y = 0.0
level.add_object(new_obj)

save(level, "level_out.gmd")
```

### Work directly with the level string

```python
from gmd_editor.level import GMDLevel

level = GMDLevel.from_level_string("kA,1;1,1,2,100,3,200;")
print(level[0].x)   # 100.0
raw = level.to_level_string()
```
