sdforge 0.1.2__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sdforge-0.2.0/PKG-INFO +287 -0
- sdforge-0.2.0/README.md +248 -0
- sdforge-0.2.0/examples/__init__.py +0 -0
- sdforge-0.2.0/examples/camera.py +66 -0
- sdforge-0.2.0/examples/debug.py +56 -0
- sdforge-0.2.0/examples/export.py +32 -0
- sdforge-0.2.0/examples/forge.py +63 -0
- sdforge-0.2.0/examples/group.py +58 -0
- sdforge-0.2.0/examples/light.py +62 -0
- sdforge-0.2.0/examples/material.py +23 -0
- sdforge-0.2.0/examples/noise.py +57 -0
- sdforge-0.2.0/examples/operations.py +71 -0
- sdforge-0.2.0/examples/params.py +35 -0
- sdforge-0.2.0/examples/primitives.py +68 -0
- sdforge-0.2.0/examples/render.py +30 -0
- sdforge-0.2.0/examples/saving.py +79 -0
- sdforge-0.2.0/examples/shaping.py +70 -0
- sdforge-0.2.0/examples/transforms.py +125 -0
- sdforge-0.2.0/sdforge/__init__.py +30 -0
- sdforge-0.2.0/sdforge/core.py +340 -0
- sdforge-0.2.0/sdforge/debug.py +13 -0
- sdforge-0.2.0/sdforge/export.py +119 -0
- {sdforge-0.1.2/sdforge/glsl/scene → sdforge-0.2.0/sdforge/glsl}/camera.glsl +2 -2
- sdforge-0.2.0/sdforge/glsl/debug.glsl +15 -0
- sdforge-0.2.0/sdforge/glsl/noise.glsl +50 -0
- {sdforge-0.1.2/sdforge/glsl/sdf → sdforge-0.2.0/sdforge/glsl}/operations.glsl +7 -31
- {sdforge-0.1.2/sdforge/glsl/sdf → sdforge-0.2.0/sdforge/glsl}/primitives.glsl +12 -9
- {sdforge-0.1.2/sdforge/glsl/scene → sdforge-0.2.0/sdforge/glsl}/raymarching.glsl +2 -4
- sdforge-0.2.0/sdforge/glsl/shaping.glsl +23 -0
- {sdforge-0.1.2/sdforge/glsl/sdf → sdforge-0.2.0/sdforge/glsl}/transforms.glsl +32 -35
- sdforge-0.2.0/sdforge/loader.py +32 -0
- sdforge-0.2.0/sdforge/mesh.py +150 -0
- sdforge-0.2.0/sdforge/render.py +309 -0
- sdforge-0.2.0/sdforge/utils.py +8 -0
- sdforge-0.2.0/sdforge.egg-info/PKG-INFO +287 -0
- sdforge-0.2.0/sdforge.egg-info/SOURCES.txt +59 -0
- sdforge-0.2.0/sdforge.egg-info/requires.txt +15 -0
- sdforge-0.2.0/sdforge.egg-info/top_level.txt +3 -0
- {sdforge-0.1.2 → sdforge-0.2.0}/setup.py +10 -5
- sdforge-0.2.0/tests/__init__.py +0 -0
- sdforge-0.2.0/tests/camera.py +27 -0
- sdforge-0.2.0/tests/conftest.py +147 -0
- sdforge-0.2.0/tests/debug.py +64 -0
- sdforge-0.2.0/tests/export.py +47 -0
- sdforge-0.2.0/tests/forge.py +85 -0
- sdforge-0.2.0/tests/group.py +67 -0
- sdforge-0.2.0/tests/light.py +10 -0
- sdforge-0.2.0/tests/material.py +48 -0
- sdforge-0.2.0/tests/noise.py +50 -0
- sdforge-0.2.0/tests/operations.py +131 -0
- sdforge-0.2.0/tests/params.py +27 -0
- sdforge-0.2.0/tests/primitives.py +144 -0
- sdforge-0.2.0/tests/render.py +95 -0
- sdforge-0.2.0/tests/saving.py +78 -0
- sdforge-0.2.0/tests/shaping.py +58 -0
- sdforge-0.2.0/tests/transforms.py +124 -0
- sdforge-0.1.2/PKG-INFO +0 -255
- sdforge-0.1.2/README.md +0 -219
- sdforge-0.1.2/sdforge/__init__.py +0 -45
- sdforge-0.1.2/sdforge/api.py +0 -1078
- sdforge-0.1.2/sdforge/mesh.py +0 -95
- sdforge-0.1.2/sdforge/render.py +0 -427
- sdforge-0.1.2/sdforge.egg-info/PKG-INFO +0 -255
- sdforge-0.1.2/sdforge.egg-info/SOURCES.txt +0 -19
- sdforge-0.1.2/sdforge.egg-info/requires.txt +0 -11
- sdforge-0.1.2/sdforge.egg-info/top_level.txt +0 -1
- {sdforge-0.1.2 → sdforge-0.2.0}/LICENSE +0 -0
- {sdforge-0.1.2 → sdforge-0.2.0}/MANIFEST.in +0 -0
- {sdforge-0.1.2/sdforge/glsl/scene → sdforge-0.2.0/sdforge/glsl}/light.glsl +0 -0
- {sdforge-0.1.2 → sdforge-0.2.0}/sdforge.egg-info/dependency_links.txt +0 -0
- {sdforge-0.1.2 → sdforge-0.2.0}/setup.cfg +0 -0
sdforge-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sdforge
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: A Python library for SDF modeling with real-time GLSL rendering and mesh export.
|
|
5
|
+
Home-page: https://github.com/nassimberrada/sdforge
|
|
6
|
+
Author: nassimberrada
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Multimedia :: Graphics :: 3D Modeling
|
|
13
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
14
|
+
Requires-Python: >=3.6
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: numpy
|
|
18
|
+
Requires-Dist: scikit-image>=0.17
|
|
19
|
+
Requires-Dist: watchdog
|
|
20
|
+
Requires-Dist: moderngl
|
|
21
|
+
Requires-Dist: glfw
|
|
22
|
+
Provides-Extra: ui
|
|
23
|
+
Requires-Dist: imgui[glfw]>=2.0.0; extra == "ui"
|
|
24
|
+
Provides-Extra: export
|
|
25
|
+
Requires-Dist: pygltflib; extra == "export"
|
|
26
|
+
Provides-Extra: full
|
|
27
|
+
Requires-Dist: imgui[glfw]>=2.0.0; extra == "full"
|
|
28
|
+
Requires-Dist: pygltflib; extra == "full"
|
|
29
|
+
Dynamic: author
|
|
30
|
+
Dynamic: classifier
|
|
31
|
+
Dynamic: description
|
|
32
|
+
Dynamic: description-content-type
|
|
33
|
+
Dynamic: home-page
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
Dynamic: provides-extra
|
|
36
|
+
Dynamic: requires-dist
|
|
37
|
+
Dynamic: requires-python
|
|
38
|
+
Dynamic: summary
|
|
39
|
+
|
|
40
|
+
<p align="center">
|
|
41
|
+
<picture>
|
|
42
|
+
<source srcset="./assets/logo_dark.png" media="(prefers-color-scheme: dark)">
|
|
43
|
+
<source srcset="./assets/logo_light.png" media="(prefers-color-scheme: light)">
|
|
44
|
+
<img src="./assets/logo_light.png" alt="SDForge Logo" height="200">
|
|
45
|
+
</picture>
|
|
46
|
+
</p>
|
|
47
|
+
|
|
48
|
+
## About
|
|
49
|
+
|
|
50
|
+
SDF Forge is a Python library for creating 3D models using Signed Distance Functions (SDFs). It provides a real-time, interactive rendering experience in a native desktop window, powered by GLSL raymarching.
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- **Simple, Pythonic API:** Define complex shapes by combining primitives using standard operators (`|`, `-`, `&`) and chaining transformations.
|
|
55
|
+
- **Real-time Rendering with Hot-Reloading:** Get instant visual feedback in a lightweight native window powered by `moderngl` and `glfw`. Changes to your script are reloaded automatically.
|
|
56
|
+
- **Mesh Exporting:** Save your creations as `.stl`, `.obj`, or `.glb` files for 3D printing or use in other software.
|
|
57
|
+
- **Interactive Parameters:** Define parameters that can be controlled by UI sliders in real-time (with a compatible UI renderer).
|
|
58
|
+
- **Standalone GLSL Export:** Export your entire scene to a single, self-contained GLSL fragment shader for use in game engines or graphics applications.
|
|
59
|
+
- **Custom GLSL Primitives:** Write custom SDF logic directly in GLSL using the `Forge` object for maximum flexibility and performance.
|
|
60
|
+
|
|
61
|
+
## Getting Started
|
|
62
|
+
|
|
63
|
+
### Hello Forge
|
|
64
|
+
|
|
65
|
+
Define a simple shape and open a real-time preview window with just a few lines of code.
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
from sdforge import sphere, box
|
|
69
|
+
|
|
70
|
+
# A sphere intersected with a box
|
|
71
|
+
shape = sphere(1) & box(1.5)
|
|
72
|
+
|
|
73
|
+
# Render a preview in a native window.
|
|
74
|
+
# An interactive orbit camera is used by default.
|
|
75
|
+
shape.render()
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Live Preview & Hot-Reloading
|
|
79
|
+
For an interactive workflow, wrap your scene definition in a `main()` function. When you save any changes to the file, the preview window will automatically update.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from sdforge import box, sphere
|
|
83
|
+
|
|
84
|
+
def main():
|
|
85
|
+
"""
|
|
86
|
+
While this script is running, try changing the values below and
|
|
87
|
+
saving the file. The render window will update automatically.
|
|
88
|
+
"""
|
|
89
|
+
box_size = 1.5
|
|
90
|
+
sphere_radius = 1.2
|
|
91
|
+
|
|
92
|
+
scene = box(box_size, radius=0.1) | sphere(sphere_radius)
|
|
93
|
+
return scene
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
# The render() function automatically enables hot-reloading by default
|
|
97
|
+
# and looks for a `main` function in this file to call upon reload.
|
|
98
|
+
scene = main()
|
|
99
|
+
scene.render()
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Saving to a Mesh File
|
|
103
|
+
You can save any static model to an `.stl`, `.obj`, or `.glb` file. The `.save()` method uses the Marching Cubes algorithm to generate a mesh from the SDF.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from sdforge import sphere, box
|
|
107
|
+
|
|
108
|
+
# A box with a sphere carved out of it
|
|
109
|
+
shape = box(1.5) - sphere(1.2)
|
|
110
|
+
|
|
111
|
+
# Save the model. Higher samples = more detail.
|
|
112
|
+
shape.save('model.obj', samples=2**22)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Core Concepts
|
|
116
|
+
|
|
117
|
+
### Primitives & Operations
|
|
118
|
+
Create complex objects by starting with primitives and combining them with Python's bitwise operators: `|` for union, `&` for intersection, and `-` for difference.
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from sdforge import sphere, box
|
|
122
|
+
|
|
123
|
+
# A box with a sphere carved out of it.
|
|
124
|
+
b = box(size=1.5)
|
|
125
|
+
s = sphere(r=1.0)
|
|
126
|
+
scene = b - s
|
|
127
|
+
|
|
128
|
+
scene.render()
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Transformations & Shaping
|
|
132
|
+
Chain methods to transform, shape, and repeat objects.
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
import numpy as np
|
|
136
|
+
from sdforge import box, Y
|
|
137
|
+
|
|
138
|
+
# A tall box
|
|
139
|
+
b = box(size=(0.5, 2.5, 0.5))
|
|
140
|
+
|
|
141
|
+
# Twist it around the Y-axis
|
|
142
|
+
# The 'k' parameter controls the amount of twist.
|
|
143
|
+
scene = b.twist(k=3.0).rotate(Y, np.pi / 4)
|
|
144
|
+
|
|
145
|
+
scene.render()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Materials
|
|
149
|
+
You can assign a unique color to any object or group of objects using the `.color()` method.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from sdforge import sphere, box
|
|
153
|
+
|
|
154
|
+
# A blue sphere is subtracted from a red box.
|
|
155
|
+
red_box = box(1.5, radius=0.1).color(1.0, 0.2, 0.2)
|
|
156
|
+
blue_sphere = sphere(1.2).color(0.3, 0.5, 1.0)
|
|
157
|
+
|
|
158
|
+
scene = red_box - blue_sphere
|
|
159
|
+
|
|
160
|
+
scene.render()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Camera & Lighting
|
|
164
|
+
You can override the default interactive camera and lighting to set a static viewpoint or create specific lighting conditions by passing `Camera` and `Light` objects to the renderer.
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from sdforge import box, sphere, Camera, Light
|
|
168
|
+
|
|
169
|
+
def main():
|
|
170
|
+
scene = box(1.5, radius=0.1) | sphere(1.2)
|
|
171
|
+
|
|
172
|
+
# A camera positioned at (4, 3, 4), looking at the origin.
|
|
173
|
+
cam = Camera(position=(4, 3, 4), target=(0, 0, 0), zoom=1.5)
|
|
174
|
+
|
|
175
|
+
# A light source with soft shadows
|
|
176
|
+
light = Light(position=(4, 5, 3), shadow_softness=16.0)
|
|
177
|
+
|
|
178
|
+
return scene, cam, light
|
|
179
|
+
|
|
180
|
+
if __name__ == '__main__':
|
|
181
|
+
scene, cam, light = main()
|
|
182
|
+
scene.render(camera=cam, light=light)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Advanced Usage
|
|
186
|
+
|
|
187
|
+
### Interactive Parameters
|
|
188
|
+
Use `Param` objects to define interactive, real-time parameters for your model. A compatible UI-enabled renderer would show these as sliders. The default renderer will use their default values.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from sdforge import box, Param
|
|
192
|
+
|
|
193
|
+
# Create Param objects to control different aspects of the scene.
|
|
194
|
+
# Param(name, default_value, min_value, max_value)
|
|
195
|
+
p_size = Param("Box Size", 0.8, 0.2, 2.0)
|
|
196
|
+
p_radius = Param("Corner Radius", 0.1, 0.0, 0.5)
|
|
197
|
+
|
|
198
|
+
# Use the Param objects just like regular numbers.
|
|
199
|
+
scene = box(size=p_size, radius=p_radius)
|
|
200
|
+
|
|
201
|
+
scene.render()
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Custom GLSL with `Forge`
|
|
205
|
+
For complex or highly-performant shapes, you can write GLSL code directly. This object integrates perfectly with the rest of the API.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
from sdforge import sphere, Forge
|
|
209
|
+
|
|
210
|
+
# A standard library primitive
|
|
211
|
+
s = sphere(1.2)
|
|
212
|
+
|
|
213
|
+
# A custom shape defined with GLSL
|
|
214
|
+
# 'p' is the vec3 point in space
|
|
215
|
+
custom_box = Forge("""
|
|
216
|
+
vec3 q = abs(p) - vec3(0.8);
|
|
217
|
+
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
|
|
218
|
+
""")
|
|
219
|
+
|
|
220
|
+
# The Forge object can be combined like any other shape
|
|
221
|
+
scene = s - custom_box
|
|
222
|
+
|
|
223
|
+
scene.render()
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Standalone GLSL Export
|
|
227
|
+
Generate a complete, self-contained GLSL fragment shader for your scene. This file can be used directly in applications like Godot, TouchDesigner, or Three.js.
|
|
228
|
+
|
|
229
|
+
```python
|
|
230
|
+
from sdforge import box, sphere
|
|
231
|
+
|
|
232
|
+
scene = box(1.5, radius=0.1) - sphere(1.2)
|
|
233
|
+
|
|
234
|
+
# This single call generates the entire shader file.
|
|
235
|
+
scene.export_shader("exported_shader.glsl")
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Installation
|
|
239
|
+
|
|
240
|
+
### 1. System Dependencies
|
|
241
|
+
|
|
242
|
+
The live viewer relies on `glfw`, which may require you to install its system-level libraries first. This is a common first step before installing the Python package.
|
|
243
|
+
|
|
244
|
+
**On Debian/Ubuntu:**
|
|
245
|
+
```bash
|
|
246
|
+
sudo apt-get install libglfw3-dev
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**On macOS (with Homebrew):**
|
|
250
|
+
```bash
|
|
251
|
+
brew install glfw
|
|
252
|
+
```
|
|
253
|
+
**On Windows:**
|
|
254
|
+
`glfw` is generally bundled with the Python wheels, so no separate installation is typically needed.
|
|
255
|
+
|
|
256
|
+
### 2. Python Package
|
|
257
|
+
|
|
258
|
+
The library and its core dependencies can be installed using pip.
|
|
259
|
+
|
|
260
|
+
**Standard Installation:**
|
|
261
|
+
This will install the core library, including `numpy`, `moderngl`, and `glfw`, enabling all fundamental features like modeling, live preview with hot-reloading, and exporting to `.stl` and `.obj` formats.
|
|
262
|
+
|
|
263
|
+
```bash
|
|
264
|
+
pip install sdforge
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
**Optional Features:**
|
|
268
|
+
For additional functionality, you can install "extras":
|
|
269
|
+
|
|
270
|
+
* **`.glb` Export:** To enable saving models to the GLB format, a modern and efficient format for web and game engines.
|
|
271
|
+
```bash
|
|
272
|
+
pip install "sdforge[export]"
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
* **Interactive UI:** To enable UI widgets like sliders for `Param` objects.
|
|
276
|
+
```bash
|
|
277
|
+
pip install "sdforge[ui]"
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
* **Full Installation:** To install all optional features at once.
|
|
281
|
+
```bash
|
|
282
|
+
pip install "sdforge[full]"
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Acknowledgements
|
|
286
|
+
|
|
287
|
+
This project is inspired by the simplicity and elegant API of Michael Fogleman's [fogleman/sdf](https://github.com/fogleman/sdf) library. SDF Forge aims to build on that foundation by adding a real-time, interactive GLSL-powered renderer.
|
sdforge-0.2.0/README.md
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source srcset="./assets/logo_dark.png" media="(prefers-color-scheme: dark)">
|
|
4
|
+
<source srcset="./assets/logo_light.png" media="(prefers-color-scheme: light)">
|
|
5
|
+
<img src="./assets/logo_light.png" alt="SDForge Logo" height="200">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
## About
|
|
10
|
+
|
|
11
|
+
SDF Forge is a Python library for creating 3D models using Signed Distance Functions (SDFs). It provides a real-time, interactive rendering experience in a native desktop window, powered by GLSL raymarching.
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Simple, Pythonic API:** Define complex shapes by combining primitives using standard operators (`|`, `-`, `&`) and chaining transformations.
|
|
16
|
+
- **Real-time Rendering with Hot-Reloading:** Get instant visual feedback in a lightweight native window powered by `moderngl` and `glfw`. Changes to your script are reloaded automatically.
|
|
17
|
+
- **Mesh Exporting:** Save your creations as `.stl`, `.obj`, or `.glb` files for 3D printing or use in other software.
|
|
18
|
+
- **Interactive Parameters:** Define parameters that can be controlled by UI sliders in real-time (with a compatible UI renderer).
|
|
19
|
+
- **Standalone GLSL Export:** Export your entire scene to a single, self-contained GLSL fragment shader for use in game engines or graphics applications.
|
|
20
|
+
- **Custom GLSL Primitives:** Write custom SDF logic directly in GLSL using the `Forge` object for maximum flexibility and performance.
|
|
21
|
+
|
|
22
|
+
## Getting Started
|
|
23
|
+
|
|
24
|
+
### Hello Forge
|
|
25
|
+
|
|
26
|
+
Define a simple shape and open a real-time preview window with just a few lines of code.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from sdforge import sphere, box
|
|
30
|
+
|
|
31
|
+
# A sphere intersected with a box
|
|
32
|
+
shape = sphere(1) & box(1.5)
|
|
33
|
+
|
|
34
|
+
# Render a preview in a native window.
|
|
35
|
+
# An interactive orbit camera is used by default.
|
|
36
|
+
shape.render()
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Live Preview & Hot-Reloading
|
|
40
|
+
For an interactive workflow, wrap your scene definition in a `main()` function. When you save any changes to the file, the preview window will automatically update.
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from sdforge import box, sphere
|
|
44
|
+
|
|
45
|
+
def main():
|
|
46
|
+
"""
|
|
47
|
+
While this script is running, try changing the values below and
|
|
48
|
+
saving the file. The render window will update automatically.
|
|
49
|
+
"""
|
|
50
|
+
box_size = 1.5
|
|
51
|
+
sphere_radius = 1.2
|
|
52
|
+
|
|
53
|
+
scene = box(box_size, radius=0.1) | sphere(sphere_radius)
|
|
54
|
+
return scene
|
|
55
|
+
|
|
56
|
+
if __name__ == "__main__":
|
|
57
|
+
# The render() function automatically enables hot-reloading by default
|
|
58
|
+
# and looks for a `main` function in this file to call upon reload.
|
|
59
|
+
scene = main()
|
|
60
|
+
scene.render()
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Saving to a Mesh File
|
|
64
|
+
You can save any static model to an `.stl`, `.obj`, or `.glb` file. The `.save()` method uses the Marching Cubes algorithm to generate a mesh from the SDF.
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
from sdforge import sphere, box
|
|
68
|
+
|
|
69
|
+
# A box with a sphere carved out of it
|
|
70
|
+
shape = box(1.5) - sphere(1.2)
|
|
71
|
+
|
|
72
|
+
# Save the model. Higher samples = more detail.
|
|
73
|
+
shape.save('model.obj', samples=2**22)
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Core Concepts
|
|
77
|
+
|
|
78
|
+
### Primitives & Operations
|
|
79
|
+
Create complex objects by starting with primitives and combining them with Python's bitwise operators: `|` for union, `&` for intersection, and `-` for difference.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from sdforge import sphere, box
|
|
83
|
+
|
|
84
|
+
# A box with a sphere carved out of it.
|
|
85
|
+
b = box(size=1.5)
|
|
86
|
+
s = sphere(r=1.0)
|
|
87
|
+
scene = b - s
|
|
88
|
+
|
|
89
|
+
scene.render()
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Transformations & Shaping
|
|
93
|
+
Chain methods to transform, shape, and repeat objects.
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import numpy as np
|
|
97
|
+
from sdforge import box, Y
|
|
98
|
+
|
|
99
|
+
# A tall box
|
|
100
|
+
b = box(size=(0.5, 2.5, 0.5))
|
|
101
|
+
|
|
102
|
+
# Twist it around the Y-axis
|
|
103
|
+
# The 'k' parameter controls the amount of twist.
|
|
104
|
+
scene = b.twist(k=3.0).rotate(Y, np.pi / 4)
|
|
105
|
+
|
|
106
|
+
scene.render()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Materials
|
|
110
|
+
You can assign a unique color to any object or group of objects using the `.color()` method.
|
|
111
|
+
|
|
112
|
+
```python
|
|
113
|
+
from sdforge import sphere, box
|
|
114
|
+
|
|
115
|
+
# A blue sphere is subtracted from a red box.
|
|
116
|
+
red_box = box(1.5, radius=0.1).color(1.0, 0.2, 0.2)
|
|
117
|
+
blue_sphere = sphere(1.2).color(0.3, 0.5, 1.0)
|
|
118
|
+
|
|
119
|
+
scene = red_box - blue_sphere
|
|
120
|
+
|
|
121
|
+
scene.render()
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Camera & Lighting
|
|
125
|
+
You can override the default interactive camera and lighting to set a static viewpoint or create specific lighting conditions by passing `Camera` and `Light` objects to the renderer.
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
from sdforge import box, sphere, Camera, Light
|
|
129
|
+
|
|
130
|
+
def main():
|
|
131
|
+
scene = box(1.5, radius=0.1) | sphere(1.2)
|
|
132
|
+
|
|
133
|
+
# A camera positioned at (4, 3, 4), looking at the origin.
|
|
134
|
+
cam = Camera(position=(4, 3, 4), target=(0, 0, 0), zoom=1.5)
|
|
135
|
+
|
|
136
|
+
# A light source with soft shadows
|
|
137
|
+
light = Light(position=(4, 5, 3), shadow_softness=16.0)
|
|
138
|
+
|
|
139
|
+
return scene, cam, light
|
|
140
|
+
|
|
141
|
+
if __name__ == '__main__':
|
|
142
|
+
scene, cam, light = main()
|
|
143
|
+
scene.render(camera=cam, light=light)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Advanced Usage
|
|
147
|
+
|
|
148
|
+
### Interactive Parameters
|
|
149
|
+
Use `Param` objects to define interactive, real-time parameters for your model. A compatible UI-enabled renderer would show these as sliders. The default renderer will use their default values.
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from sdforge import box, Param
|
|
153
|
+
|
|
154
|
+
# Create Param objects to control different aspects of the scene.
|
|
155
|
+
# Param(name, default_value, min_value, max_value)
|
|
156
|
+
p_size = Param("Box Size", 0.8, 0.2, 2.0)
|
|
157
|
+
p_radius = Param("Corner Radius", 0.1, 0.0, 0.5)
|
|
158
|
+
|
|
159
|
+
# Use the Param objects just like regular numbers.
|
|
160
|
+
scene = box(size=p_size, radius=p_radius)
|
|
161
|
+
|
|
162
|
+
scene.render()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Custom GLSL with `Forge`
|
|
166
|
+
For complex or highly-performant shapes, you can write GLSL code directly. This object integrates perfectly with the rest of the API.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from sdforge import sphere, Forge
|
|
170
|
+
|
|
171
|
+
# A standard library primitive
|
|
172
|
+
s = sphere(1.2)
|
|
173
|
+
|
|
174
|
+
# A custom shape defined with GLSL
|
|
175
|
+
# 'p' is the vec3 point in space
|
|
176
|
+
custom_box = Forge("""
|
|
177
|
+
vec3 q = abs(p) - vec3(0.8);
|
|
178
|
+
return length(max(q, 0.0)) + min(max(q.x, max(q.y, q.z)), 0.0);
|
|
179
|
+
""")
|
|
180
|
+
|
|
181
|
+
# The Forge object can be combined like any other shape
|
|
182
|
+
scene = s - custom_box
|
|
183
|
+
|
|
184
|
+
scene.render()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Standalone GLSL Export
|
|
188
|
+
Generate a complete, self-contained GLSL fragment shader for your scene. This file can be used directly in applications like Godot, TouchDesigner, or Three.js.
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from sdforge import box, sphere
|
|
192
|
+
|
|
193
|
+
scene = box(1.5, radius=0.1) - sphere(1.2)
|
|
194
|
+
|
|
195
|
+
# This single call generates the entire shader file.
|
|
196
|
+
scene.export_shader("exported_shader.glsl")
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## Installation
|
|
200
|
+
|
|
201
|
+
### 1. System Dependencies
|
|
202
|
+
|
|
203
|
+
The live viewer relies on `glfw`, which may require you to install its system-level libraries first. This is a common first step before installing the Python package.
|
|
204
|
+
|
|
205
|
+
**On Debian/Ubuntu:**
|
|
206
|
+
```bash
|
|
207
|
+
sudo apt-get install libglfw3-dev
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
**On macOS (with Homebrew):**
|
|
211
|
+
```bash
|
|
212
|
+
brew install glfw
|
|
213
|
+
```
|
|
214
|
+
**On Windows:**
|
|
215
|
+
`glfw` is generally bundled with the Python wheels, so no separate installation is typically needed.
|
|
216
|
+
|
|
217
|
+
### 2. Python Package
|
|
218
|
+
|
|
219
|
+
The library and its core dependencies can be installed using pip.
|
|
220
|
+
|
|
221
|
+
**Standard Installation:**
|
|
222
|
+
This will install the core library, including `numpy`, `moderngl`, and `glfw`, enabling all fundamental features like modeling, live preview with hot-reloading, and exporting to `.stl` and `.obj` formats.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pip install sdforge
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Optional Features:**
|
|
229
|
+
For additional functionality, you can install "extras":
|
|
230
|
+
|
|
231
|
+
* **`.glb` Export:** To enable saving models to the GLB format, a modern and efficient format for web and game engines.
|
|
232
|
+
```bash
|
|
233
|
+
pip install "sdforge[export]"
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
* **Interactive UI:** To enable UI widgets like sliders for `Param` objects.
|
|
237
|
+
```bash
|
|
238
|
+
pip install "sdforge[ui]"
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
* **Full Installation:** To install all optional features at once.
|
|
242
|
+
```bash
|
|
243
|
+
pip install "sdforge[full]"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Acknowledgements
|
|
247
|
+
|
|
248
|
+
This project is inspired by the simplicity and elegant API of Michael Fogleman's [fogleman/sdf](https://github.com/fogleman/sdf) library. SDF Forge aims to build on that foundation by adding a real-time, interactive GLSL-powered renderer.
|
|
File without changes
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from sdforge import box, sphere, Camera
|
|
3
|
+
|
|
4
|
+
def static_camera_example():
|
|
5
|
+
"""
|
|
6
|
+
Returns a scene and a fixed Camera object.
|
|
7
|
+
The renderer will use this camera's position and target.
|
|
8
|
+
"""
|
|
9
|
+
scene = box(1.5, radius=0.1) | sphere(1.2)
|
|
10
|
+
|
|
11
|
+
# Define a camera positioned at (4, 3, 4), looking at the origin.
|
|
12
|
+
cam = Camera(position=(4, 3, 4), target=(0, 0, 0), zoom=1.5)
|
|
13
|
+
|
|
14
|
+
return scene, cam
|
|
15
|
+
|
|
16
|
+
def interactive_camera_example():
|
|
17
|
+
"""
|
|
18
|
+
Returns only a scene.
|
|
19
|
+
When no camera is provided to the render function, it defaults
|
|
20
|
+
to an interactive orbit camera controlled by the mouse.
|
|
21
|
+
"""
|
|
22
|
+
scene = box(1.5, radius=0.1) | sphere(1.2)
|
|
23
|
+
return scene
|
|
24
|
+
|
|
25
|
+
def main():
|
|
26
|
+
"""
|
|
27
|
+
Renders an example based on a command-line argument.
|
|
28
|
+
"""
|
|
29
|
+
print("--- SDForge Camera Examples ---")
|
|
30
|
+
|
|
31
|
+
examples = {
|
|
32
|
+
"static": static_camera_example,
|
|
33
|
+
"interactive": interactive_camera_example,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if len(sys.argv) < 2:
|
|
37
|
+
print("\nPlease provide the name of an example to run.")
|
|
38
|
+
print("Available examples:")
|
|
39
|
+
for key in examples:
|
|
40
|
+
print(f" - {key}")
|
|
41
|
+
print(f"\nUsage: python {sys.argv[0]} <example_name>")
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
example_name = sys.argv[1]
|
|
45
|
+
scene_func = examples.get(example_name)
|
|
46
|
+
|
|
47
|
+
if not scene_func:
|
|
48
|
+
print(f"\nError: Example '{example_name}' not found.")
|
|
49
|
+
print("Available examples are:")
|
|
50
|
+
for key in examples:
|
|
51
|
+
print(f" - {key}")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
55
|
+
result = scene_func()
|
|
56
|
+
|
|
57
|
+
# Handle both return types: (scene, camera) or just scene
|
|
58
|
+
if isinstance(result, tuple):
|
|
59
|
+
scene, cam = result
|
|
60
|
+
scene.render(camera=cam)
|
|
61
|
+
else:
|
|
62
|
+
scene = result
|
|
63
|
+
scene.render() # camera=None, defaults to interactive
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from sdforge import box, sphere, Debug
|
|
3
|
+
|
|
4
|
+
def normals_debug_example():
|
|
5
|
+
"""Visualizes the surface normals as colors."""
|
|
6
|
+
scene = box(1.5, radius=0.1) - sphere(1.2)
|
|
7
|
+
debug = Debug('normals')
|
|
8
|
+
return scene, debug
|
|
9
|
+
|
|
10
|
+
def steps_debug_example():
|
|
11
|
+
"""Visualizes the number of raymarching steps."""
|
|
12
|
+
scene = box(1.5, radius=0.1) - sphere(1.2)
|
|
13
|
+
debug = Debug('steps')
|
|
14
|
+
return scene, debug
|
|
15
|
+
|
|
16
|
+
def main():
|
|
17
|
+
"""Renders a debug example based on a command-line argument."""
|
|
18
|
+
print("--- SDForge Debug Examples ---")
|
|
19
|
+
|
|
20
|
+
examples = {
|
|
21
|
+
"normals": normals_debug_example,
|
|
22
|
+
"steps": steps_debug_example,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if len(sys.argv) < 2:
|
|
26
|
+
print("\nPlease provide the name of an example to run.")
|
|
27
|
+
print("Available examples:")
|
|
28
|
+
for key in examples:
|
|
29
|
+
print(f" - {key}")
|
|
30
|
+
print(f"\nUsage: python {sys.argv[0]} <example_name>")
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
example_name = sys.argv[1]
|
|
34
|
+
scene_func = examples.get(example_name)
|
|
35
|
+
|
|
36
|
+
if not scene_func:
|
|
37
|
+
print(f"\nError: Example '{example_name}' not found.")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
print(f"Rendering: {example_name.replace('_', ' ').title()} Example")
|
|
41
|
+
result = scene_func()
|
|
42
|
+
|
|
43
|
+
scene, debug = None, None
|
|
44
|
+
for item in result:
|
|
45
|
+
from sdforge.core import SDFNode
|
|
46
|
+
if isinstance(item, SDFNode): scene = item
|
|
47
|
+
if isinstance(item, Debug): debug = item
|
|
48
|
+
|
|
49
|
+
if scene:
|
|
50
|
+
scene.render(debug=debug)
|
|
51
|
+
else:
|
|
52
|
+
print("Error: Example function did not return a scene object.")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
from sdforge import box, sphere, Param
|
|
4
|
+
|
|
5
|
+
def main():
|
|
6
|
+
"""
|
|
7
|
+
Demonstrates exporting a complete, standalone GLSL shader file.
|
|
8
|
+
"""
|
|
9
|
+
print("--- SDForge Shader Export Example ---")
|
|
10
|
+
|
|
11
|
+
# Create a scene with some complexity and a parameter.
|
|
12
|
+
p_radius = Param("Sphere Radius", 0.8, 0.5, 1.5)
|
|
13
|
+
scene = box(1.5, radius=0.1) - sphere(p_radius)
|
|
14
|
+
|
|
15
|
+
output_path = "exported_shader.glsl"
|
|
16
|
+
print(f"Exporting scene to '{output_path}'...")
|
|
17
|
+
|
|
18
|
+
# This single call generates the entire shader file.
|
|
19
|
+
scene.export_shader(output_path)
|
|
20
|
+
|
|
21
|
+
if os.path.exists(output_path):
|
|
22
|
+
print("\nSUCCESS! You can now use this shader file in other applications")
|
|
23
|
+
print("that support GLSL fragment shaders, like Godot, TouchDesigner, or Three.js.")
|
|
24
|
+
else:
|
|
25
|
+
print("\nERROR: Shader export failed.")
|
|
26
|
+
|
|
27
|
+
# We can also render the scene as usual.
|
|
28
|
+
return scene
|
|
29
|
+
|
|
30
|
+
if __name__ == "__main__":
|
|
31
|
+
scene = main()
|
|
32
|
+
scene.render()
|