Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add some more documentation of functions #27

Merged
merged 4 commits into from
Mar 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
*.ipynb
*.quarto_ipynb
/docs/api
/build
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

A set of data-oriented wrappers around the python API of Blender.

This was originally used internally inside of [Molecular Nodes](https://github.com/BradyAJohnston/MolecularNodes) but was broken out into a separate python module for re-use in other projects.
This was originally used internally inside of [Molecular
Nodes](https://github.com/BradyAJohnston/MolecularNodes) but was broken
out into a separate python module for re-use in other projects.

## Installation

Expand Down
2 changes: 2 additions & 0 deletions README.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ title: databpy

A set of data-oriented wrappers around the python API of Blender.

This was originally used internally inside of [Molecular Nodes](https://github.com/BradyAJohnston/MolecularNodes) but was broken out into a separate python module for re-use in other projects.

## Installation
Available on PyPI, install with pip:

Expand Down
1 change: 1 addition & 0 deletions databpy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
store_named_attribute,
remove_named_attribute,
list_attributes,
evaluate_object,
Attribute,
AttributeType,
AttributeTypeInfo,
Expand Down
83 changes: 41 additions & 42 deletions databpy/attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import bpy
from bpy.types import Object
import numpy as np
from pathlib import Path

COMPATIBLE_TYPES = [bpy.types.Mesh, bpy.types.Curve, bpy.types.PointCloud]

Expand Down Expand Up @@ -36,34 +35,6 @@ def list_attributes(
return list([name for name in obj.data.attributes.keys()])


def path_resolve(path: str | Path) -> Path:
"""
Resolve a path string or Path object to an absolute Path.

Parameters
----------
path : str or Path
The path to resolve, either as a string or Path object.

Returns
-------
Path
The resolved absolute Path.

Raises
------
ValueError
If the path cannot be resolved.
"""

if isinstance(path, str):
return Path(bpy.path.abspath(path))
elif isinstance(path, Path):
return Path(bpy.path.abspath(str(path)))
else:
raise ValueError(f"Unable to resolve path: {path}")


@dataclass
class AttributeTypeInfo:
dname: str
Expand All @@ -88,7 +59,8 @@ def __init__(self, message):
# https://docs.blender.org/api/current/bpy_types_enum_items/attribute_domain_items.html#rna-enum-attribute-domain-items
class AttributeDomains:
"""
Enumeration of attribute domains in Blender.
Enumeration of attribute domains in Blender. You can store an attribute onto one of
these domains if there is corressponding geometry. All data is on a domain on geometry.

Attributes
----------
Expand Down Expand Up @@ -411,9 +383,9 @@ def store_named_attribute(
import numpy as np
from databpy import store_named_attribute, list_attributes, named_attribute
obj = bpy.data.objects["Cube"]
list_attributes(obj)
print(f"{list_attributes(obj)=}")
store_named_attribute(obj, np.arange(8), "test_attribute")
list_attributes(obj)
print(f"{list_attributes(obj)=}")
named_attribute(obj, "test_attribute")
```
"""
Expand Down Expand Up @@ -464,23 +436,43 @@ def store_named_attribute(
return attribute


def evaluate_object(obj: bpy.types.Object):
def evaluate_object(
obj: bpy.types.Object, context: bpy.types.Context | None = None
) -> bpy.types.Object:
"""
Return an object which has the modifiers evaluated.

Parameters
----------
obj : bpy.types.Object
The Blender object to evaluate.
context : bpy.types.Context | None, optional
The Blender context to use for evaluation, by default None

Returns
-------
bpy.types.Object
The evaluated object with modifiers applied.

Notes
-----
This function evaluates the object's modifiers using the current depsgraph.
If no context is provided, it uses the current bpy.context.

Examples
--------
```{python}
import bpy
from databpy import evaluate_object
obj = bpy.data.objects['Cube']
evaluated_obj = evaluate_object(obj)
```
"""
if context is None:
context = bpy.context
_check_is_mesh(obj)
obj.update_tag()
return obj.evaluated_get(bpy.context.evaluated_depsgraph_get())
return obj.evaluated_get(context.evaluated_depsgraph_get())


def named_attribute(
Expand Down Expand Up @@ -514,7 +506,7 @@ def named_attribute(
import bpy
from databpy import named_attribute, list_attributes
obj = bpy.data.objects["Cube"]
list_attributes(obj)
print(f"{list_attributes(obj)=}")
named_attribute(obj, "position")
```

Expand All @@ -539,11 +531,7 @@ def named_attribute(
return attr.as_array()


def remove_named_attribute(
obj: bpy.types.Object,
name: str,
domain: str | AttributeDomain = AttributeDomains.POINT,
):
def remove_named_attribute(obj: bpy.types.Object, name: str):
"""
Remove a named attribute from an object.

Expand All @@ -553,13 +541,24 @@ def remove_named_attribute(
The Blender object.
name : str
Name of the attribute to remove.
domain : str or AttributeDomain, optional
The domain of the attribute, by default POINT.

Raises
------
AttributeError
If the named attribute does not exist on the mesh.

Examples
--------
```{python}
import bpy
import numpy as np
from databpy import remove_named_attribute, list_attributes, store_named_attribute
obj = bpy.data.objects["Cube"]
store_named_attribute(obj, np.random.rand(8, 3), "random_numbers")
print(f"{list_attributes(obj)=}")
remove_named_attribute(obj, "random_numbers")
print(f"{list_attributes(obj)=}")
```
"""
_check_obj_attributes(obj)
try:
Expand Down
88 changes: 76 additions & 12 deletions databpy/object.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,38 @@
from uuid import uuid1

import bpy
from bpy.types import Object
import numpy as np
from bpy.types import Object
from mathutils import Matrix
from numpy import typing as npt

from . import attribute as attr
from .addon import register
from .attribute import (
AttributeDomain,
AttributeDomains,
AttributeTypes,
AttributeType,
evaluate_object,
_check_obj_attributes,
AttributeDomains,
AttributeDomain,
evaluate_object,
)
from .addon import register

from .collection import create_collection
from uuid import uuid1
from . import attribute as attr
from .utils import centre
from mathutils import Matrix


class LinkedObjectError(Exception):
"""
Error raised when a Python object doesn't have a linked object in the 3D scene.

Parameters
----------
message : str
The error message describing why the linked object is missing or invalid.

Attributes
----------
message : str
The error message that was passed.
"""

def __init__(self, message: str):
self.message = message
super().__init__(self.message)
Expand Down Expand Up @@ -607,7 +620,58 @@ def create_bob(
collection: bpy.types.Collection | None = None,
uuid: str | None = None,
) -> BlenderObject:
"Create an object but return it wrapped as a BlenderObject"
"""
Create a BlenderObject wrapper around a new Blender object.

Parameters
----------
vertices : ndarray or None, optional
Array of vertex coordinates. Each row represents a vertex.
Default is None.
edges : ndarray or None, optional
Array of edge indices. Each row contains indices of vertices forming an edge.
Default is None.
faces : ndarray or None, optional
Array of face indices. Each row contains indices of vertices forming a face.
Default is None.
name : str, optional
Name of the created object.
Default is "NewObject".
collection : bpy.types.Collection or None, optional
Blender collection to link the object to.
Default is None.
uuid : str or None, optional
Directly set the UUID on the resulting BlenderObject rather than generating one.
Default is None.

Returns
-------
BlenderObject
A wrapped Blender object with additional functionality.

See Also
--------
:func:`create_object` : The underlying function used to create the Blender object.

Notes
-----
If uuid is provided, it will be set both on the BlenderObject wrapper
and the underlying Blender object.

Examples
--------
```{python}
import numpy as np
from databpy.object import create_bob
vertices = np.array([[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]])
bob = create_bob(vertices=vertices, name="MyObject")
print(bob.name)
print(len(bob))
bob.named_attribute("position")
```


"""
bob = BlenderObject(
create_object(
vertices=vertices,
Expand Down
41 changes: 34 additions & 7 deletions databpy/utils.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import numpy as np
from pathlib import Path
import bpy


def centre(position: np.ndarray, weight: np.ndarray | None = None):
Expand Down Expand Up @@ -33,14 +35,39 @@ def lerp(a: np.ndarray, b: np.ndarray, t: float = 0.5) -> np.ndarray:

Examples
--------
>>> lerp(1, 2, 0.5)
1.5
```{python}
from databpy.utils import lerp
lerp(1, 2, 0.5)
lerp(3, 7, 0.2)
lerp([1, 2, 3], [4, 5, 6], 0.5)
```
"""
return np.add(a, np.multiply(np.subtract(b, a), t))


>>> lerp(3, 7, 0.2)
3.8
def path_resolve(path: str | Path) -> Path:
"""
Resolve a path string or Path object to an absolute Path

>>> lerp([1, 2, 3], [4, 5, 6], 0.5)
array([2.5, 3.5, 4.5])
Parameters
----------
path : str or Path
The path to resolve, either as a string or Path object.

Returns
-------
Path
The resolved absolute Path.

Raises
------
ValueError
If the path cannot be resolved.
"""
return np.add(a, np.multiply(np.subtract(b, a), t))

if isinstance(path, str):
return Path(bpy.path.abspath(path)).absolute()
elif isinstance(path, Path):
return Path(bpy.path.abspath(str(path))).absolute()
else:
raise ValueError(f"Unable to resolve path: {path}")
6 changes: 5 additions & 1 deletion docs/_quarto.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ quartodoc:
contents:
- named_attribute
- store_named_attribute
- remove_named_attribute
- AttributeDomains
- AttributeTypes
- title: Collections
Expand All @@ -43,6 +44,9 @@ quartodoc:
- title: Objects
contents:
# - object.ObjectTracker
- BlenderObject
- create_object
- create_bob
- evaluate_object
- BlenderObject
- LinkedObjectError
# - ObjectTracker
5 changes: 4 additions & 1 deletion docs/api/_sidebar.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@ website:
- contents:
- api/named_attribute.qmd
- api/store_named_attribute.qmd
- api/remove_named_attribute.qmd
- api/AttributeDomains.qmd
- api/AttributeTypes.qmd
section: Attribute
- contents:
- api/create_collection.qmd
section: Collections
- contents:
- api/BlenderObject.qmd
- api/create_object.qmd
- api/create_bob.qmd
- api/evaluate_object.qmd
- api/BlenderObject.qmd
- api/LinkedObjectError.qmd
section: Objects
id: api
- id: dummy-sidebar
Loading